Skip to content

Commit

Permalink
Allow value type operation function use dependency execution path
Browse files Browse the repository at this point in the history
  • Loading branch information
AkiKurisu committed Jan 8, 2025
1 parent a18b145 commit 163f9b4
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 128 deletions.
16 changes: 14 additions & 2 deletions Editor/Core/UIElements/Graph/Ports/CeresPortView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,13 @@ private CeresPortElement(Orientation portOrientation, Direction portDirection, C
protected virtual void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
evt.menu.ClearItems();
evt.menu.MenuItems().Add(new CeresDropdownMenuAction("Disconnect", a =>
evt.menu.MenuItems().Add(new CeresDropdownMenuAction("Disconnect", _ =>
{
var edge = connections.First();
edge.input.Disconnect(edge);
edge.output.Disconnect(edge);
View.NodeOwner.GraphView.DeleteElements(new []{ edge });
}, a => connections.Any() ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden));
}, _ => connections.Any() ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Hidden));
View.NodeOwner.GraphView.ContextualMenuRegistry.BuildContextualMenu(ContextualMenuType.Port, evt, portType);
}

Expand Down Expand Up @@ -176,6 +176,8 @@ public static CeresPortElement Create(CeresPortView ownerView)

public bool IsConnectable()
{
/* Can not connect when invisible */
if (style.display == DisplayStyle.None) return false;
return !connected || capacity != Capacity.Single;
}

Expand Down Expand Up @@ -579,5 +581,15 @@ public static void HidePort(this CeresPortView portView)
{
portView.PortElement.style.display = DisplayStyle.None;
}

