diff --git a/Docs/flow_startup.md b/Docs/flow_startup.md
index 101ce59..3409421 100644
--- a/Docs/flow_startup.md
+++ b/Docs/flow_startup.md
@@ -98,12 +98,14 @@ public class MyComponent: Component
}
```
-2. For static method, create a new class and implement `ExecutableFunctionLibrary` to
- add static executable functions, then add `ExecutableFunctionAttribute`.
+2. For static method, create a new partial class and implement `ExecutableFunctionLibrary` to
+ add static executable functions, then add `ExecutableFunctionAttribute`.
+
+ >You must add `partial` modifier to let source generator work. Source generator will register static function pointer to the flow reflection system instead of using MethodInfo to enhance runtime performance.
```C#
-public class UnityExecutableFunctionLibrary: ExecutableFunctionLibrary
+public partial class UnityExecutableFunctionLibrary: ExecutableFunctionLibrary
{
// IsScriptMethod will consider UObject as function target type
// IsSelfTarget will let graph pass self reference as first parameter if self is UObject
@@ -472,6 +474,49 @@ public void Test()
}
```
+#### Source Generator
+
+In [executable function part](#executable-function), it is mentioned that source generator will register static methods to improve runtime performance.
+
+The following shows what SourceGenerator does.
+
+Source code:
+
+```C#
+///
+/// Executable function library for ceres
+///
+[CeresGroup("Ceres")]
+public partial class CeresExecutableLibrary: ExecutableFunctionLibrary
+{
+ [ExecutableFunction, CeresLabel("Set LogLevel")]
+ public static void Flow_SetLogLevel(LogType logType)
+ {
+ CeresAPI.LogLevel = logType;
+ }
+
+ [ExecutableFunction(ExecuteInDependency = true), CeresLabel("Get LogLevel")]
+ public static LogType Flow_GetLogLevel()
+ {
+ return CeresAPI.LogLevel;
+ }
+}
+```
+
+Generated code:
+
+```C#
+[CompilerGenerated]
+public partial class CeresExecutableLibrary
+{
+ protected override unsafe void CollectExecutableFunctions()
+ {
+ RegisterExecutableFunctions(nameof(Flow_SetLogLevel), 1, (delegate* )&Flow_SetLogLevel);
+ RegisterExecutableFunctions(nameof(Flow_GetLogLevel), 0, (delegate* )&Flow_GetLogLevel);
+ }
+}
+```
+
## Debug
To enable and disable debug mode, click `debug` button in the upper right corner.
diff --git a/Runtime/Core/Components/SceneVariableScope.cs b/Runtime/Core/Components/SceneVariableScope.cs
index 8e55c47..0f2da30 100644
--- a/Runtime/Core/Components/SceneVariableScope.cs
+++ b/Runtime/Core/Components/SceneVariableScope.cs
@@ -6,21 +6,27 @@ public class SceneVariableScope : MonoBehaviour, IVariableScope, IVariableSource
{
[SerializeReference]
private List sharedVariables = new();
+
public List SharedVariables => sharedVariables;
+
[SerializeField]
private GameVariableScope parentScope;
+
public GlobalVariables GlobalVariables { get; private set; }
- private bool initialized = false;
+
+ private bool _initialized;
+
private void Awake()
{
- if (!initialized)
+ if (!_initialized)
{
Initialize();
}
}
+
public void Initialize()
{
- initialized = true;
+ _initialized = true;
if (parentScope && parentScope.IsCurrentScope())
{
GlobalVariables = new GlobalVariables(sharedVariables, parentScope);
@@ -30,6 +36,7 @@ public void Initialize()
GlobalVariables = new GlobalVariables(sharedVariables);
}
}
+
private void OnDestroy()
{
GlobalVariables.Dispose();
diff --git a/Runtime/Core/Models/CeresAPI.cs b/Runtime/Core/Models/CeresAPI.cs
index bd049b7..3d8654f 100644
--- a/Runtime/Core/Models/CeresAPI.cs
+++ b/Runtime/Core/Models/CeresAPI.cs
@@ -11,6 +11,11 @@ public static class CeresAPI
///
public static LogType LogLevel { get; set; } = LogType.Log;
+ ///
+ /// Whether to log relink details
+ ///
+ public static bool LogUObjectRelink { get; set; }
+
public static void LogWarning(string message)
{
if(LogLevel >= LogType.Warning)
diff --git a/Runtime/Core/Models/GlobalVariables.cs b/Runtime/Core/Models/GlobalVariables.cs
index 7c0abd6..90c69c4 100644
--- a/Runtime/Core/Models/GlobalVariables.cs
+++ b/Runtime/Core/Models/GlobalVariables.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
-using UnityEngine;
-using Object = UnityEngine.Object;
+using UObject = UnityEngine.Object;
namespace Ceres
{
///
@@ -11,46 +10,53 @@ namespace Ceres
public class GlobalVariables : IVariableSource, IDisposable
{
public List SharedVariables { get; }
- private static GlobalVariables instance;
- public static GlobalVariables Instance => instance ?? FindOrCreateDefault();
- private readonly IVariableScope parentScope;
+
+ private static GlobalVariables _instance;
+
+ public static GlobalVariables Instance => _instance ?? FindOrCreateDefault();
+
+ private readonly IVariableScope _parentScope;
+
public GlobalVariables(List sharedVariables)
{
- instance = this;
+ _instance = this;
SharedVariables = new List(sharedVariables);
}
+
public GlobalVariables(List sharedVariables, IVariableScope parentScope)
{
- instance = this;
- this.parentScope = parentScope;
+ _instance = this;
+ _parentScope = parentScope;
SharedVariables = new List(sharedVariables);
if (parentScope != null)
{
sharedVariables.AddRange(parentScope.GlobalVariables.SharedVariables);
}
}
+
private static GlobalVariables FindOrCreateDefault()
{
- var scope = Object.FindObjectOfType();
+ var scope = UObject.FindObjectOfType();
if (scope != null)
{
scope.Initialize();
return scope.GlobalVariables;
}
- instance = new(new());
- return instance;
+ _instance = new GlobalVariables(new List());
+ return _instance;
}
+
public void Dispose()
{
- if (instance != this)
+ if (_instance != this)
{
- Debug.LogWarning("Only scope current used should be disposed!");
+ CeresAPI.LogWarning("Global variables can only be disposed in top level scope");
return;
}
- instance = null;
- if (parentScope != null)
+ _instance = null;
+ if (_parentScope != null)
{
- instance = parentScope.GlobalVariables;
+ _instance = _parentScope.GlobalVariables;
}
}
}
diff --git a/Runtime/Core/Models/Graph/CeresGraph.cs b/Runtime/Core/Models/Graph/CeresGraph.cs
index 051b351..ad769e1 100644
--- a/Runtime/Core/Models/Graph/CeresGraph.cs
+++ b/Runtime/Core/Models/Graph/CeresGraph.cs
@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
#if CERES_DISABLE_ILPP
+using Ceres.Utilities;
+using System.Reflection;
using Chris;
using System.Collections;
#endif
@@ -81,9 +82,13 @@ public BlackBoard BlackBoard
private readonly HashSet _internalVariables = new();
+ private readonly HashSet _internalPorts = new();
+
+#if CERES_DISABLE_ILPP
private static readonly Dictionary> VariableFieldInfoLookup = new();
private static readonly Dictionary> PortFieldInfoLookup = new();
+#endif
[SerializeReference]
public List variables;
@@ -142,8 +147,8 @@ protected static void InitVariables_Imp(CeresGraph graph)
var internalVariables = graph._internalVariables;
foreach (var node in graph.GetAllNodes())
{
- /* Variables will be collected by node using ILPP */
#if !CERES_DISABLE_ILPP
+ /* Variables will be collected by node using ILPP */
node.InitializeVariables();
foreach (var variable in node.SharedVariables)
{
@@ -156,7 +161,7 @@ protected static void InitVariables_Imp(CeresGraph graph)
{
fields = nodeType
.GetAllFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
- .Where(x => x.FieldType.IsSubclassOf(typeof(SharedVariable)) || IsIListVariable(x.FieldType))
+ .Where(x => x.FieldType.IsSubclassOf(typeof(SharedVariable)) || x.FieldType.IsIListVariable())
.ToList();
VariableFieldInfoLookup.Add(nodeType, fields);
}
@@ -187,47 +192,29 @@ protected static void InitVariables_Imp(CeresGraph graph)
}
}
- private static bool IsIListVariable(Type fieldType)
- {
- if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List<>))
- {
- var genericArgument = fieldType.GetGenericArguments()[0];
- if (typeof(SharedVariable).IsAssignableFrom(genericArgument))
- {
- return true;
- }
- }
- else if (fieldType.IsArray)
- {
- var elementType = fieldType.GetElementType();
- if (typeof(SharedVariable).IsAssignableFrom(elementType))
- {
- return true;
- }
- }
- return false;
- }
-
///
/// Traverse the graph and init all ports automatically
///
///
protected static void InitPorts_Imp(CeresGraph graph)
{
+ var internalPorts = graph._internalPorts;
foreach (var node in graph.GetAllNodes())
{
- /* Ports will be collected by node using ILPP */
#if !CERES_DISABLE_ILPP
+ /* Ports will be collected by node using ILPP */
node.InitializePorts();
foreach (var pair in node.Ports)
{
graph.LinkPort(pair.Value, pair.Key, node);
+ internalPorts.Add(pair.Value);
}
foreach (var pair in node.PortLists)
{
for(int i = 0; i < pair.Value.Count; i++)
{
graph.LinkPort((CeresPort)pair.Value[i], pair.Key, i, node);
+ internalPorts.Add((CeresPort)pair.Value[i]);
}
}
#else
@@ -236,7 +223,7 @@ protected static void InitPorts_Imp(CeresGraph graph)
{
fields = nodeType
.GetAllFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
- .Where(x => x.FieldType.IsSubclassOf(typeof(CeresPort)) || IsIListPort(x.FieldType))
+ .Where(x => x.FieldType.IsSubclassOf(typeof(CeresPort)) || x.FieldType.IsIListPort())
.ToList();
PortFieldInfoLookup.Add(nodeType, fields);
}
@@ -324,27 +311,6 @@ protected virtual void LinkPort(CeresPort port, CeresNode ownerNode, CeresPortDa
targetNode.NodeData.AddDependency(ownerNode.Guid);
}
}
-
- private static bool IsIListPort(Type fieldType)
- {
- if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List<>))
- {
- var genericArgument = fieldType.GetGenericArguments()[0];
- if (typeof(CeresPort).IsAssignableFrom(genericArgument))
- {
- return true;
- }
- }
- else if (fieldType.IsArray)
- {
- var elementType = fieldType.GetElementType();
- if (typeof(CeresPort).IsAssignableFrom(elementType))
- {
- return true;
- }
- }
- return false;
- }
protected static void CollectDependencyPath(CeresGraph graph)
{
@@ -417,23 +383,28 @@ public virtual void Dispose()
{
foreach (var variable in variables)
{
- variable.Unbind();
+ variable.Dispose();
}
+ variables.Clear();
foreach (var variable in _internalVariables)
{
- variable.Unbind();
+ variable.Dispose();
}
+ _internalVariables.Clear();
+ foreach (var port in _internalPorts)
+ {
+ port.Dispose();
+ }
+ _internalPorts.Clear();
_nodeDependencyPath = null;
- variables.Clear();
- _internalVariables.Clear();
foreach (var node in GetAllNodes())
{
node.Dispose();
}
nodes.Clear();
- if(_disposables != null)
+ if (_disposables != null)
{
foreach (var disposable in _disposables)
{
@@ -482,7 +453,7 @@ void VisitDependency(int destinationIndex, CeresNode current)
foreach (var dependency in dependencies)
{
var dependencyNode = graph.FindNode(dependency);
- if(dependencyNode == null || dependencyNode.NodeData.executionPath == ExecutionPath.Forward)
+ if (dependencyNode == null || dependencyNode.NodeData.executionPath == ExecutionPath.Forward)
{
continue;
}
diff --git a/Runtime/Core/Models/Graph/Nodes/CeresNode.cs b/Runtime/Core/Models/Graph/Nodes/CeresNode.cs
index 33eba47..7ffae9a 100644
--- a/Runtime/Core/Models/Graph/Nodes/CeresNode.cs
+++ b/Runtime/Core/Models/Graph/Nodes/CeresNode.cs
@@ -388,8 +388,6 @@ public readonly override string ToString()
[SerializeField]
private UObjectLink[] uobjectLinks = Array.Empty();
- public static bool LogUObjectRelink { get; set; }
-
public void AddPortData(CeresPortData data)
{
ArrayUtils.Add(ref portData, data);
@@ -456,7 +454,7 @@ public virtual CeresNodeData Clone()
public virtual void Serialize(CeresNode node)
{
var type = node.GetType();
- if(type.IsGenericType)
+ if (type.IsGenericType)
{
nodeType = new NodeType(type.GetGenericTypeDefinition());
genericParameters = type.GetGenericArguments().Select(SerializedType.ToString).ToArray();
@@ -468,7 +466,7 @@ public virtual void Serialize(CeresNode node)
serializedData = JsonUtility.ToJson(node);
#if UNITY_EDITOR
uobjectLinks = Array.Empty();
- if(!Application.isPlaying)
+ if (!Application.isPlaying)
{
var obj = JObject.Parse(serializedData);
/* Persistent instanceID */
@@ -477,7 +475,7 @@ public virtual void Serialize(CeresNode node)
if (prop.Name != "instanceID") continue;
var instanceId = (int)prop.Value;
var uObject = UnityEditor.EditorUtility.InstanceIDToObject(instanceId);
- if(uObject)
+ if (uObject)
{
ArrayUtils.Add(ref uobjectLinks, new UObjectLink(uObject));
}
@@ -505,7 +503,7 @@ public CeresNode Deserialize(Type outNodeType)
{
var linkedUObject = uObject.linkedObject;
prop.Value = linkedUObject == null ? 0 : linkedUObject.GetInstanceID();
- if(linkedUObject && LogUObjectRelink)
+ if (linkedUObject && CeresAPI.LogUObjectRelink)
{
CeresAPI.Log($"Relink UObject {instanceId} to {uObject.linkedObject.name} {prop.Value}");
}
diff --git a/Runtime/Core/Models/Graph/Nodes/InvalidNode.cs b/Runtime/Core/Models/Graph/Nodes/InvalidNode.cs
index fb7417f..e4fb05b 100644
--- a/Runtime/Core/Models/Graph/Nodes/InvalidNode.cs
+++ b/Runtime/Core/Models/Graph/Nodes/InvalidNode.cs
@@ -1,8 +1,10 @@
+using System;
using Ceres.Annotations;
using UnityEngine;
namespace Ceres.Graph
{
- [CeresGroup(Annotations.CeresGroup.Hidden)]
+ [Serializable]
+ [CeresGroup(CeresGroup.Hidden)]
[CeresLabel(NodeLabel)]
[NodeInfo(NodeInfo)]
internal sealed class InvalidNode : CeresNode
diff --git a/Runtime/Core/Models/Graph/Ports/CeresPort.cs b/Runtime/Core/Models/Graph/Ports/CeresPort.cs
index 356327d..1d5a146 100644
--- a/Runtime/Core/Models/Graph/Ports/CeresPort.cs
+++ b/Runtime/Core/Models/Graph/Ports/CeresPort.cs
@@ -7,7 +7,6 @@
using Chris;
using Chris.Serialization;
using Unity.Collections.LowLevel.Unsafe;
-using UnityEngine;
namespace Ceres.Graph
{
public interface IPort
@@ -23,7 +22,7 @@ public interface IPort: IPort
///
/// Base class for ceres graph port
///
- public abstract class CeresPort: IPort
+ public abstract class CeresPort: IPort, IDisposable
{
protected class PortCompatibleStructure
{
@@ -122,13 +121,10 @@ public virtual void AssignValueGetter(IPort port)
{
AdaptedGetter = port;
}
-
- static CeresPort()
+
+ public virtual void Dispose()
{
- /* Implicit conversation */
- CeresPort.MakeCompatibleTo(f => (int)f);
- CeresPort.MakeCompatibleTo(i => i);
- CeresPort.MakeCompatibleTo(vector3 => vector3);
+ AdaptedGetter = null;
}
}
@@ -269,6 +265,13 @@ public override object GetValue()
{
return Value;
}
+
+ public override void Dispose()
+ {
+ _getter = null;
+ defaultValue = default;
+ base.Dispose();
+ }
}
public class AdapterPort: IPort
diff --git a/Runtime/Core/Models/Graph/Ports/CeresPortSetup.cs b/Runtime/Core/Models/Graph/Ports/CeresPortSetup.cs
new file mode 100644
index 0000000..04dad84
--- /dev/null
+++ b/Runtime/Core/Models/Graph/Ports/CeresPortSetup.cs
@@ -0,0 +1,40 @@
+using Ceres.Graph;
+using Chris.Schedulers;
+using Unity.Collections.LowLevel.Unsafe;
+using UnityEngine;
+namespace Chris.Gameplay.Flow.Utilities
+{
+ internal static class CeresPortSetup
+ {
+ [RuntimeInitializeOnLoadMethod]
+#if UNITY_EDITOR
+ [UnityEditor.InitializeOnLoadMethod]
+#endif
+ private static void InitializeOnLoad()
+ {
+ /* Implicit conversation */
+ // ======================== Value type =========================== //
+ CeresPort.MakeCompatibleTo(f => (int)f);
+ CeresPort.MakeCompatibleTo(i => i);
+ CeresPort.MakeCompatibleTo(vector3 => vector3);
+ // ======================== Value type =========================== //
+ // ========================= Scheduler =========================== //
+ unsafe
+ {
+ CeresPort.MakeCompatibleTo(handle =>
+ {
+ double value = default;
+ UnsafeUtility.CopyStructureToPtr(ref handle, &value);
+ return value;
+ });
+ CeresPort.MakeCompatibleTo(d =>
+ {
+ SchedulerHandle handle = default;
+ UnsafeUtility.CopyStructureToPtr(ref d, &handle);
+ return handle;
+ });
+ }
+ // ========================= Scheduler =========================== //
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Core/Models/Graph/Ports/CeresPortSetup.cs.meta b/Runtime/Core/Models/Graph/Ports/CeresPortSetup.cs.meta
new file mode 100644
index 0000000..6fe1d76
--- /dev/null
+++ b/Runtime/Core/Models/Graph/Ports/CeresPortSetup.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: fa28bf51a2ee4255a53fd050d99fe75e
+timeCreated: 1737783495
\ No newline at end of file
diff --git a/Runtime/Core/Models/Variables/SharedVariable.cs b/Runtime/Core/Models/Variables/SharedVariable.cs
index d3d8e3f..07c8839 100644
--- a/Runtime/Core/Models/Variables/SharedVariable.cs
+++ b/Runtime/Core/Models/Variables/SharedVariable.cs
@@ -7,7 +7,7 @@ namespace Ceres
/// Variable can be shared between behaviors in behavior tree
///
[Serializable]
- public abstract class SharedVariable : ICloneable
+ public abstract class SharedVariable : ICloneable, IDisposable
{
///
/// Whether variable is shared
@@ -67,7 +67,7 @@ public string Name
///
/// Unbind self
///
- public abstract void Unbind();
+ public abstract void Dispose();
///
/// Clone shared variable by deep copy, an option here is to override for preventing using reflection
@@ -159,7 +159,7 @@ public override void Bind(SharedVariable other)
}
}
- public override void Unbind()
+ public override void Dispose()
{
Getter = null;
Setter = null;
diff --git a/Runtime/Core/Utilities/GraphTypeExtensions.cs b/Runtime/Core/Utilities/GraphTypeExtensions.cs
index 88164d7..063d98c 100644
--- a/Runtime/Core/Utilities/GraphTypeExtensions.cs
+++ b/Runtime/Core/Utilities/GraphTypeExtensions.cs
@@ -108,5 +108,47 @@ public static bool IsIList(this Type type)
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) return true;
return type.IsArray;
}
+
+ public static bool IsIListPort(this Type fieldType)
+ {
+ if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List<>))
+ {
+ var genericArgument = fieldType.GetGenericArguments()[0];
+ if (typeof(CeresPort).IsAssignableFrom(genericArgument))
+ {
+ return true;
+ }
+ }
+ else if (fieldType.IsArray)
+ {
+ var elementType = fieldType.GetElementType();
+ if (typeof(CeresPort).IsAssignableFrom(elementType))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static bool IsIListVariable(this Type fieldType)
+ {
+ if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List<>))
+ {
+ var genericArgument = fieldType.GetGenericArguments()[0];
+ if (typeof(SharedVariable).IsAssignableFrom(genericArgument))
+ {
+ return true;
+ }
+ }
+ else if (fieldType.IsArray)
+ {
+ var elementType = fieldType.GetElementType();
+ if (typeof(SharedVariable).IsAssignableFrom(elementType))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
}
}
\ No newline at end of file
diff --git a/Runtime/Core/Utilities/SubclassSearchUtility.cs b/Runtime/Core/Utilities/SubclassSearchUtility.cs
index d33a2a9..4bdbf4d 100644
--- a/Runtime/Core/Utilities/SubclassSearchUtility.cs
+++ b/Runtime/Core/Utilities/SubclassSearchUtility.cs
@@ -3,7 +3,6 @@
using System.Linq;
using System.Reflection;
using Ceres.Annotations;
-
namespace Ceres.Utilities
{
public static class SubClassSearchUtility
diff --git a/Runtime/Flow/Models/ExecutableReflection.cs b/Runtime/Flow/Models/ExecutableReflection.cs
index 259da06..faa5381 100644
--- a/Runtime/Flow/Models/ExecutableReflection.cs
+++ b/Runtime/Flow/Models/ExecutableReflection.cs
@@ -4,6 +4,8 @@
using System.Reflection;
using Ceres.Annotations;
using Ceres.Graph.Flow.Annotations;
+using Ceres.Graph.Flow.Utilities;
+using UnityEngine.Assertions;
namespace Ceres.Graph.Flow
{
public enum ExecutableFunctionType
@@ -157,7 +159,7 @@ public bool AmbiguousEquals(ExecutableFunctionInfo other)
public class ExecutableReflection: ExecutableReflection
{
- public class ExecutableFunction: Flow.ExecutableFunction
+ public unsafe class ExecutableFunction: Flow.ExecutableFunction
{
public readonly ExecutableFunctionInfo FunctionInfo;
@@ -167,6 +169,8 @@ public class ExecutableFunction: Flow.ExecutableFunction
public readonly ExecutableFunc ExecutableFunc;
+ public readonly void* FunctionPtr;
+
internal ExecutableFunction(ExecutableFunctionInfo functionInfo, MethodInfo methodInfo)
{
FunctionInfo = functionInfo;
@@ -174,19 +178,45 @@ internal ExecutableFunction(ExecutableFunctionInfo functionInfo, MethodInfo meth
ExecutableAction = new ExecutableAction(MethodInfo);
ExecutableFunc = new ExecutableFunc(MethodInfo);
}
+
+ internal ExecutableFunction(ExecutableFunctionInfo functionInfo, void* functionPtr)
+ {
+ FunctionInfo = functionInfo;
+ FunctionPtr = functionPtr;
+ ExecutableAction = new ExecutableAction(FunctionPtr);
+ ExecutableFunc = new ExecutableFunc(FunctionPtr);
+ }
+
+ internal ExecutableFunction(ExecutableFunctionInfo functionInfo, MethodInfo methodInfo, void* functionPtr)
+ {
+ FunctionInfo = functionInfo;
+ MethodInfo = methodInfo;
+ FunctionPtr = functionPtr;
+ ExecutableAction = new ExecutableAction(FunctionPtr);
+ ExecutableFunc = new ExecutableFunc(FunctionPtr);
+ }
}
private readonly List _functions = new();
- public ExecutableReflection()
+ private ExecutableReflection()
{
- typeof(TTarget).GetMethods(BindingFlags.Static | BindingFlags.Public)
- .Where(x=>x.GetCustomAttribute() != null)
- .ToList()
- .ForEach(methodInfo =>
- {
- RegisterExecutableFunction(ExecutableFunctionType.StaticMethod, methodInfo);
- });
+ _instance = this;
+ if (typeof(TTarget).IsSubclassOf(typeof(ExecutableFunctionLibrary)))
+ {
+#if UNITY_EDITOR
+ typeof(TTarget).GetMethods(BindingFlags.Static | BindingFlags.Public)
+ .Where(x => x.GetCustomAttribute() != null)
+ .ToList()
+ .ForEach(methodInfo =>
+ {
+ RegisterExecutableFunction(ExecutableFunctionType.StaticMethod, methodInfo);
+ });
+#endif
+ Activator.CreateInstance(typeof(TTarget));
+ return;
+ }
+
typeof(TTarget).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(x=>x.GetCustomAttribute() != null)
.ToList()
@@ -222,6 +252,23 @@ private void RegisterExecutableFunction(ExecutableFunctionType functionType, Met
var functionStructure = new ExecutableFunction(functionInfo, methodInfo);
_functions.Add(functionStructure);
}
+
+ internal static unsafe void RegisterStaticExecutableFunction(string functionName, int parameterCount, void* functionPtr)
+ {
+ var functionInfo = new ExecutableFunctionInfo(ExecutableFunctionType.StaticMethod, functionName, parameterCount);
+#if UNITY_EDITOR
+ var function = Instance.FindFunction_Internal(functionInfo);
+ if (function != null)
+ {
+ Instance._functions.Remove(function);
+ var overrideStructure = new ExecutableFunction(functionInfo, function.MethodInfo, functionPtr);
+ Instance._functions.Add(overrideStructure);
+ return;
+ }
+#endif
+ var functionStructure = new ExecutableFunction(functionInfo, functionPtr);
+ Instance._functions.Add(functionStructure);
+ }
private ExecutableFunction FindFunction_Internal(ExecutableFunctionInfo functionInfo)
{
@@ -260,7 +307,7 @@ private ExecutableFunction GetFunction_Internal(ExecutableFunctionInfo functionI
var functionType = functionInfo.FunctionType;
var functionName = functionInfo.FunctionName;
- MethodInfo methodInfo = functionType switch
+ var methodInfo = functionType switch
{
ExecutableFunctionType.PropertySetter => typeof(TTarget).GetProperty(functionName,
BindingFlags.Public | BindingFlags.Instance)!.SetMethod,
@@ -277,29 +324,34 @@ private ExecutableFunction GetFunction_Internal(ExecutableFunctionInfo functionI
}
- public abstract class ExecutableDelegate
+ public abstract unsafe class ExecutableDelegate
{
protected Delegate Delegate;
- protected IntPtr FunctionPtr;
+ protected readonly void* FunctionPtr;
- protected readonly bool IsStatic;
+ public readonly bool IsStatic;
protected readonly MethodInfo MethodInfo;
protected ExecutableDelegate(MethodInfo methodInfo)
{
+#if !UNITY_EDITOR
+ Assert.IsFalse(methodInfo.IsStatic);
+#endif
MethodInfo = methodInfo;
- IsStatic = methodInfo.IsStatic;
- if (IsStatic)
- {
- FunctionPtr = methodInfo.MethodHandle.GetFunctionPointer();
- }
+ IsStatic = false;
+ }
+
+ protected ExecutableDelegate(void* functionPtr)
+ {
+ IsStatic = true;
+ FunctionPtr = functionPtr;
}
protected static void ReallocateDelegateIfNeed(ref Delegate outDelegate, MethodInfo methodInfo) where TDelegate: Delegate
{
- if (methodInfo.IsStatic)
+ if (methodInfo == null || methodInfo.IsStatic)
{
return;
}
@@ -317,12 +369,16 @@ protected static void ReallocateDelegateIfNeed(ref Delegate outDelega
}
}
- public class ExecutableAction: ExecutableDelegate
+ public unsafe class ExecutableAction: ExecutableDelegate
{
internal ExecutableAction(MethodInfo methodInfo) : base(methodInfo)
{
}
+ internal ExecutableAction(void* functionPtr) : base(functionPtr)
+ {
+ }
+
private void ReallocateDelegateIfNeed()
{
ReallocateDelegateIfNeed>(ref Delegate, MethodInfo);
@@ -358,7 +414,7 @@ private void ReallocateDelegateIfNeed()
ReallocateDelegateIfNeed>(ref Delegate, MethodInfo);
}
- public unsafe void Invoke(TTarget target)
+ public void Invoke(TTarget target)
{
ReallocateDelegateIfNeed();
if (IsStatic)
@@ -366,10 +422,11 @@ public unsafe void Invoke(TTarget target)
((delegate* )FunctionPtr)();
return;
}
+ Assert.IsNotNull(Delegate);
((Action)Delegate).Invoke(target);
}
- public unsafe void Invoke(TTarget target, T1 arg1)
+ public void Invoke(TTarget target, T1 arg1)
{
ReallocateDelegateIfNeed();
if (IsStatic)
@@ -377,10 +434,11 @@ public unsafe void Invoke(TTarget target, T1 arg1)
((delegate* )FunctionPtr)(arg1);
return;
}
+ Assert.IsNotNull(Delegate);
((Action)Delegate).Invoke(target, arg1);
}
- public unsafe void Invoke(TTarget target, T1 arg1, T2 arg2)
+ public void Invoke(TTarget target, T1 arg1, T2 arg2)
{
ReallocateDelegateIfNeed();
if (IsStatic)
@@ -388,10 +446,11 @@ public unsafe void Invoke(TTarget target, T1 arg1, T2 arg2)
((delegate* )FunctionPtr)(arg1, arg2);
return;
}
+ Assert.IsNotNull(Delegate);
((Action)Delegate).Invoke(target, arg1, arg2);
}
- public unsafe void Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3)
+ public void Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3)
{
ReallocateDelegateIfNeed();
if (IsStatic)
@@ -399,10 +458,11 @@ public unsafe void Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3)
((delegate* )FunctionPtr)(arg1, arg2, arg3);
return;
}
+ Assert.IsNotNull(Delegate);
((Action)Delegate).Invoke(target, arg1, arg2, arg3);
}
- public unsafe void Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+ public void Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
{
ReallocateDelegateIfNeed();
if (IsStatic)
@@ -410,10 +470,11 @@ public unsafe void Invoke(TTarget target, T1 arg1, T2 arg2, T3 a
((delegate* )FunctionPtr)(arg1, arg2, arg3, arg4);
return;
}
+ Assert.IsNotNull(Delegate);
((Action)Delegate).Invoke(target, arg1, arg2, arg3, arg4);
}
- public unsafe void Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+ public void Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
{
ReallocateDelegateIfNeed();
if (IsStatic)
@@ -421,10 +482,11 @@ public unsafe void Invoke(TTarget target, T1 arg1, T2 arg2,
((delegate* )FunctionPtr)(arg1, arg2, arg3, arg4, arg5);
return;
}
+ Assert.IsNotNull(Delegate);
((Action)Delegate).Invoke(target, arg1, arg2, arg3, arg4, arg5);
}
- public unsafe void Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+ public void Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
{
ReallocateDelegateIfNeed();
if (IsStatic)
@@ -432,16 +494,21 @@ public unsafe void Invoke(TTarget target, T1 arg1, T2 ar
((delegate* )FunctionPtr)(arg1, arg2, arg3, arg4, arg5, arg6);
return;
}
+ Assert.IsNotNull(Delegate);
((Action)Delegate).Invoke(target, arg1, arg2, arg3, arg4, arg5, arg6);
}
}
- public class ExecutableFunc: ExecutableDelegate
+ public unsafe class ExecutableFunc: ExecutableDelegate
{
internal ExecutableFunc(MethodInfo methodInfo) : base(methodInfo)
{
}
+ internal ExecutableFunc(void* functionPtr) : base(functionPtr)
+ {
+ }
+
private void ReallocateDelegateIfNeed()
{
ReallocateDelegateIfNeed>(ref Delegate, MethodInfo);
@@ -477,73 +544,80 @@ private void ReallocateDelegateIfNeed()
ReallocateDelegateIfNeed>(ref Delegate, MethodInfo);
}
- public unsafe TR Invoke(TTarget target)
+ public TR Invoke
(TTarget target)
{
ReallocateDelegateIfNeed
();
if (IsStatic)
{
return ((delegate*
)FunctionPtr)();
}
+ Assert.IsNotNull(Delegate);
return ((Func)Delegate).Invoke(target);
}
- public unsafe TR Invoke(TTarget target, T1 arg1)
+ public TR Invoke(TTarget target, T1 arg1)
{
ReallocateDelegateIfNeed();
if (IsStatic)
{
return ((delegate* )FunctionPtr)(arg1);
}
+ Assert.IsNotNull(Delegate);
return ((Func)Delegate).Invoke(target, arg1);
}
- public unsafe TR Invoke(TTarget target, T1 arg1, T2 arg2)
+ public TR Invoke(TTarget target, T1 arg1, T2 arg2)
{
ReallocateDelegateIfNeed();
if (IsStatic)
{
return ((delegate* )FunctionPtr)(arg1, arg2);
}
+ Assert.IsNotNull(Delegate);
return ((Func)Delegate).Invoke(target, arg1, arg2);
}
- public unsafe TR Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3)
+ public TR Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3)
{
ReallocateDelegateIfNeed();
if (IsStatic)
{
return ((delegate* )FunctionPtr)(arg1, arg2, arg3);
}
+ Assert.IsNotNull(Delegate);
return ((Func)Delegate).Invoke(target, arg1, arg2, arg3);
}
- public unsafe TR Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
+ public TR Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
{
ReallocateDelegateIfNeed();
if (IsStatic)
{
return ((delegate* )FunctionPtr)(arg1, arg2, arg3, arg4);
}
+ Assert.IsNotNull(Delegate);
return ((Func)Delegate).Invoke(target, arg1, arg2, arg3, arg4);
}
- public unsafe TR Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
+ public TR Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
{
ReallocateDelegateIfNeed();
if (IsStatic)
{
return ((delegate* )FunctionPtr)(arg1, arg2, arg3, arg4, arg5);
}
+ Assert.IsNotNull(Delegate);
return ((Func)Delegate).Invoke(target, arg1, arg2, arg3, arg4, arg5);
}
- public unsafe TR Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
+ public TR Invoke(TTarget target, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
{
ReallocateDelegateIfNeed();
if (IsStatic)
{
return ((delegate* )FunctionPtr)(arg1, arg2, arg3, arg4, arg5, arg6);
}
+ Assert.IsNotNull(Delegate);
return ((Func)Delegate).Invoke(target, arg1, arg2, arg3, arg4, arg5, arg6);
}
}
diff --git a/Runtime/Flow/Models/Nodes/Utilities/FlowNode_ExecuteFunction.cs b/Runtime/Flow/Models/Nodes/Utilities/FlowNode_ExecuteFunction.cs
index abedca1..476638c 100644
--- a/Runtime/Flow/Models/Nodes/Utilities/FlowNode_ExecuteFunction.cs
+++ b/Runtime/Flow/Models/Nodes/Utilities/FlowNode_ExecuteFunction.cs
@@ -2,6 +2,7 @@
using System.Reflection;
using Ceres.Annotations;
using UnityEngine;
+using UnityEngine.Assertions;
using UObject = UnityEngine.Object;
namespace Ceres.Graph.Flow.Utilities
{
@@ -124,6 +125,7 @@ public void OnAfterDeserialize()
try
{
Delegate = GetExecutableFunction().ExecutableFunc;
+ Assert.IsTrue(Delegate.IsStatic == isStatic);
}
catch(ArgumentException)
{
@@ -156,6 +158,7 @@ public void OnAfterDeserialize()
try
{
Delegate = GetExecutableFunction().ExecutableAction;
+ Assert.IsTrue(Delegate.IsStatic == isStatic);
}
catch(ArgumentException)
{
diff --git a/Runtime/Flow/Models/Nodes/Utilities/FlowNode_SoftAssetReferenceTLoadAssetAsync.cs b/Runtime/Flow/Models/Nodes/Utilities/FlowNode_SoftAssetReferenceTLoadAssetAsync.cs
new file mode 100644
index 0000000..9b38247
--- /dev/null
+++ b/Runtime/Flow/Models/Nodes/Utilities/FlowNode_SoftAssetReferenceTLoadAssetAsync.cs
@@ -0,0 +1,42 @@
+using System;
+using Ceres.Annotations;
+using R3.Chris;
+using Chris.Resource;
+using UObject = UnityEngine.Object;
+namespace Ceres.Graph.Flow.Utilities
+{
+ [Serializable]
+ [CeresGroup("Utilities")]
+ [CeresLabel("Load {0} Async")]
+ public sealed class FlowNode_SoftAssetReferenceTLoadAssetAsync: FlowNode where TObject: UObject
+ {
+ [InputPort, HideInGraphEditor]
+ public CeresPort> reference;
+
+ [InputPort]
+ public DelegatePort> onComplete;
+
+ protected override void LocalExecute(ExecutionContext executionContext)
+ {
+ reference.Value.LoadAsync().AddTo(executionContext.Graph).RegisterCallback(onComplete.Value);
+ }
+ }
+
+ [Serializable]
+ [CeresGroup("Utilities")]
+ [CeresLabel("Load Asset Async")]
+ [RequirePort(typeof(SoftAssetReference))]
+ public sealed class FlowNode_SoftAssetReferenceLoadAssetAsync: FlowNode
+ {
+ [InputPort, HideInGraphEditor]
+ public CeresPort reference;
+
+ [InputPort]
+ public DelegatePort> onComplete;
+
+ protected override void LocalExecute(ExecutionContext executionContext)
+ {
+ reference.Value.LoadAsync().AddTo(executionContext.Graph).RegisterCallback(onComplete.Value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Flow/Models/Nodes/Utilities/FlowNode_SoftAssetReferenceTLoadAssetAsync.cs.meta b/Runtime/Flow/Models/Nodes/Utilities/FlowNode_SoftAssetReferenceTLoadAssetAsync.cs.meta
new file mode 100644
index 0000000..c4090f3
--- /dev/null
+++ b/Runtime/Flow/Models/Nodes/Utilities/FlowNode_SoftAssetReferenceTLoadAssetAsync.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1317c567fc724c88a31d94db37e0c346
+timeCreated: 1736668939
\ No newline at end of file
diff --git a/Runtime/Flow/Models/Nodes/Utilities/Templates/FlowNode_SoftAssetReferenceTLoadAssetAsync_Template.cs b/Runtime/Flow/Models/Nodes/Utilities/Templates/FlowNode_SoftAssetReferenceTLoadAssetAsync_Template.cs
new file mode 100644
index 0000000..b59fe83
--- /dev/null
+++ b/Runtime/Flow/Models/Nodes/Utilities/Templates/FlowNode_SoftAssetReferenceTLoadAssetAsync_Template.cs
@@ -0,0 +1,36 @@
+using System;
+using Chris;
+using Chris.Resource;
+namespace Ceres.Graph.Flow.Utilities
+{
+ public class FlowNode_SoftAssetReferenceTLoadAssetAsync_Template: GenericNodeTemplate
+ {
+ public override bool RequirePort()
+ {
+ return true;
+ }
+
+ public override bool CanFilterPort(Type portValueType)
+ {
+ if (portValueType == null) return false;
+ if (!portValueType.IsGenericType) return false;
+ return portValueType.GetGenericTypeDefinition() == typeof(SoftAssetReference<>);
+ }
+
+ public override Type[] GetGenericArguments(Type portValueType, Type selectArgumentType)
+ {
+ return new[] { selectArgumentType };
+ }
+
+ public override Type[] GetAvailableArgumentTypes(Type portValueType)
+ {
+ return new[] { ReflectionUtility.GetGenericArgumentType(portValueType)};
+ }
+
+ protected override string GetTargetName(Type[] argumentTypes)
+ {
+ var genericType = typeof(SoftAssetReference<>).MakeGenericType(argumentTypes[0]);
+ return CeresNode.GetTargetSubtitle(genericType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Flow/Models/Nodes/Utilities/Templates/FlowNode_SoftAssetReferenceTLoadAssetAsync_Template.cs.meta b/Runtime/Flow/Models/Nodes/Utilities/Templates/FlowNode_SoftAssetReferenceTLoadAssetAsync_Template.cs.meta
new file mode 100644
index 0000000..766cc4c
--- /dev/null
+++ b/Runtime/Flow/Models/Nodes/Utilities/Templates/FlowNode_SoftAssetReferenceTLoadAssetAsync_Template.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: fd0f8c06799d48da9454b52a4ac3a5e6
+timeCreated: 1736670082
\ No newline at end of file
diff --git a/Runtime/Flow/Utilities/ExecutableFunctionRegistry.cs b/Runtime/Flow/Utilities/ExecutableFunctionRegistry.cs
index 00a554c..6ee0f5a 100644
--- a/Runtime/Flow/Utilities/ExecutableFunctionRegistry.cs
+++ b/Runtime/Flow/Utilities/ExecutableFunctionRegistry.cs
@@ -9,11 +9,39 @@ namespace Ceres.Graph.Flow.Utilities
///
/// Derived from this class to add custom static functions
///
+ /// Must add partial modifier
public abstract class ExecutableFunctionLibrary
{
+ ///
+ /// Collect all static executable functions in this library
+ ///
+ protected virtual void CollectExecutableFunctions()
+ {
+
+ }
+ protected ExecutableFunctionLibrary()
+ {
+ CollectExecutableFunctions();
+ }
+
+ ///
+ /// Register static executable function to reflection system
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected static unsafe void RegisterExecutableFunctions(string functionName, int parameterCount, void* functionPtr)
+ where TLibrary: ExecutableFunctionLibrary
+ {
+ ExecutableReflection.RegisterStaticExecutableFunction(functionName, parameterCount, functionPtr);
+ }
}
+ ///
+ /// Helper class for query executable functions
+ ///
public class ExecutableFunctionRegistry
{
private readonly Dictionary _retargetFunctionTables;
diff --git a/Runtime/Flow/Utilities/Libraries/CeresExecutableLibrary.cs b/Runtime/Flow/Utilities/Libraries/CeresExecutableLibrary.cs
index ad92888..292de54 100644
--- a/Runtime/Flow/Utilities/Libraries/CeresExecutableLibrary.cs
+++ b/Runtime/Flow/Utilities/Libraries/CeresExecutableLibrary.cs
@@ -1,6 +1,5 @@
using Ceres.Annotations;
using Ceres.Graph.Flow.Annotations;
-using Chris.Serialization;
using UnityEngine;
using UnityEngine.Scripting;
namespace Ceres.Graph.Flow.Utilities
@@ -9,9 +8,8 @@ namespace Ceres.Graph.Flow.Utilities
/// Executable function library for ceres
///
[Preserve]
- [FormerlySerializedType("Ceres.Graph.Flow.Utilities.CeresExecutableFunctionLibrary, Ceres")]
[CeresGroup("Ceres")]
- public class CeresExecutableLibrary: ExecutableFunctionLibrary
+ public partial class CeresExecutableLibrary: ExecutableFunctionLibrary
{
[ExecutableFunction, CeresLabel("Set LogLevel")]
public static void Flow_SetLogLevel(LogType logType)
diff --git a/Runtime/Flow/Utilities/Libraries/MathExecutableLibrary.cs b/Runtime/Flow/Utilities/Libraries/MathExecutableLibrary.cs
index 25a1033..9675910 100644
--- a/Runtime/Flow/Utilities/Libraries/MathExecutableLibrary.cs
+++ b/Runtime/Flow/Utilities/Libraries/MathExecutableLibrary.cs
@@ -1,6 +1,5 @@
using Ceres.Annotations;
using Ceres.Graph.Flow.Annotations;
-using Chris.Serialization;
using UnityEngine.Scripting;
using UnityEngine;
namespace Ceres.Graph.Flow.Utilities
@@ -9,8 +8,7 @@ namespace Ceres.Graph.Flow.Utilities
/// Executable function library for basic math operations
///
[Preserve]
- [FormerlySerializedType("Ceres.Graph.Flow.Utilities.MathExecutableFunctionLibrary, Ceres")]
- public class MathExecutableLibrary : ExecutableFunctionLibrary
+ public partial class MathExecutableLibrary : ExecutableFunctionLibrary
{
#region Float
diff --git a/Runtime/Flow/Utilities/Libraries/ResourceExecutableLibrary..cs b/Runtime/Flow/Utilities/Libraries/ResourceExecutableLibrary..cs
new file mode 100644
index 0000000..ba52cfc
--- /dev/null
+++ b/Runtime/Flow/Utilities/Libraries/ResourceExecutableLibrary..cs
@@ -0,0 +1,42 @@
+using Ceres.Annotations;
+using Ceres.Graph.Flow.Annotations;
+using Chris.Events;
+using Chris.Resource;
+using UnityEngine;
+using UnityEngine.Scripting;
+using UObject = UnityEngine.Object;
+using R3;
+namespace Ceres.Graph.Flow.Utilities
+{
+ ///
+ /// Executable function library for Chris.Resource
+ ///
+ [Preserve]
+ [CeresGroup("Resource")]
+ public partial class ResourceExecutableLibrary: ExecutableFunctionLibrary
+ {
+ [ExecutableFunction, CeresLabel("Load Asset Synchronous")]
+ public static UObject Flow_LoadAssetSynchronous(string address)
+ {
+ return ResourceSystem.LoadAssetAsync(address).AddTo(EventSystem.Instance).WaitForCompletion();
+ }
+
+ [ExecutableFunction, CeresLabel("Load Asset Async")]
+ public static void Flow_LoadAssetAsync(string address, EventDelegate onComplete)
+ {
+ ResourceSystem.LoadAssetAsync(address, onComplete).AddTo(EventSystem.Instance);
+ }
+
+ [ExecutableFunction, CeresLabel("Instantiate Synchronous")]
+ public static GameObject Flow_InstantiateAsync(string address, Transform parent)
+ {
+ return ResourceSystem.InstantiateAsync(address, parent).AddTo(EventSystem.Instance).WaitForCompletion();
+ }
+
+ [ExecutableFunction, CeresLabel("Instantiate Async")]
+ public static void Flow_InstantiateAsync(string address, Transform parent, EventDelegate onComplete)
+ {
+ ResourceSystem.InstantiateAsync(address, parent, onComplete).AddTo(EventSystem.Instance);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Flow/Utilities/Libraries/ResourceExecutableLibrary..cs.meta b/Runtime/Flow/Utilities/Libraries/ResourceExecutableLibrary..cs.meta
new file mode 100644
index 0000000..d102699
--- /dev/null
+++ b/Runtime/Flow/Utilities/Libraries/ResourceExecutableLibrary..cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e0ac018726e64098aa3838e7f58847dd
+timeCreated: 1736667844
\ No newline at end of file
diff --git a/Runtime/Flow/Utilities/Libraries/SchedulerExecutableLibrary.cs b/Runtime/Flow/Utilities/Libraries/SchedulerExecutableLibrary.cs
new file mode 100644
index 0000000..5367281
--- /dev/null
+++ b/Runtime/Flow/Utilities/Libraries/SchedulerExecutableLibrary.cs
@@ -0,0 +1,43 @@
+using Ceres.Annotations;
+using Ceres.Graph.Flow.Annotations;
+using Chris.Schedulers;
+using UnityEngine.Scripting;
+namespace Ceres.Graph.Flow.Utilities
+{
+ ///
+ /// Executable function library for Chris.Schedulers
+ ///
+ [Preserve]
+ [CeresGroup("Scheduler")]
+ public partial class SchedulerExecutableLibrary: ExecutableFunctionLibrary
+ {
+ #region Scheduler
+
+ [ExecutableFunction, CeresLabel("Schedule Timer by Event")]
+ public static SchedulerHandle Flow_SchedulerDelay(
+ float delaySeconds, EventDelegate onComplete, EventDelegate onUpdate,
+ TickFrame tickFrame, bool isLooped, bool ignoreTimeScale)
+ {
+ var handle = Scheduler.Delay(delaySeconds,onComplete,onUpdate,
+ tickFrame, isLooped, ignoreTimeScale);
+ return handle;
+ }
+
+ [ExecutableFunction, CeresLabel("Schedule FrameCounter by Event")]
+ public static SchedulerHandle Flow_SchedulerWaitFrame(
+ int frame, EventDelegate onComplete, EventDelegate onUpdate,
+ TickFrame tickFrame, bool isLooped)
+ {
+ var handle = Scheduler.WaitFrame(frame, onComplete, onUpdate, tickFrame, isLooped);
+ return handle;
+ }
+
+ [ExecutableFunction, CeresLabel("Cancel Scheduler")]
+ public static void Flow_SchedulerCancel(SchedulerHandle handle)
+ {
+ handle.Cancel();
+ }
+
+ #endregion Scheduler
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Flow/Utilities/Libraries/SchedulerExecutableLibrary.cs.meta b/Runtime/Flow/Utilities/Libraries/SchedulerExecutableLibrary.cs.meta
new file mode 100644
index 0000000..f74b061
--- /dev/null
+++ b/Runtime/Flow/Utilities/Libraries/SchedulerExecutableLibrary.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 866d675c7a6f457eb0f3b24879a6543c
+timeCreated: 1736667765
\ No newline at end of file
diff --git a/Runtime/Flow/Utilities/Libraries/UnityExecutableLibrary.cs b/Runtime/Flow/Utilities/Libraries/UnityExecutableLibrary.cs
index 6943591..66c6a38 100644
--- a/Runtime/Flow/Utilities/Libraries/UnityExecutableLibrary.cs
+++ b/Runtime/Flow/Utilities/Libraries/UnityExecutableLibrary.cs
@@ -10,9 +10,8 @@ namespace Ceres.Graph.Flow.Utilities
/// Executable function library for Unity built-in types
///
[Preserve]
- [FormerlySerializedType("Ceres.Graph.Flow.Utilities.UnityExecutableFunctionLibrary, Ceres")]
[CeresGroup("Unity")]
- public class UnityExecutableLibrary: ExecutableFunctionLibrary
+ public partial class UnityExecutableLibrary: ExecutableFunctionLibrary
{
#region UObject
diff --git a/Runtime/SourceGenerators/Ceres.SourceGenerator.dll b/Runtime/SourceGenerators/Ceres.SourceGenerator.dll
index eab0025..b670a7a 100644
Binary files a/Runtime/SourceGenerators/Ceres.SourceGenerator.dll and b/Runtime/SourceGenerators/Ceres.SourceGenerator.dll differ
diff --git a/Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/ExecutableLibraryGeneratorContext.cs b/Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/ExecutableLibraryGeneratorContext.cs
new file mode 100644
index 0000000..376cdb7
--- /dev/null
+++ b/Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/ExecutableLibraryGeneratorContext.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+namespace Ceres.SourceGenerator.Generators
+{
+ internal class ExecutableLibraryGeneratorContext
+ {
+ private static readonly string StartTemplate =
+"""
+///
+/// This file is auto-generated by Ceres.SourceGenerator.
+/// All changes will be discarded.
+///
+{USINGNAMESPACE}
+namespace {NAMESPACE}
+{
+ [System.Runtime.CompilerServices.CompilerGenerated]
+ public partial class {CLASSNAME}
+ {
+ protected override unsafe void CollectExecutableFunctions()
+ {
+""";
+ private static readonly string EndTemplate =
+"""
+
+ }
+ }
+}
+""";
+
+
+ public string Namespace;
+
+ public string ClassName;
+
+ public List FunctionInfos;
+
+ public HashSet Namespaces;
+
+ public string GenerateCode()
+ {
+ var sb = new StringBuilder();
+ var namedCode = StartTemplate
+ .Replace("{USINGNAMESPACE}", string.Join("\n", Namespaces))
+ .Replace("{NAMESPACE}", Namespace)
+ .Replace("{CLASSNAME}", ClassName);
+ sb.Append(namedCode);
+ foreach (var function in FunctionInfos)
+ {
+ var list = new List();
+ list.AddRange(function.Parameters.Select(x => x.ParameterType));
+ list.Add(function.ReturnParameter.ParameterType);
+ string delegateStructure = string.Join(", ", list.ToArray());
+ sb.Append(
+$"""
+
+ RegisterExecutableFunctions<{ClassName}>(nameof({function.MethodName}), {function.Parameters.Count}, (delegate* <{delegateStructure}>)&{function.MethodName});
+""");
+ }
+ sb.Append(EndTemplate);
+ return sb.ToString();
+ }
+ }
+}
diff --git a/Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/ExecutableLibrarySourceGenerator.cs b/Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/ExecutableLibrarySourceGenerator.cs
new file mode 100644
index 0000000..a49a7ac
--- /dev/null
+++ b/Runtime/SourceGenerators/Source~/Ceres.SourceGenerator/Generators/ExecutableLibrarySourceGenerator.cs
@@ -0,0 +1,217 @@
+using Ceres.SourceGenerator.Generators;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection.Metadata;
+
+namespace Ceres.SourceGenerator
+{
+ [Generator]
+ public class ExecutableLibrarySourceGenerator : ISourceGenerator
+ {
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForSyntaxNotifications(() => new ExecutableLibrarySyntaxReceiver());
+ }
+
+ private static bool ShouldRunGenerator(GeneratorExecutionContext executionContext)
+ {
+ // Skip running if no references to ceres are passed to the compilation
+ return executionContext.Compilation.Assembly.Name.StartsWith("Ceres", StringComparison.Ordinal) ||
+ executionContext.Compilation.ReferencedAssemblyNames.Any(r => r.Name.Equals("Ceres", StringComparison.Ordinal));
+ }
+
+ public struct GeneratedFile
+ {
+ public string ClassName;
+
+ public string Namespace;
+
+ public string GeneratedFileName;
+
+ public string Code;
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ var receiver = context.SyntaxReceiver as ExecutableLibrarySyntaxReceiver;
+ if (receiver == null) return;
+
+ if (!ShouldRunGenerator(context))
+ return;
+
+ Helpers.SetupContext(context);
+ Debug.LogInfo($"Execute assmebly {context.Compilation.Assembly.Name}");
+
+ //If the attach_debugger key is present (but without value) the returned string is the empty string (not null)
+ var debugAssembly = context.GetOptionsString(GlobalOptions.AttachDebugger);
+ if (debugAssembly != null)
+ {
+ Debug.LaunchDebugger(context, debugAssembly);
+ }
+
+ List generatedFiles = [];
+
+ foreach (var classDeclaration in receiver.Candidates)
+ {
+ var className = classDeclaration.Identifier.Text;
+ Debug.LogInfo($"Analyze {className}");
+
+ var semanticModel = context.Compilation.GetSemanticModel(classDeclaration.SyntaxTree);
+ var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration);
+
+ if (classSymbol == null)
+ {
+ continue;
+ }
+
+ var namespaceNode = classDeclaration.Parent as NamespaceDeclarationSyntax;
+ var namespaceName = namespaceNode.Name.ToString();
+
+ ExecutableLibraryGeneratorContext generatorContext = new()
+ {
+ Namespace = namespaceName,
+ ClassName = className,
+ FunctionInfos = receiver.Methods[classDeclaration],
+ Namespaces = receiver.Namespaces[classDeclaration]
+ };
+ var generatedCode = generatorContext.GenerateCode();
+ generatedFiles.Add(new GeneratedFile
+ {
+ ClassName = className,
+ Namespace = namespaceName,
+ Code = generatedCode,
+ GeneratedFileName = $"{className}.gen.cs"
+ });
+ }
+
+ // Always delete all the previously generated files
+ if (Helpers.CanWriteFiles)
+ {
+ var outputFolder = Path.Combine(Helpers.GetOutputPath(), $"{context.Compilation.AssemblyName}");
+ if (Directory.Exists(outputFolder))
+ Directory.Delete(outputFolder, true);
+ if (generatedFiles.Count != 0)
+ Directory.CreateDirectory(outputFolder);
+ }
+
+ foreach (var nameAndSource in generatedFiles)
+ {
+ Debug.LogInfo($"Generate {nameAndSource.GeneratedFileName}");
+ var sourceText = SourceText.From(nameAndSource.Code, System.Text.Encoding.UTF8);
+ // Normalize filename for hint purpose. Special characters are not supported anymore
+ // var hintName = uniqueName.Replace('/', '_').Replace('+', '-');
+ // TODO: compute a normalized hash of that name using a common stable hash algorithm
+ var sourcePath = Path.Combine($"{context.Compilation.AssemblyName}", nameAndSource.GeneratedFileName);
+ var hintName = TypeHash.FNV1A64(sourcePath).ToString();
+ context.AddSource(hintName, sourceText.WithInitialLineDirective(sourcePath));
+ try
+ {
+ if (Helpers.CanWriteFiles)
+ File.WriteAllText(Path.Combine(Helpers.GetOutputPath(), sourcePath), nameAndSource.Code);
+ }
+ catch (Exception e)
+ {
+ // In the rare event/occasion when this happen, at the very least don't bother the user and move forward
+ Debug.LogWarning($"cannot write file {sourcePath}. An exception has been thrown:{e}");
+ }
+ }
+ }
+ }
+
+ public class ExecutableFunctionParameterInfo
+ {
+ public string ParameterType;
+
+ public string ParameterName;
+ }
+
+ public class ExecutableFunctionInfo
+ {
+ public string MethodName;
+
+ public readonly List Parameters = new();
+
+ public ExecutableFunctionParameterInfo ReturnParameter;
+ }
+
+ public class ExecutableLibrarySyntaxReceiver : ISyntaxReceiver
+ {
+ public readonly List Candidates = [];
+
+ public readonly Dictionary> Methods = new();
+
+ public readonly Dictionary> Namespaces = new();
+
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode is not ClassDeclarationSyntax classNode)
+ return;
+
+ if (classNode.BaseList == null || classNode.BaseList.Types.Count == 0)
+ return;
+
+ if (!classNode.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
+ {
+ return;
+ }
+
+ // Check inherit from ExecutableFunctionLibrary
+ if (!classNode.BaseList.Types.Any(baseType =>
+ baseType.Type is IdentifierNameSyntax identifierName &&
+ identifierName.Identifier.Text == "ExecutableFunctionLibrary"))
+ {
+ return;
+ }
+
+ Candidates.Add(classNode);
+
+ var namespaces = new HashSet();
+ var root = classNode.SyntaxTree.GetRoot();
+ var usings = root.DescendantNodes().OfType();
+ foreach (var usingDirective in usings)
+ {
+ var namespaceName = usingDirective.ToString();
+ namespaces.Add(namespaceName);
+ }
+ Namespaces[classNode] = namespaces;
+
+ var methodInfos = new List();
+ foreach (var member in classNode.Members)
+ {
+ if (member is MethodDeclarationSyntax methodNode)
+ {
+ var methodInfo = new ExecutableFunctionInfo
+ {
+ MethodName = methodNode.Identifier.Text
+ };
+
+ foreach (var parameter in methodNode.ParameterList.Parameters)
+ {
+ var parameterInfo = new ExecutableFunctionParameterInfo
+ {
+ ParameterType = parameter.Type.ToString(),
+ ParameterName = parameter.Identifier.Text
+ };
+ methodInfo.Parameters.Add(parameterInfo);
+ }
+
+ var returnParameterInfo = new ExecutableFunctionParameterInfo
+ {
+ ParameterType = methodNode.ReturnType.ToString()
+ };
+
+ methodInfo.ReturnParameter = returnParameterInfo;
+ methodInfos.Add(methodInfo);
+ }
+ }
+
+ Methods[classNode] = methodInfos;
+ }
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 12b4584..d5a3ad7 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "com.kurisu.ceres",
"displayName": "Ceres",
- "version": "0.1.3",
+ "version": "0.1.4",
"unity": "2022.3",
"description": "Powerful visual scripting toolkit for Unity.",
"keywords": [