/// <summary>
/// Whether port is visible
/// </summary>
/// <param name="portView"></param>
/// <returns></returns>
public static bool IsVisible(this CeresPortView portView)
{
return portView.PortElement.style.display == DisplayStyle.Flex;
}
}
}
2 changes: 1 addition & 1 deletion Editor/Flow/ExecutableNodeSearchWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ private void BuildExecutableFunctionEntries(CeresNodeSearchEntryBuilder builder,
builder.AddGroupEntry("Execute Functions", 1);
foreach (var method in methods)
{
var functionName = ExecutableFunctionRegistry.GetFunctionName(method, false);
var functionName = ExecutableFunction.GetFunctionName(method, false);
builder.AddEntry(new SearchTreeEntry(new GUIContent($"{functionName}", _indentationIcon))
{
level = 2,
Expand Down
30 changes: 20 additions & 10 deletions Editor/Flow/Nodes/ExecuteFunctionNodeView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public abstract class ExecuteFunctionNodeView : ExecutableNodeView

protected bool IsScriptMethod { get; private set; }

protected bool ExecuteInDependency { get; private set; }

protected bool DisplayTarget { get; private set; }

protected bool IsSelfTarget { get; private set; }
Expand All @@ -28,24 +30,30 @@ public void SetMethodInfo(MethodInfo methodInfo)
MethodInfo = methodInfo;
MethodName = methodInfo.Name;
IsStatic = methodInfo.IsStatic;
IsScriptMethod = ExecutableFunctionRegistry.IsScriptMethod(methodInfo);
DisplayTarget = ExecutableFunctionRegistry.CanDisplayTarget(methodInfo);
IsSelfTarget = ExecutableFunctionRegistry.IsSelfTarget(methodInfo);
SetNodeElementTitle(ExecutableFunctionRegistry.GetFunctionName(methodInfo));
IsScriptMethod = ExecutableFunction.IsScriptMethod(methodInfo);
ExecuteInDependency = ExecutableFunction.ExecuteInDependency(methodInfo);
DisplayTarget = ExecutableFunction.CanDisplayTarget(methodInfo);
IsSelfTarget = ExecutableFunction.IsSelfTarget(methodInfo);
SetNodeElementTitle(ExecutableFunction.GetFunctionName(methodInfo));
FillMethodParametersPorts(methodInfo);
if (ExecutableFunctionRegistry.IsNeedResolveReturnType(methodInfo))
if (ExecutableFunction.IsNeedResolveReturnType(methodInfo))
{
ResolveMethodReturnPort(methodInfo);
}
if (IsStatic)
{
NodeElement.AddToClassList("ConstNode");
}
if (ExecuteInDependency)
{
FindPortView("input").HidePort();
FindPortView("exec").HidePort();
}
}

private void ResolveMethodReturnPort(MethodInfo methodInfo)
{
var resolveParameter = ExecutableFunctionRegistry.GetResolveReturnTypeParameter(methodInfo);
var resolveParameter = ExecutableFunction.GetResolveReturnTypeParameter(methodInfo);
var portView = FindPortViewWithDisplayName(CeresLabel.GetLabel(resolveParameter.Name));
var returnPortView = FindPortView("output");
var currentType = (portView.FieldResolver.Value as SerializedTypeBase)?.GetObjectType();
Expand All @@ -62,7 +70,7 @@ private void SetNodeElementTitle(string functionTitle)
var targetType = NodeType.GetGenericArguments()[0];
if (MethodInfo != null)
{
targetType = ExecutableFunctionRegistry.GetTargetType(MethodInfo) ?? NodeType.GetGenericArguments()[0];
targetType = ExecutableFunction.GetTargetType(MethodInfo) ?? NodeType.GetGenericArguments()[0];
}
if(!IsStatic || DisplayTarget)
{
Expand Down Expand Up @@ -102,6 +110,7 @@ public sealed override void SetNodeInstance(CeresNode ceresNode)
IsStatic = functionNode.isStatic;
IsScriptMethod = functionNode.isScriptMethod;
IsSelfTarget = functionNode.isSelfTarget;
ExecuteInDependency = functionNode.executeInDependency;
SetNodeElementTitle(functionNode.methodName);
}
else
Expand All @@ -118,6 +127,7 @@ public override ExecutableNode CompileNode()
instance.isStatic = IsStatic;
instance.isScriptMethod = IsScriptMethod;
instance.isSelfTarget = IsSelfTarget;
instance.executeInDependency = ExecuteInDependency;
return instance;
}

Expand Down Expand Up @@ -167,17 +177,17 @@ protected override void FillMethodParametersPorts(MethodInfo methodInfo)
}
if (DisplayTarget)
{
FindPortView("inputs", 0).SetDisplayName("Target");
FindPortView("inputs").SetDisplayName("Target");
}
if (IsSelfTarget)
{
FindPortView("inputs", 0).SetTooltip(" [Default is Self]");
FindPortView("inputs").SetTooltip(" [Default is Self]");
}

var output = methodInfo.ReturnParameter;
if (output!.ParameterType == typeof(void)) return;

var outputPortData = new CeresPortData()
var outputPortData = new CeresPortData
{
/* Remap to actual property */
propertyName = "outputs",
Expand Down
17 changes: 13 additions & 4 deletions Runtime/Core/Models/Graph/Nodes/CeresNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,19 @@ public virtual CeresNodeData GetSerializedData()
{
/* Allows polymorphic serialization */
var data = NodeData.Clone();
data.executionPath = GetExecutionPath(GetType());
data.executionPath = GetExecutionPath();
data.Serialize(this);
return data;
}

/// <summary>
/// Get node instance execution path
/// </summary>
/// <returns></returns>
public virtual ExecutionPath GetExecutionPath()
{
return GetExecutionPath(GetType());
}

public virtual IEnumerator<CeresNode> GetEnumerator()
{
Expand All @@ -159,12 +168,12 @@ public static ExecutionPath GetExecutionPath(Type nodeType)
return ExecutionPath.Forward;
}

var path = paths[0];
if (path is "Forward" or "forward")
var path = paths[0].ToLower();
if (path == "forward")
{
return ExecutionPath.Forward;
}
if (path is "Dependency" or "dependency")
if (path == "dependency")
{
return ExecutionPath.Dependency;
}
Expand Down
21 changes: 9 additions & 12 deletions Runtime/Flow/Annotations/ExecutableFunctionAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using Chris.Serialization;
namespace Ceres.Graph.Flow.Annotations
{
/// <summary>
Expand All @@ -12,6 +11,14 @@ public sealed class ExecutableFunctionAttribute : Attribute
/// Function should use first parameter type as its script type
/// </summary>
public bool IsScriptMethod { get; set; }

/// <summary>
/// Function can be executed in dependency execution path, only support static method
/// </summary>
/// <remarks>Functions executed in dependency mode should not depend on the execution order
/// between nodes, and only execute based on the input values. For functions whose input
/// parameters contain reference types, it is more appropriate to use forward path.</remarks>
public bool ExecuteInDependency { get; set; }

/// <summary>
/// Function should display first parameter as method declare type target, need set <see cref="IsScriptMethod"/> first
Expand All @@ -21,16 +28,6 @@ public sealed class ExecutableFunctionAttribute : Attribute
/// <summary>
/// Function first parameter that should pass graph context object as default value
/// </summary>
public bool IsSelfTarget { get; set; } = false;
}


public static class ExecutableFunction
{
// ReSharper disable once InconsistentNaming
/// <summary>
/// Metadata for function parameter to resolve return type, only support <see cref="SerializedType{T}"/>
/// </summary>
public const string RESOLVE_RETURN = nameof(RESOLVE_RETURN);
public bool IsSelfTarget { get; set; }
}
}
92 changes: 88 additions & 4 deletions Runtime/Flow/Models/ExecutableReflection.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.Assertions;

using Ceres.Annotations;
using Ceres.Graph.Flow.Annotations;
using Chris.Serialization;
namespace Ceres.Graph.Flow
{
public enum ExecutableFunctionType
Expand Down Expand Up @@ -53,6 +54,89 @@ protected static void ReallocateDelegateIfNeed<TDelegate>(ref Delegate outDelega
}
}

public class ExecutableFunction
{
// ReSharper disable once InconsistentNaming
/// <summary>
/// Metadata for function parameter to resolve return type, only support <see cref="SerializedType{T}"/>
/// </summary>
public const string RESOLVE_RETURN = nameof(RESOLVE_RETURN);

public static bool IsScriptMethod(MethodInfo methodInfo)
{
if (!methodInfo.IsStatic) return false;
var parameters = methodInfo.GetParameters();
if (parameters.Length < 1) return false;

return methodInfo.GetCustomAttribute<ExecutableFunctionAttribute>().IsScriptMethod;
}

public static bool ExecuteInDependency(MethodInfo methodInfo)
{
if (!methodInfo.IsStatic) return false;
return methodInfo.GetCustomAttribute<ExecutableFunctionAttribute>().ExecuteInDependency;
}

public static bool CanDisplayTarget(MethodInfo methodInfo)
{
if (!methodInfo.IsStatic) return false;
var parameters = methodInfo.GetParameters();
if (parameters.Length < 1) return false;

var attribute = methodInfo.GetCustomAttribute<ExecutableFunctionAttribute>();
if (attribute == null) return false;
return attribute.IsScriptMethod && attribute.DisplayTarget;
}

public static bool IsNeedResolveReturnType(MethodInfo methodInfo)
{
var parameters = methodInfo.GetParameters();
if (parameters.Length < 1) return false;
if (methodInfo.ReturnType == typeof(void)) return false;

return parameters.Any(x=> CeresMetadata.IsDefined(x, ExecutableFunction.RESOLVE_RETURN));
}

public static bool IsSelfTarget(MethodInfo methodInfo)
{
if (!methodInfo.IsStatic) return false;
var parameters = methodInfo.GetParameters();
if (parameters.Length < 1) return false;

var attribute = methodInfo.GetCustomAttribute<ExecutableFunctionAttribute>();
if (attribute == null) return false;
return attribute.IsSelfTarget;
}

public static Type GetTargetType(MethodInfo methodInfo)
{
if (!methodInfo.IsStatic) return null;
var parameters = methodInfo.GetParameters();
if (parameters.Length < 1) return null;

if (methodInfo.GetCustomAttribute<ExecutableFunctionAttribute>().IsScriptMethod)
{
return parameters[0].ParameterType;
}

return null;
}

public static ParameterInfo GetResolveReturnTypeParameter(MethodInfo methodInfo)
{
var parameters = methodInfo.GetParameters();
if (parameters.Length < 1) return null;

return parameters.First(x => CeresMetadata.IsDefined(x, ExecutableFunction.RESOLVE_RETURN));
}

public static string GetFunctionName(MethodInfo methodInfo, bool richText = true)
{
var labelAttribute = methodInfo.GetCustomAttribute<CeresLabelAttribute>();
return labelAttribute != null ? labelAttribute.GetLabel(richText) : methodInfo.Name.Replace("Flow_", string.Empty);
}
}

public readonly struct ExecutableFunctionInfo: IEquatable<ExecutableFunctionInfo>
{
public readonly ExecutableFunctionType FunctionType;
Expand Down Expand Up @@ -89,7 +173,7 @@ public override string ToString()
// ReSharper disable once ClassNeverInstantiated.Global
public class ExecutableReflection<TTarget>: ExecutableReflection
{
public class ExecutableFunction
public class ExecutableFunction: Flow.ExecutableFunction
{
public readonly ExecutableFunctionInfo FunctionInfo;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public abstract class FlowNode_ExecuteFunction: FlowNode
[HideInGraphEditor]
public bool isStatic;

[HideInGraphEditor]
public bool executeInDependency;

[HideInGraphEditor]
public bool isSelfTarget;

Expand Down Expand Up @@ -72,6 +75,11 @@ protected TValue GetSelfTargetOrDefault<TValue>(CeresPort<TValue> inputPort, Exe
}
return inputPort.Value;
}

public override ExecutionPath GetExecutionPath()
{
return executeInDependency ? ExecutionPath.Dependency : ExecutionPath.Forward;
}
}

public abstract class FlowNode_ExecuteFunctionUber: FlowNode_ExecuteFunction
Expand Down
Loading

0 comments on commit 163f9b4

Please sign in to comment.