From 77160feaeb9bff7822231732cea3e20ce2bd8ead Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Tue, 29 Jul 2025 14:25:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E8=BF=9E=E6=8E=A5=E5=BC=82=E5=B8=B8=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Extension/FlowModelExtension.cs | 298 +-- Library/FlowNode/FlowCanvasDetailsInfo.cs | 2 + Library/FlowNode/FlowContext.cs | 33 - Library/FlowNode/FlowResult.cs | 3 +- .../FlowNode/LightweightFlowEnvironment.cs | 45 +- Library/FlowNode/MethodDetails.cs | 2 +- .../{LinqAsyncHelper.cs => LinqHelper.cs} | 22 +- Library/Utils/ObjectPool.cs | 3 +- NodeFlow/Env/FlowControl.cs | 1 + NodeFlow/Env/FlowEdit.cs | 1 + NodeFlow/FlowNodeExtension.cs | 1 + NodeFlow/Model/Infos/FlowApiMethodInfo.cs | 353 ++++ .../{Library => Librarys}/FlowLibraryCache.cs | 0 .../{Library => Librarys}/LibraryMdDd.cs | 0 NodeFlow/Model/Node/NodeModelBaseData.cs | 2 +- NodeFlow/Model/Node/NodeModelBaseFunc.cs | 2 +- NodeFlow/Model/Node/SingleActionNode.cs | 2 +- NodeFlow/Model/Node/SingleConditionNode.cs | 11 +- NodeFlow/Model/Node/SingleExpOpNode.cs | 2 +- NodeFlow/Model/Node/SingleFlipflopNode.cs | 3 +- NodeFlow/Model/Node/SingleFlowCallNode.cs | 2 +- NodeFlow/Model/Node/SingleGlobalDataNode.cs | 21 +- NodeFlow/Model/Node/SingleNetScriptNode.cs | 2 +- NodeFlow/Model/Node/SingleScriptNode.cs | 16 +- NodeFlow/Model/Node/SingleUINode.cs | 2 +- .../ChangeNodeConnectionOperation.cs | 70 +- NodeFlow/Services/CoreGenerateExtension.cs | 211 ++ NodeFlow/Services/FlowCoreGenerateService.cs | 1744 +++++++---------- NodeFlow/Services/FlowModelService.cs | 1 + NodeFlow/Services/FlowWorkManagement.cs | 8 +- Serein.Script/Node/AssignmentNode.cs | 8 + Serein.Script/Node/BinaryOperationNode.cs | 5 + Serein.Script/Node/ClassTypeDefinitionNode.cs | 6 +- Serein.Script/Node/CollectionIndexNode.cs | 11 + Serein.Script/Node/CtorAssignmentNode.cs | 5 +- Serein.Script/Node/FunctionCallNode.cs | 7 + Serein.Script/Node/IdentifierNode.cs | 6 + Serein.Script/Node/MemberAccessNode.cs | 5 + Serein.Script/Node/MemberAssignmentNode.cs | 5 + Serein.Script/Node/MemberFunctionCallNode.cs | 7 + Serein.Script/Node/ObjectInstantiationNode.cs | 7 + Serein.Script/Node/TypeNode.cs | 4 + Serein.Script/Node/ValueNode/BooleanNode.cs | 5 + Serein.Script/Node/ValueNode/CharNode.cs | 4 + Serein.Script/Node/ValueNode/NullNode.cs | 4 + Serein.Script/Node/ValueNode/NumberNode.cs | 5 + Serein.Script/Node/ValueNode/StringNode.cs | 5 + Serein.Script/SereinScript.cs | 3 - Serein.Script/SereinScriptMethodInfo.cs | 32 + Serein.Script/SereinScriptToCsharpScript.cs | 37 +- Serein.Script/SereinScriptTypeAnalysis.cs | 2 +- .../Node/View/ConditionNodeControl.xaml.cs | 1 + Workbench/Node/View/ExpOpNodeControl.xaml.cs | 1 + .../Node/View/FlowCallNodeControl.xaml.cs | 1 + Workbench/Node/View/GlobalDataControl.xaml.cs | 1 + .../Node/View/NetScriptNodeControl.xaml.cs | 7 +- Workbench/Node/View/ScriptNodeControl.xaml.cs | 1 + .../ViewModel/ActionNodeControlViewModel.cs | 1 + .../ConditionNodeControlViewModel.cs | 1 + .../ViewModel/ExpOpNodeControlViewModel.cs | 1 + .../ViewModel/FlipflopNodeControlViewModel.cs | 1 + .../ViewModel/FlowCallNodeControlViewModel.cs | 1 + .../GlobalDataNodeControlViewModel.cs | 1 + .../NetScriptNodeControlViewModel.cs | 5 +- .../ViewModel/ScriptNodeControlViewModel.cs | 1 + .../Node/ViewModel/UINodeControlViewModel.cs | 1 + 66 files changed, 1719 insertions(+), 1342 deletions(-) rename Library/Utils/{LinqAsyncHelper.cs => LinqHelper.cs} (70%) create mode 100644 NodeFlow/Model/Infos/FlowApiMethodInfo.cs rename NodeFlow/Model/{Library => Librarys}/FlowLibraryCache.cs (100%) rename NodeFlow/Model/{Library => Librarys}/LibraryMdDd.cs (100%) create mode 100644 NodeFlow/Services/CoreGenerateExtension.cs diff --git a/Library/Extension/FlowModelExtension.cs b/Library/Extension/FlowModelExtension.cs index 4defd34..c630517 100644 --- a/Library/Extension/FlowModelExtension.cs +++ b/Library/Extension/FlowModelExtension.cs @@ -2,6 +2,7 @@ using Serein.Library.Utils; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -196,6 +197,10 @@ namespace Serein.Library } } + private static ObjectPool> flowStackPool = new ObjectPool>(()=> new Stack()); + //private static ObjectPool> processedNodesPool = new ObjectPool>(()=> new HashSet()); + private static ObjectPool> checkpoints = new ObjectPool>(()=> new HashSet()); + /// /// 开始执行 /// @@ -203,133 +208,186 @@ namespace Serein.Library /// /// 流程运行 /// +#nullable enable public static async Task StartFlowAsync(this IFlowNode nodeModel, IFlowContext context, CancellationToken token) { - Stack stack = new Stack(); - HashSet processedNodes = new HashSet(); // 用于记录已处理上游节点的节点 - stack.Push(nodeModel); -#nullable enable + Stack flowStack = flowStackPool.Allocate() ; + //HashSet processedNodes = processedNodesPool.Allocate() ; // 用于记录已处理上游节点的节点 + + flowStack.Push(nodeModel); IFlowNode? previousNode = null; IFlowNode? currentNode = null; - while (true) + try { - if (token.IsCancellationRequested) - { - throw new Exception($"流程执行被取消,未能获取到流程结果。"); - } - - #region 执行相关 - // 从栈中弹出一个节点作为当前节点进行处理 - previousNode = currentNode; - currentNode = stack.Pop(); - - - - #region 新增调用信息 - FlowInvokeInfo? invokeInfo = null; - var isRecordInvokeInfo = context.IsRecordInvokeInfo; - if (!isRecordInvokeInfo) goto Label_NotRecordInvoke; - - FlowInvokeInfo.InvokeType invokeType = context.NextOrientation switch - { - ConnectionInvokeType.IsSucceed => FlowInvokeInfo.InvokeType.IsSucceed, - ConnectionInvokeType.IsFail => FlowInvokeInfo.InvokeType.IsFail, - ConnectionInvokeType.IsError => FlowInvokeInfo.InvokeType.IsError, - ConnectionInvokeType.Upstream => FlowInvokeInfo.InvokeType.Upstream, - _ => FlowInvokeInfo.InvokeType.None - }; - invokeInfo = context.NewInvokeInfo(previousNode, currentNode, invokeType); - #endregion - -Label_NotRecordInvoke: - context.NextOrientation = ConnectionInvokeType.None; // 重置上下文状态 - FlowResult flowResult = null; - try - { - flowResult = await currentNode.ExecutingAsync(context, token); - - if (context.NextOrientation == ConnectionInvokeType.None) // 没有手动设置时,进行自动设置 - { - context.NextOrientation = ConnectionInvokeType.IsSucceed; - } - } - catch (Exception ex) - { - flowResult = new FlowResult(currentNode.Guid, context); - context.Env.WriteLine(InfoType.ERROR, $"节点[{currentNode.Guid}]异常:" + ex); - context.NextOrientation = ConnectionInvokeType.IsError; - context.ExceptionOfRuning = ex; - } - #endregion - - #region 更新调用信息 - var state = context.NextOrientation switch - { - ConnectionInvokeType.IsFail => FlowInvokeInfo.RunState.Failed, - ConnectionInvokeType.IsError => FlowInvokeInfo.RunState.Error, - _ => FlowInvokeInfo.RunState.Succeed - }; - if (isRecordInvokeInfo) - { - invokeInfo.UploadState(state); - invokeInfo.UploadResultValue(flowResult.Value); - } - - - #endregion - - #region 执行完成时更新栈 - context.AddOrUpdateFlowData(currentNode.Guid, flowResult); // 上下文中更新数据 - - // 首先将指定类别后继分支的所有节点逆序推入栈中 - var nextNodes = currentNode.SuccessorNodes[context.NextOrientation]; - for (int index = nextNodes.Count - 1; index >= 0; index--) - { - // 筛选出启用的节点的节点 - if (nextNodes[index].DebugSetting.IsEnable) - { - //if (!ignodeState) - context.SetPreviousNode(nextNodes[index].Guid, currentNode.Guid); - stack.Push(nextNodes[index]); - } - } - - // 然后将指上游分支的所有节点逆序推入栈中 - var upstreamNodes = currentNode.SuccessorNodes[ConnectionInvokeType.Upstream]; - for (int index = upstreamNodes.Count - 1; index >= 0; index--) - { - // 筛选出启用的节点的节点 - if (upstreamNodes[index].DebugSetting.IsEnable) - { - context.SetPreviousNode(upstreamNodes[index].Guid, currentNode.Guid); - stack.Push(upstreamNodes[index]); - } - } - #endregion - - #region 执行完成后检查 - - if (stack.Count == 0) - { - return flowResult; // 说明流程到了终点 - } - - if (context.RunState == RunState.Completion) - { - currentNode.Env.WriteLine(InfoType.INFO, $"流程执行到节点[{currentNode.Guid}]时提前结束,将返回当前执行结果。"); - return flowResult; // 流程执行完成,返回结果 - } - - if (token.IsCancellationRequested) - { - throw new Exception($"流程执行到节点[{currentNode.Guid}]时被取消,未能获取到流程结果。"); - } - - - #endregion #if DEBUG - //await Task.Delay(1); + + /* + var sw = Stopwatch.StartNew(); + var checkpoints = new Dictionary(); + checkpoints["创建调用信息"] = sw.Elapsed; + var last = TimeSpan.Zero; + foreach (var kv in checkpoints) + { + SereinEnv.WriteLine(InfoType.INFO, $"{kv.Key} 耗时: {(kv.Value - last).TotalMilliseconds} ms"); + last = kv.Value; + } + */ + var sw = Stopwatch.StartNew(); + var checkpoints = new Dictionary(); #endif + while (true) + { +#if DEBUG + sw.Restart(); + var last = TimeSpan.Zero; + checkpoints.Clear(); +#endif + + #region 执行相关 + // 从栈中弹出一个节点作为当前节点进行处理 + previousNode = currentNode; + currentNode = flowStack.Pop(); + + #region 新增调用信息 + FlowInvokeInfo? invokeInfo = null; + var isRecordInvokeInfo = context.IsRecordInvokeInfo; + if (!isRecordInvokeInfo) goto Label_NotRecordInvoke; + + FlowInvokeInfo.InvokeType invokeType = context.NextOrientation switch + { + ConnectionInvokeType.IsSucceed => FlowInvokeInfo.InvokeType.IsSucceed, + ConnectionInvokeType.IsFail => FlowInvokeInfo.InvokeType.IsFail, + ConnectionInvokeType.IsError => FlowInvokeInfo.InvokeType.IsError, + ConnectionInvokeType.Upstream => FlowInvokeInfo.InvokeType.Upstream, + _ => FlowInvokeInfo.InvokeType.None + }; + invokeInfo = context.NewInvokeInfo(previousNode, currentNode, invokeType); + #endregion +#if DEBUG + checkpoints[$"[{currentNode.Guid}]\t创建调用信息"] = sw.Elapsed; +#endif + + Label_NotRecordInvoke: + context.NextOrientation = ConnectionInvokeType.IsSucceed; // 默认执行成功 + FlowResult? flowResult = null; + try + { + flowResult = await currentNode.ExecutingAsync(context, token); + } + catch (Exception ex) + { + flowResult = new FlowResult(currentNode.Guid, context); + context.Env.WriteLine(InfoType.ERROR, $"节点[{currentNode.Guid}]异常:" + ex); + context.NextOrientation = ConnectionInvokeType.IsError; + context.ExceptionOfRuning = ex; + } +#if DEBUG + finally + { + checkpoints[$"[{currentNode.Guid}]\t方法调用"] = sw.Elapsed; + } +#endif + #endregion + + #region 更新调用信息 + var state = context.NextOrientation switch + { + ConnectionInvokeType.IsFail => FlowInvokeInfo.RunState.Failed, + ConnectionInvokeType.IsError => FlowInvokeInfo.RunState.Error, + _ => FlowInvokeInfo.RunState.Succeed + }; + if (isRecordInvokeInfo) + { + invokeInfo.UploadState(state); + invokeInfo.UploadResultValue(flowResult.Value); + } + + #endregion + +#if DEBUG + checkpoints[$"[{currentNode.Guid}]\t更新调用信息"] = sw.Elapsed; +#endif + + #region 执行完成时更新栈 + context.AddOrUpdateFlowData(currentNode.Guid, flowResult); // 上下文中更新数据 +#if DEBUG + checkpoints["[{currentNode.Guid}]\t执行完成时更新栈"] = sw.Elapsed; +#endif + + // 首先将指定类别后继分支的所有节点逆序推入栈中 + var nextNodes = currentNode.SuccessorNodes[context.NextOrientation]; + for (int index = nextNodes.Count - 1; index >= 0; index--) + { + // 筛选出启用的节点的节点 + if (nextNodes[index].DebugSetting.IsEnable) + { + var node = nextNodes[index]; + context.SetPreviousNode(node.Guid, currentNode.Guid); + flowStack.Push(node); + } + } + +#if DEBUG + checkpoints[$"[{currentNode.Guid}]\t后继分支推入栈中"] = sw.Elapsed; +#endif + + // 然后将指上游分支的所有节点逆序推入栈中 + var upstreamNodes = currentNode.SuccessorNodes[ConnectionInvokeType.Upstream]; + for (int index = upstreamNodes.Count - 1; index >= 0; index--) + { + // 筛选出启用的节点的节点 + if (upstreamNodes[index].DebugSetting.IsEnable) + { + var node = upstreamNodes[index]; + context.SetPreviousNode(node.Guid, currentNode.Guid); + flowStack.Push(node); + } + } + +#if DEBUG + checkpoints[$"[{currentNode.Guid}]\t上游分支推入栈中"] = sw.Elapsed; +#endif + #endregion + + +#if DEBUG + foreach (var kv in checkpoints) + { + SereinEnv.WriteLine(InfoType.INFO, $"{kv.Key} 耗时: {(kv.Value - last).TotalMilliseconds} ms"); + last = kv.Value; + } + SereinEnv.WriteLine(InfoType.INFO, $"------"); + +#endif + + #region 执行完成后检查 + if (flowStack.Count == 0) + { + return flowResult; // 说明流程到了终点 + } + + if (context.RunState == RunState.Completion) + { + currentNode.Env.WriteLine(InfoType.INFO, $"流程执行到节点[{currentNode.Guid}]时提前结束,将返回当前执行结果。"); + return flowResult; // 流程执行完成,返回结果 + } + + if (token.IsCancellationRequested) + { + throw new Exception($"流程执行到节点[{currentNode.Guid}]时被取消,未能获取到流程结果。"); + } + + + #endregion +#if DEBUG + //await Task.Delay(1); +#endif + } + } + finally + { + flowStackPool.Free(flowStack); + //processedNodesPool.Free(processedNodes); } } diff --git a/Library/FlowNode/FlowCanvasDetailsInfo.cs b/Library/FlowNode/FlowCanvasDetailsInfo.cs index ee55778..0f51074 100644 --- a/Library/FlowNode/FlowCanvasDetailsInfo.cs +++ b/Library/FlowNode/FlowCanvasDetailsInfo.cs @@ -58,3 +58,5 @@ namespace Serein.Library public double ScaleY; } } + + diff --git a/Library/FlowNode/FlowContext.cs b/Library/FlowNode/FlowContext.cs index 51f63c4..1cd41b9 100644 --- a/Library/FlowNode/FlowContext.cs +++ b/Library/FlowNode/FlowContext.cs @@ -173,34 +173,6 @@ namespace Serein.Library dictPreviousNodes.AddOrUpdate(currentNodeModel, (_) => PreviousNode, (o, n) => PreviousNode); } - /// - /// 忽略处理该节点流程 - /// - /// - public void IgnoreFlowHandle(string node) - { - dictIgnoreNodeFlow.AddOrUpdate(node, (o) => true, (o, n) => true); - } - - /// - /// 获取此次流程处理状态 - /// - /// - /// - public bool GetIgnodeFlowStateUpload(string node) - { - return dictIgnoreNodeFlow.TryGetValue(node, out var state) ? state : false; - } - /// - /// 恢复流程处理状态 - /// - /// - /// - public void RecoverIgnodeFlowStateUpload(string node) - { - dictIgnoreNodeFlow.AddOrUpdate(node, (o) => false, (o, n) => false); - } - /// /// 获取当前节点的运行时上一节点 /// @@ -242,7 +214,6 @@ namespace Serein.Library /// 新的数据 public void AddOrUpdateFlowData(string nodeModel, FlowResult flowData) { - // this.dictNodeFlowData.TryGetValue(nodeGuid, out var oldFlowData); dictNodeFlowData.AddOrUpdate(nodeModel, _ => flowData, (o,n ) => flowData); } @@ -274,10 +245,6 @@ namespace Serein.Library throw new InvalidOperationException($"透传{nodeModel}节点数据时发生异常:上一节点不存在数据"); } - /// - /// 开始 - /// - /// /// 重置 /// diff --git a/Library/FlowNode/FlowResult.cs b/Library/FlowNode/FlowResult.cs index 84dd308..74eb432 100644 --- a/Library/FlowNode/FlowResult.cs +++ b/Library/FlowNode/FlowResult.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reactive; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -23,7 +24,7 @@ namespace Serein.Library /// /// 流程返回值的包装 /// - public class FlowResult + public sealed class FlowResult { /// /// 实例化返回值 diff --git a/Library/FlowNode/LightweightFlowEnvironment.cs b/Library/FlowNode/LightweightFlowEnvironment.cs index 495c783..ee143e1 100644 --- a/Library/FlowNode/LightweightFlowEnvironment.cs +++ b/Library/FlowNode/LightweightFlowEnvironment.cs @@ -151,14 +151,21 @@ namespace Serein.Library } } - + private enum ActionType + { + Action, + Task, + } + private ActionType actionType = ActionType.Action; public void SetAction(Action action) { this.action = action; + actionType = ActionType.Action; } public void SetAction(Func taskFunc) { this.taskFunc = taskFunc; + actionType = ActionType.Task; } @@ -177,12 +184,14 @@ namespace Serein.Library public Dictionary> SuccessorNodes { get; private set; } public CallNode[][] ChildNodes { get; private set; } = new CallNode[][] { - new CallNode[32], - new CallNode[32], - new CallNode[32], - new CallNode[32] + new CallNode[MaxChildNodeCount], + new CallNode[MaxChildNodeCount], + new CallNode[MaxChildNodeCount], + new CallNode[MaxChildNodeCount] }; + private const int MaxChildNodeCount = 16; // 每个分支最多支持16个子节点 + public int GetCount(ConnectionInvokeType type) { if (type == ConnectionInvokeType.Upstream) return UpstreamNodeCount; @@ -245,13 +254,13 @@ namespace Serein.Library { return; } - if (action is not null) + if (actionType == ActionType.Action) { - action(context); + action.Invoke(context); } - else if (taskFunc is not null) + else if (actionType == ActionType.Task) { - await taskFunc(context); + await taskFunc.Invoke(context); } else { @@ -286,11 +295,8 @@ namespace Serein.Library FlowResult flowResult = null; try { + context.NextOrientation = ConnectionInvokeType.IsSucceed; // 默认执行成功 await currentNode.InvokeAsync(context, token); - if (context.NextOrientation == ConnectionInvokeType.None) // 没有手动设置时,进行自动设置 - { - context.NextOrientation = ConnectionInvokeType.IsSucceed; - } } catch (Exception ex) { @@ -306,17 +312,18 @@ namespace Serein.Library var nextNodes = currentNode.SuccessorNodes[context.NextOrientation]; for (int index = nextNodes.Count - 1; index >= 0; index--) { - //if (!ignodeState) - context.SetPreviousNode(nextNodes[index].Guid, currentNode.Guid); - stack.Push(nextNodes[index]); + var node = nextNodes[index]; + context.SetPreviousNode(node.Guid, currentNode.Guid); + stack.Push(node); } // 然后将指上游分支的所有节点逆序推入栈中 var upstreamNodes = currentNode.SuccessorNodes[ConnectionInvokeType.Upstream]; for (int index = upstreamNodes.Count - 1; index >= 0; index--) { - context.SetPreviousNode(upstreamNodes[index].Guid, currentNode.Guid); - stack.Push(upstreamNodes[index]); + var node = upstreamNodes[index]; + context.SetPreviousNode(node.Guid, currentNode.Guid); + stack.Push(node); } #endregion @@ -334,7 +341,7 @@ namespace Serein.Library _stackPool.Return(stack); context.Env.WriteLine(InfoType.INFO, $"流程执行到节点[{currentNode.Guid}]时提前结束,将返回当前执行结果。"); - flowResult = context.GetFlowData(currentNode.Guid); _stackPool.Return(stack); + flowResult = context.GetFlowData(currentNode.Guid); return flowResult; // 流程执行完成,返回结果 } diff --git a/Library/FlowNode/MethodDetails.cs b/Library/FlowNode/MethodDetails.cs index 340f69d..4a6001f 100644 --- a/Library/FlowNode/MethodDetails.cs +++ b/Library/FlowNode/MethodDetails.cs @@ -306,7 +306,7 @@ namespace Serein.Library MethodName = this.MethodName, // 拷贝 MethodLockName = this.MethodLockName, // 拷贝 ParamsArgIndex = this.ParamsArgIndex, // 拷贝 - ParameterDetailss = this.ParameterDetailss?.Select(p => p?.CloneOfModel(nodeModel)).ToArray(), // 拷贝属于节点方法的新入参描述 + ParameterDetailss = this.ParameterDetailss?.Select(p => p?.CloneOfModel(nodeModel)).ToArray() , // 拷贝属于节点方法的新入参描述 IsAsync = this.IsAsync, // 拷贝 IsStatic = this.IsStatic, // 拷贝 }; diff --git a/Library/Utils/LinqAsyncHelper.cs b/Library/Utils/LinqHelper.cs similarity index 70% rename from Library/Utils/LinqAsyncHelper.cs rename to Library/Utils/LinqHelper.cs index 5610c8d..3ac3de9 100644 --- a/Library/Utils/LinqAsyncHelper.cs +++ b/Library/Utils/LinqHelper.cs @@ -10,8 +10,28 @@ namespace Serein.Library.Utils /// /// 对于 linq 的异步扩展方法 /// - public static class LinqAsyncHelper + public static class LinqHelper { + /// + /// 根据条件筛选,只保留第一个满足条件的元素,其余的不包含。 + /// + public static IEnumerable DistinctByCondition( + this IEnumerable source, + Func predicate) + { + var seenKeys = new HashSet(); + + foreach (var item in source) + { + if (!predicate(item)) + continue; + + /*var key = keySelector(item); + if (seenKeys.Add(key)) // 如果是新键*/ + yield return item; + } + } + public static async Task> SelectAsync(this IEnumerable source, Func> method) { diff --git a/Library/Utils/ObjectPool.cs b/Library/Utils/ObjectPool.cs index 6e61be8..526f7e1 100644 --- a/Library/Utils/ObjectPool.cs +++ b/Library/Utils/ObjectPool.cs @@ -42,7 +42,8 @@ namespace Serein.Library.Utils public ObjectPool(Factory factory) : this(factory, Environment.ProcessorCount * 2) - { } + { + } public ObjectPool(Factory factory, int size) { diff --git a/NodeFlow/Env/FlowControl.cs b/NodeFlow/Env/FlowControl.cs index 922ae99..ebe3eae 100644 --- a/NodeFlow/Env/FlowControl.cs +++ b/NodeFlow/Env/FlowControl.cs @@ -2,6 +2,7 @@ using Serein.Library.Api; using Serein.Library.Utils; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.NodeFlow.Services; using System; using System.Collections; diff --git a/NodeFlow/Env/FlowEdit.cs b/NodeFlow/Env/FlowEdit.cs index 6c57779..45da21c 100644 --- a/NodeFlow/Env/FlowEdit.cs +++ b/NodeFlow/Env/FlowEdit.cs @@ -3,6 +3,7 @@ using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.NodeFlow.Model.Operation; using Serein.NodeFlow.Services; using System.Diagnostics; diff --git a/NodeFlow/FlowNodeExtension.cs b/NodeFlow/FlowNodeExtension.cs index b742b5e..1f1cc3f 100644 --- a/NodeFlow/FlowNodeExtension.cs +++ b/NodeFlow/FlowNodeExtension.cs @@ -3,6 +3,7 @@ using Serein.Library.Api; using Serein.Library.Utils; using Serein.NodeFlow.Env; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using System.Collections.Concurrent; using System.ComponentModel; using System.Reflection; diff --git a/NodeFlow/Model/Infos/FlowApiMethodInfo.cs b/NodeFlow/Model/Infos/FlowApiMethodInfo.cs new file mode 100644 index 0000000..1fe6195 --- /dev/null +++ b/NodeFlow/Model/Infos/FlowApiMethodInfo.cs @@ -0,0 +1,353 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; +using Serein.NodeFlow.Services; +using System.Text; + +namespace Serein.NodeFlow.Model.Infos +{ + /// + /// 指示流程接口方法需要生成什么代码 + /// + internal class FlowApiMethodInfo + { + + public FlowApiMethodInfo(SingleFlowCallNode singleFlowCallNode) + { + NodeModel = singleFlowCallNode; + } + + /// + /// 对应的流程节点 + /// + public SingleFlowCallNode NodeModel { get; } + + /// + /// 生成的接口名称 + /// + public string ApiMethodName { get; set; } + + /// + /// 返回类型 + /// + public Type ReturnType { get; set; } + + /// + /// 参数实体信息 + /// + public List ParamInfos { get; set; } = []; + + /// + /// 参数实体类型名称 + /// + public string ParamTypeName => $"FlowApiInvoke_{ApiMethodName}"; + + public class ParamInfo + { + public ParamInfo(Type type, string paramName) + { + Type = type; + ParamName = paramName; + } + + /// + /// + /// + public Type Type { get; set; } + /// + /// 参数名称 + /// + public string ParamName { get; set; } + /// + /// 注释备注 + /// + public string Comments { get; set; } + } + + + public enum ParamType + { + Defute, // 仅使用方法参数 + HasToken, // 包含取消令牌和方法参数 + HasContextAndToken, // 包含上下文、取消令牌和方法参数 + } + + + public bool IsVoid => ReturnType == typeof(void); + public string ObjPoolName => $"_objPool{ParamTypeName.ToPascalCase()}"; + /// + /// 生成所需的参数类的签名代码 + /// + /// + public string ToParamterClassSignature() + { + StringBuilder sb = new StringBuilder(); + + + //sb.AppendCode(2, $"private static readonly global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}> {ObjPoolName} = "); + //sb.AppendCode(2, $" new global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}>("); + //sb.AppendCode(2, $" new global::Microsoft.Extensions.ObjectPool.DefaultPooledObjectPolicy<{ParamTypeName}>()); "); + sb.AppendLine(); + var classXmlComments = $"流程接口[{ApiMethodName}]需要的参数类".ToXmlComments(2); + sb.AppendCode(2, classXmlComments); + sb.AppendCode(2, $"public class {ParamTypeName}"); + sb.AppendCode(2, $"{{"); + for (int index = 0; index < ParamInfos.Count; index++) + { + ParamInfo? info = ParamInfos[index]; + var argXmlComments = $"[{index}]流程接口参数{(string.IsNullOrWhiteSpace(info.Comments) ? string.Empty : $",{info.Comments}。")}".ToXmlComments(2); + sb.AppendCode(3, argXmlComments); + sb.AppendCode(3, $"public global::{info.Type.FullName} {info.ParamName.ToPascalCase()} {{ get; set; }}"); + } + sb.AppendCode(2, $"}}"); + return sb.ToString(); + } + + public string ToObjPoolSignature() + { + StringBuilder sb = new StringBuilder(); + sb.AppendCode(2, $"private static readonly global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}> {ObjPoolName} = "); + sb.AppendCode(4, $"new global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}>("); + sb.AppendCode(5, $"new global::Microsoft.Extensions.ObjectPool.DefaultPooledObjectPolicy<{ParamTypeName}>()); "); + return sb.ToString(); + } + + /// + /// 生成接口的签名方法 + /// + /// + /// + /// + public string ToInterfaceMethodSignature(ParamType type) + { + var taskTypeFullName = $"global::System.Threading.Tasks.Task"; + var contextFullName = $"global::{typeof(IFlowContext).FullName}"; + var tokenFullName = $"global::{typeof(CancellationToken).FullName}"; + var returnContext = IsVoid ? taskTypeFullName : $"{taskTypeFullName}<{ReturnType.FullName}>"; + if (type == ParamType.Defute) + { + var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); + return $"{returnContext} {ApiMethodName}({paramSignature});"; + } + else if (type == ParamType.HasToken) + { + var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); + return $"{returnContext} {ApiMethodName}({tokenFullName} token, {paramSignature});"; + } + else if (type == ParamType.HasContextAndToken) + { + var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); + return $"{returnContext} {ApiMethodName}({contextFullName} flowContext, {tokenFullName} token, {paramSignature});"; + } + throw new Exception(); + } + + /// + /// 生成实现方法的签名代码 + /// + /// + /// + /// + public string ToImpleMethodSignature(ParamType type) + { + var taskTypeFullName = $"global::System.Threading.Tasks.Task"; + var contextApiFullName = $"global::{typeof(IFlowContext).FullName}"; + var contextImpleFullName = $"global::{typeof(FlowContext).FullName}"; + var tokenSourceFullName = $"global::{typeof(CancellationTokenSource).FullName}"; + var tokenFullName = $"global::{typeof(CancellationToken).FullName}"; + var flowContextPoolName = $"global::{typeof(LightweightFlowControl).FullName}"; + string flowEnvironment = nameof(flowEnvironment); + string flowContext = nameof(flowContext); + string token = nameof(token); + + var returnTypeContext = IsVoid ? taskTypeFullName : $"{taskTypeFullName}<{ReturnType.FullName}>"; + + StringBuilder sb = new StringBuilder(); + if (IsVoid) + { + if (type == ParamType.Defute) + { + var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{{{p.Type.FullName} {p.ParamName}")); + var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); + sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({paramSignature})"); + sb.AppendCode(2, $"{{"); + sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文"); + sb.AppendCode(3, $"{tokenSourceFullName} cts = new {tokenSourceFullName}(); // 创建取消令牌"); + sb.AppendCode(3, $"try"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"await {ApiMethodName}({flowContext}, cts.Token, {invokeParamSignature}); // 调用目标方法"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"catch (Exception)"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"throw;"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"finally"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); "); + sb.AppendCode(4, $"cts.{nameof(CancellationTokenSource.Dispose)}(); "); + sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(2, $"}}"); + return sb.ToString(); + } + else if (type == ParamType.HasToken) + { + var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{{{p.Type.FullName} {p.ParamName}")); + var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); + sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({tokenFullName} {token}, {paramSignature})"); + sb.AppendCode(2, $"{{"); + sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文"); + sb.AppendCode(3, $"try"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"await {ApiMethodName}({flowContext}, {token}, {invokeParamSignature}); // 调用目标方法"); + sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); "); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"catch (Exception)"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"throw;"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"finally"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(2, $"}}"); + return sb.ToString(); + } + else if (type == ParamType.HasContextAndToken) + { + var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{{{p.Type.FullName} {p.ParamName}")); + var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); + + + sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({contextApiFullName} {flowContext}, {tokenFullName} token, {paramSignature})"); + sb.AppendCode(2, $"{{"); + sb.AppendCode(3, $"token.ThrowIfCancellationRequested(); // 检查任务是否取消"); + sb.AppendCode(3, $"try"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(3, $"global::{ParamTypeName} data = {ObjPoolName}.Get(); // 从对象池获取一个对象"); + for (int index = 0; index < ParamInfos.Count; index++) + { + ParamInfo? info = ParamInfos[index]; + sb.AppendCode(4, $"data.{info.ParamName.ToPascalCase()} = {info.ParamName}; // [{index}] {info.Comments}"); + } + sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{ApiMethodName}\", data);"); + sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.SetPreviousNode)}(\"{NodeModel.TargetNode.Guid}\", \"{ApiMethodName}\");"); + sb.AppendCode(3, $"global::{typeof(CallNode).FullName} node = Get(\"{NodeModel.Guid}\");"); + sb.AppendCode(3, $"await node.{nameof(CallNode.StartFlowAsync)}({flowContext}, {token}); // 调用目标方法"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"catch (Exception)"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"throw;"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"finally"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(2, $"}}"); + return sb.ToString(); + } + } + else + { + string flowResult = nameof(flowResult); + if (type == ParamType.Defute) + { + //sb.AppendCode(3, $"{contextApiFullName} {flowContext} = new {contextImpleFullName}({flowEnvironment}); // 创建上下文"); + + var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); + var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); + sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({paramSignature})"); + sb.AppendCode(2, $"{{"); + sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文"); + sb.AppendCode(3, $"{tokenSourceFullName} cts = new {tokenSourceFullName}(); // 创建取消令牌"); + sb.AppendCode(3, $"try"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"{ReturnType.FullName} {flowResult} = await {ApiMethodName}({flowContext}, cts.{nameof(CancellationTokenSource.Token)}, {invokeParamSignature}); // 调用目标方法"); + sb.AppendCode(4, $"return {flowResult};"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"catch (Exception)"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"throw;"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"finally"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); "); + sb.AppendCode(4, $"cts.{nameof(CancellationTokenSource.Dispose)}(); "); + sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(2, $"}}"); + return sb.ToString(); + } + else if (type == ParamType.HasToken) + { + var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); + var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); + sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({tokenFullName} {token}, {paramSignature})"); + sb.AppendCode(2, $"{{"); + sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文"); + sb.AppendCode(3, $"try"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"{ReturnType.FullName} {flowResult} = await {ApiMethodName}({flowContext}, {token}, {invokeParamSignature}); // 调用目标方法"); + sb.AppendCode(4, $"return {flowResult};"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"catch (Exception)"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"throw;"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"finally"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); "); + sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(2, $"}}"); + return sb.ToString(); + } + else if (type == ParamType.HasContextAndToken) + { + var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); + var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); + sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({contextApiFullName} {flowContext}, {tokenFullName} token, {paramSignature})"); + sb.AppendCode(2, $"{{"); + sb.AppendCode(3, $"token.ThrowIfCancellationRequested(); // 检查任务是否取消"); + sb.AppendCode(3, $"global::{ParamTypeName} data = {ObjPoolName}.Get(); // 从对象池获取一个对象"); + for (int index = 0; index < ParamInfos.Count; index++) + { + ParamInfo? info = ParamInfos[index]; + sb.AppendCode(4, $"data.{info.ParamName.ToPascalCase()} = {info.ParamName}; // [{index}] {info.Comments}"); // 进行赋值 + } + sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{ApiMethodName}\", data);"); + sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.SetPreviousNode)}(\"{NodeModel.Guid}\", \"{ApiMethodName}\");"); + sb.AppendCode(3, $"global::{typeof(CallNode).FullName} node = Get(\"{NodeModel.Guid}\");"); + sb.AppendCode(3, $"global::{typeof(FlowResult).FullName} {flowResult} = await node.{nameof(CallNode.StartFlowAsync)}({flowContext}, {token}); // 调用目标方法"); + if(ReturnType == typeof(object)) + { + sb.AppendCode(3, $"if ({flowResult}.{nameof(FlowResult.Value)} is null)"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"return null;"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"else", isWrapping :false); + } + sb.AppendCode(3, $"if ({flowResult}.{nameof(FlowResult.Value)} is global::{ReturnType.FullName} result)"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"return result;"); + sb.AppendCode(3, $"}}"); + sb.AppendCode(3, $"else"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"throw new ArgumentNullException($\"类型转换失败,{{(flowResult.Value is null ? \"返回数据为 null\" : $\"返回数据与需求类型不匹配,当前返回类型为[{{flowResult.Value.GetType().FullName}}。\")}}\");"); + sb.AppendCode(3, $"}}"); + //sb.AppendCode(3, $"return {flowResult};"); + sb.AppendCode(2, $"}}"); + return sb.ToString(); + // throw new ArgumentNullException($"类型转换失败,{(flowResult.Value is null ? "返回数据为 null" : $"返回数据与需求类型不匹配,当前返回类型为[{flowResult.Value.GetType().FullName}。")}"); + } + } + + throw new Exception(); + + } + } + + + +} diff --git a/NodeFlow/Model/Library/FlowLibraryCache.cs b/NodeFlow/Model/Librarys/FlowLibraryCache.cs similarity index 100% rename from NodeFlow/Model/Library/FlowLibraryCache.cs rename to NodeFlow/Model/Librarys/FlowLibraryCache.cs diff --git a/NodeFlow/Model/Library/LibraryMdDd.cs b/NodeFlow/Model/Librarys/LibraryMdDd.cs similarity index 100% rename from NodeFlow/Model/Library/LibraryMdDd.cs rename to NodeFlow/Model/Librarys/LibraryMdDd.cs diff --git a/NodeFlow/Model/Node/NodeModelBaseData.cs b/NodeFlow/Model/Node/NodeModelBaseData.cs index ffe90e2..f904ce2 100644 --- a/NodeFlow/Model/Node/NodeModelBaseData.cs +++ b/NodeFlow/Model/Node/NodeModelBaseData.cs @@ -7,7 +7,7 @@ using System.ComponentModel; using System.Net.Mime; using System.Threading; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { diff --git a/NodeFlow/Model/Node/NodeModelBaseFunc.cs b/NodeFlow/Model/Node/NodeModelBaseFunc.cs index 9a4ace4..9a997bc 100644 --- a/NodeFlow/Model/Node/NodeModelBaseFunc.cs +++ b/NodeFlow/Model/Node/NodeModelBaseFunc.cs @@ -1,7 +1,7 @@ using Serein.Library; using Serein.Library.Api; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { diff --git a/NodeFlow/Model/Node/SingleActionNode.cs b/NodeFlow/Model/Node/SingleActionNode.cs index 496b1fb..1720229 100644 --- a/NodeFlow/Model/Node/SingleActionNode.cs +++ b/NodeFlow/Model/Node/SingleActionNode.cs @@ -2,7 +2,7 @@ using Serein.Library; using System.Security.AccessControl; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { /// /// 单动作节点(用于动作控件) diff --git a/NodeFlow/Model/Node/SingleConditionNode.cs b/NodeFlow/Model/Node/SingleConditionNode.cs index f8455c4..f314449 100644 --- a/NodeFlow/Model/Node/SingleConditionNode.cs +++ b/NodeFlow/Model/Node/SingleConditionNode.cs @@ -4,7 +4,7 @@ using Serein.Script; using System.Dynamic; using System.Linq.Expressions; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { /// /// 条件节点(用于条件控件) @@ -177,7 +177,7 @@ namespace Serein.NodeFlow.Model context.ExceptionOfRuning = ex; } - SereinEnv.WriteLine(InfoType.INFO, $"{result} {Expression} -> " + context.NextOrientation); + SereinEnv.WriteLine(InfoType.INFO, $"{Expression} -> " + context.NextOrientation); //return result; return new FlowResult(this.Guid, context, judgmentResult); } @@ -236,19 +236,18 @@ namespace Serein.NodeFlow.Model var dataName = nameof(data); if (!expression.Equals(conditionExpression)) { - conditionExpression = expression; + conditionExpression = expression.Trim(); conditionScript = new SereinScript(); var dataType = data is null ? typeof(object) : data.GetType(); - conditionExpression = expression.Trim(); if (expression[0] == '.') { // 对象取值 - conditionExpression = $"return {dataName}{expression};"; + conditionExpression = $"return {dataName}{conditionExpression};"; } else { // 直接表达式 - conditionExpression = $"return {dataName}.{expression};"; + conditionExpression = $"return {dataName}.{conditionExpression};"; } var resultType = conditionScript.ParserScript(conditionExpression, new Dictionary { diff --git a/NodeFlow/Model/Node/SingleExpOpNode.cs b/NodeFlow/Model/Node/SingleExpOpNode.cs index 76507d7..857b3fc 100644 --- a/NodeFlow/Model/Node/SingleExpOpNode.cs +++ b/NodeFlow/Model/Node/SingleExpOpNode.cs @@ -3,7 +3,7 @@ using Serein.Library.Api; using Serein.Script; using System.Dynamic; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { /// /// Expression Operation - 表达式操作 diff --git a/NodeFlow/Model/Node/SingleFlipflopNode.cs b/NodeFlow/Model/Node/SingleFlipflopNode.cs index a95362d..506905d 100644 --- a/NodeFlow/Model/Node/SingleFlipflopNode.cs +++ b/NodeFlow/Model/Node/SingleFlipflopNode.cs @@ -3,13 +3,14 @@ using Serein.Library; using Serein.Library.Utils; using System; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { /// /// 触发器节点 /// public class SingleFlipflopNode : NodeModelBase { + public SingleFlipflopNode(IFlowEnvironment environment) : base(environment) { diff --git a/NodeFlow/Model/Node/SingleFlowCallNode.cs b/NodeFlow/Model/Node/SingleFlowCallNode.cs index a90f060..e7dc7e8 100644 --- a/NodeFlow/Model/Node/SingleFlowCallNode.cs +++ b/NodeFlow/Model/Node/SingleFlowCallNode.cs @@ -4,7 +4,7 @@ using Serein.NodeFlow.Services; using System.Dynamic; using System.Reflection; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { [NodeProperty(ValuePath = NodeValuePath.Node)] diff --git a/NodeFlow/Model/Node/SingleGlobalDataNode.cs b/NodeFlow/Model/Node/SingleGlobalDataNode.cs index 174e81a..698a07a 100644 --- a/NodeFlow/Model/Node/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/Node/SingleGlobalDataNode.cs @@ -2,7 +2,7 @@ using Serein.Library.Api; using System.Dynamic; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { @@ -13,7 +13,7 @@ namespace Serein.NodeFlow.Model public partial class SingleGlobalDataNode : NodeModelBase { /// - /// 表达式 + /// 全局数据的Key名称 /// [PropertyInfo(IsNotification = true)] private string _keyName; @@ -43,7 +43,7 @@ namespace Serein.NodeFlow.Model /// /// 数据来源的节点 /// - private IFlowNode? DataNode; + public IFlowNode? DataNode { get; private set; } = null; /// /// 有节点被放置 @@ -52,12 +52,27 @@ namespace Serein.NodeFlow.Model /// public bool PlaceNode(IFlowNode nodeModel) { + if(nodeModel.ControlType is not (NodeControlType.Action or NodeControlType.Script)) + { + SereinEnv.WriteLine(InfoType.INFO, "放置在全局数据必须是有返回值的[Action]节点,[Script]节点。"); + return false; + } + if (nodeModel.MethodDetails?.ReturnType is null + || nodeModel.MethodDetails.ReturnType == typeof(void)) + { + SereinEnv.WriteLine(InfoType.INFO, "放置在全局数据必须是有返回值的[Action]节点,[Script]节点。"); + return false; + } + if(DataNode is null) { // 放置节点 nodeModel.ContainerNode = this; ChildrenNode.Add(nodeModel); DataNode = nodeModel; + + MethodDetails.IsAsync = nodeModel.MethodDetails.IsAsync; + MethodDetails.ReturnType = nodeModel.MethodDetails.ReturnType; return true; } else if (DataNode.Guid != nodeModel.Guid) diff --git a/NodeFlow/Model/Node/SingleNetScriptNode.cs b/NodeFlow/Model/Node/SingleNetScriptNode.cs index ad99eac..011cb17 100644 --- a/NodeFlow/Model/Node/SingleNetScriptNode.cs +++ b/NodeFlow/Model/Node/SingleNetScriptNode.cs @@ -8,7 +8,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { [NodeProperty(ValuePath = NodeValuePath.Node)] diff --git a/NodeFlow/Model/Node/SingleScriptNode.cs b/NodeFlow/Model/Node/SingleScriptNode.cs index be54bbc..62915c5 100644 --- a/NodeFlow/Model/Node/SingleScriptNode.cs +++ b/NodeFlow/Model/Node/SingleScriptNode.cs @@ -13,7 +13,7 @@ using System.Text; using System.Threading.Tasks; using System.Xml.Linq; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { [NodeProperty(ValuePath = NodeValuePath.Node)] @@ -263,20 +263,6 @@ namespace Serein.NodeFlow.Model { if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); var @params = await flowCallNode.GetParametersAsync(context, token); - //if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); - //context.AddOrUpdate($"{context.Guid}_{this.Guid}_Params", @params[0]); // 后面再改 - - /* if (IsScriptChanged) - { - lock (@params) { - if (IsScriptChanged) - { - ReloadScript();// 执行时检查是否需要重新解析 - IsScriptChanged = false; - context.Env.WriteLine(InfoType.INFO, $"[{Guid}]脚本解析完成"); - } - } - }*/ IScriptInvokeContext scriptContext = new ScriptInvokeContext(context); diff --git a/NodeFlow/Model/Node/SingleUINode.cs b/NodeFlow/Model/Node/SingleUINode.cs index 8f258bb..ce6dc83 100644 --- a/NodeFlow/Model/Node/SingleUINode.cs +++ b/NodeFlow/Model/Node/SingleUINode.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Nodes { public class SingleUINode : NodeModelBase { diff --git a/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs b/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs index 930fed6..8f07db9 100644 --- a/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs +++ b/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs @@ -172,40 +172,58 @@ namespace Serein.NodeFlow.Model.Operation #region 类型检查 bool checkTypeState = true; List toPds = new List(); - if(ToNode.MethodDetails.ParameterDetailss is null) + + if (FromNode.ContainerNode is not null) { - SereinEnv.WriteLine(InfoType.WARN, "目标节点没有入参参数,无法进行连接"); + SereinEnv.WriteLine(InfoType.WARN, "连接失败,起始节点处于容器中"); return false; } - if (ToNode.MethodDetails.ParameterDetailss.Length > 0) + + if (ToNode.ContainerNode is not null) { - var fromNoeReturnType = fromNode.MethodDetails.ReturnType; - if (fromNoeReturnType != null - && fromNoeReturnType != typeof(object) - && fromNoeReturnType != typeof(void) - && fromNoeReturnType != typeof(Unit)) + SereinEnv.WriteLine(InfoType.WARN, "连接失败,目标节点处于容器中"); + return false; + } + + if (ToNode.ControlType != NodeControlType.GlobalData) + { + + if (ToNode.MethodDetails.ParameterDetailss is null) { - var toNodePds = toNode.MethodDetails.ParameterDetailss; - foreach (ParameterDetails toNodePd in toNodePds) + SereinEnv.WriteLine(InfoType.WARN, "连接失败,目标节点没有入参参数。"); + return false; + } + if (ToNode.MethodDetails.ParameterDetailss.Length > 0) + { + var fromNoeReturnType = fromNode.MethodDetails.ReturnType; + if (fromNoeReturnType != null + && fromNoeReturnType != typeof(object) + && fromNoeReturnType != typeof(void) + && fromNoeReturnType != typeof(Unit)) { - if (string.IsNullOrWhiteSpace(toNodePd.ArgDataSourceNodeGuid) // 入参没有设置数据来源节点 - && toNodePd.DataType.IsAssignableFrom(fromNoeReturnType)) // 返回值与目标入参相同(或可转换为目标入参) + var toNodePds = toNode.MethodDetails.ParameterDetailss; + foreach (ParameterDetails toNodePd in toNodePds) { - - toPds.Add(toNodePd); + if (string.IsNullOrWhiteSpace(toNodePd.ArgDataSourceNodeGuid) // 入参没有设置数据来源节点 + && toNodePd.DataType.IsAssignableFrom(fromNoeReturnType)) // 返回值与目标入参相同(或可转换为目标入参) + { + + toPds.Add(toNodePd); + } + } + if (toPds.Count == 0) + { + var any = toNodePds.Any(pd => pd.ArgDataSourceNodeGuid == fromNode.Guid); // 判断目标节点是否已有该节点的连接 + checkTypeState = any; + } + else + { + checkTypeState = true; // 类型检查初步通过 } - } - if (toPds.Count == 0) - { - var any = toNodePds.Any(pd => pd.ArgDataSourceNodeGuid == fromNode.Guid); // 判断目标节点是否已有该节点的连接 - checkTypeState = any; - } - else - { - checkTypeState = true; // 类型检查初步通过 } } } + if (!checkTypeState) // 类型检查不通过 { SereinEnv.WriteLine(InfoType.ERROR, "创建失败,目标节点没有合适的入参接收返回值"); @@ -396,7 +414,11 @@ namespace Serein.NodeFlow.Model.Operation SereinEnv.WriteLine(InfoType.WARN, $"连接失败,节点参数入参不允许接收多个节点返回值。起始节点[{FromNode.Guid}],目标节点[{FromNode.Guid}]。"); return false; } - + if (FromNode.ContainerNode is not null) + { + SereinEnv.WriteLine(InfoType.WARN, "连接失败,参数来源节点处于容器中"); + return false; + } // 判断是否建立过连接关系 if (FromNode.Guid == toNodeArgSourceGuid && toNodeArgSourceType == ConnectionArgSourceType) diff --git a/NodeFlow/Services/CoreGenerateExtension.cs b/NodeFlow/Services/CoreGenerateExtension.cs new file mode 100644 index 0000000..0190a21 --- /dev/null +++ b/NodeFlow/Services/CoreGenerateExtension.cs @@ -0,0 +1,211 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Infos; +using Serein.NodeFlow.Model.Nodes; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Serein.NodeFlow.Services +{ + internal static class CoreGenerateExtension + { + /// + /// 生成流程接口信息描述 + /// + /// + /// + public static FlowApiMethodInfo? ToFlowApiMethodInfo(this SingleFlowCallNode flowCallNode) + { + var targetNode = flowCallNode.TargetNode; + if (targetNode.ControlType is not (NodeControlType.Action or NodeControlType.Script)) return null; + if (flowCallNode.MethodDetails is null) return null; + if (string.IsNullOrWhiteSpace(flowCallNode.ApiGlobalName)) return null; + + + FlowApiMethodInfo flowApiMethodInfo = new FlowApiMethodInfo(flowCallNode); + flowApiMethodInfo.ReturnType = targetNode.ControlType == NodeControlType.Script ? typeof(object) + : flowCallNode.MethodDetails.ReturnType; + + flowApiMethodInfo.ApiMethodName = flowCallNode.ApiGlobalName; + + List list = []; + + int index = 0; + foreach (var pd in flowCallNode.MethodDetails.ParameterDetailss) + { + if (pd.DataType is null || string.IsNullOrWhiteSpace(pd.Name)) + { + return null; + } + if (pd.IsParams) + { + list.Add(new FlowApiMethodInfo.ParamInfo(pd.DataType, $"{pd.Name}{index++}")); + } + else + { + list.Add(new FlowApiMethodInfo.ParamInfo(pd.DataType, pd.Name)); + } + } + + flowApiMethodInfo.ParamInfos = list; + return flowApiMethodInfo; + } + + /// + /// 生成方法名称 + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToNodeMethodName(this IFlowNode flowNode) + { + /*if (!flowLibraryService.TryGetMethodInfo(flowNode.MethodDetails.AssemblyName, + flowNode.MethodDetails.MethodName, + out var methodInfo)) + { + throw new Exception(); + }*/ + var guid = flowNode.Guid; + var tmp = guid.Replace("-", ""); + var methodName = $"FlowMethod_{tmp}"; + return methodName; + } + + + /// + /// 生成完全的xml注释 + /// + /// + /// + public static string ToXmlComments(this string context, int retractCount = 0) + { + StringBuilder sb = new StringBuilder(); + var startLine = "/// "; + var endLine = "/// "; + sb.AppendLine(startLine); + var rows = context.Split(Environment.NewLine); + string retract = new string(' ', retractCount * 4); + foreach (var row in rows) + { + // 处理转义 + var value = row.Replace("<", "<") + .Replace(">", ">"); + sb.AppendLine($"{retract}/// {value}"); + } + sb.AppendLine(endLine); + return sb.ToString(); + } + + /// + /// 生成类型的驼峰命名法名称(首字母小写) + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToCamelCase(this Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + // 获取类型名称(不包括命名空间) + string typeName = type.Name; + + if (string.IsNullOrEmpty(typeName)) + { + return string.Empty; + } + + // 处理泛型类型(去掉后面的`N) + int indexOfBacktick = typeName.IndexOf('`'); + if (indexOfBacktick > 0) + { + typeName = typeName.Substring(0, indexOfBacktick); + } + + // 如果是接口且以"I"开头,去掉第一个字母 + if (type.IsInterface && typeName.Length > 1 && typeName[0] == 'I' && char.IsUpper(typeName[1])) + { + typeName = typeName.Substring(1); + } + + // 转换为驼峰命名法:首字母小写,其余不变 + if (typeName.Length > 0) + { + return char.ToLowerInvariant(typeName[0]) + typeName.Substring(1); + } + + return typeName; + } + + /// + /// 生成类型的大驼峰命名法名称(PascalCase) + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToPascalCase(this Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + string typeName = type.Name; + + if (string.IsNullOrEmpty(typeName)) + { + return string.Empty; + } + + // 去掉泛型标记(如 `1) + int indexOfBacktick = typeName.IndexOf('`'); + if (indexOfBacktick > 0) + { + typeName = typeName.Substring(0, indexOfBacktick); + } + + // 如果是接口以 I 开头,且后面是大写,去掉前缀 I + if (type.IsInterface && typeName.Length > 1 && typeName[0] == 'I' && char.IsUpper(typeName[1])) + { + typeName = typeName.Substring(1); + } + + // 首字母转为大写(如果有需要) + if (typeName.Length > 0) + { + return char.ToUpperInvariant(typeName[0]) + typeName.Substring(1); + } + + return typeName; + } + + /// + /// 将字符串首字母大写(PascalCase) + /// + /// 原始文本 + /// 首字母大写的文本 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToPascalCase(this string text) + { + if (string.IsNullOrEmpty(text)) + { + return string.Empty; + } + + if (char.IsUpper(text[0])) + { + return text; // 已是大写 + } + + return char.ToUpperInvariant(text[0]) + text.Substring(1); + } + } + + + +} diff --git a/NodeFlow/Services/FlowCoreGenerateService.cs b/NodeFlow/Services/FlowCoreGenerateService.cs index fe337be..7c91ea5 100644 --- a/NodeFlow/Services/FlowCoreGenerateService.cs +++ b/NodeFlow/Services/FlowCoreGenerateService.cs @@ -2,9 +2,12 @@ using Serein.Library.Api; using Serein.Library.Utils; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Infos; +using Serein.NodeFlow.Model.Nodes; using Serein.Script; using System.Reflection; using System.Runtime.CompilerServices; +using System.Security.Cryptography; using System.Text; namespace Serein.NodeFlow.Services @@ -23,7 +26,10 @@ namespace Serein.NodeFlow.Services this.flowLibraryService = flowLibraryService; } - + /// + /// 生成C#代码文件内容,包含流程调用树和节点方法 + /// + /// public string ToCsharpCoreFile() { StringBuilder stringBuilder = new StringBuilder(); @@ -42,11 +48,18 @@ namespace Serein.NodeFlow.Services } } + + var scriptNodes = flowModelService.GetAllNodeModel().Where(n => n.ControlType == NodeControlType.Script).OfType().ToArray(); GenerateScript_InitSereinScriptMethodInfos(scriptNodes); // 初始化脚本方法 - var flowCallNode = flowModelService.GetAllNodeModel().Where(n => n.ControlType == NodeControlType.FlowCall).OfType().ToArray(); - GenerateFlowApi_InitFlowApiMethodInfos(flowCallNode); // 初始化流程接口信息 + var flowCallNodes = flowModelService.GetAllNodeModel().Where(n => n.ControlType == NodeControlType.FlowCall).OfType().ToArray(); + GenerateFlowApi_InitFlowApiMethodInfos(flowCallNodes); // 初始化流程接口信息 + + var globalDataNodes = flowModelService.GetAllNodeModel().Where(n => n.ControlType == NodeControlType.GlobalData).OfType().ToArray(); + GenerateGlobalData_InitSereinGlobalDataInfos(globalDataNodes); // 初始化全局数据信息 + + GenerateFlowApi_InterfaceAndImpleClass(stringBuilder); // 生成接口类 GenerateFlowApi_ApiParamClass(stringBuilder); // 生成接口参数类 string flowTemplateClassName = $"FlowTemplate"; // 类名 @@ -76,13 +89,17 @@ namespace Serein.NodeFlow.Services stringBuilder.AppendCode(0, $"}}"); - // 载入脚本节点转换的C#代码 + + // 载入脚本节点转换的C#代码(载入类) var scriptInfos = scriptMethodInfos.Values.ToArray(); foreach (var info in scriptInfos) { stringBuilder.AppendCode(2, info.CsharpCode); } + // 载入全局数据节点转换的C#代码(载入类) + GenerateGlobalData_ToClass(stringBuilder); + return stringBuilder.ToString(); } @@ -172,561 +189,6 @@ namespace Serein.NodeFlow.Services throw new Exception("无法为该节点生成调用逻辑"); } - /// - /// 生成[Action]节点的方法调用 - /// - /// - /// - /// - /// - /// - private void CreateMethodCore_Action(StringBuilder sb_main, SingleActionNode actionNode, string? flowContextTypeName, string flowContext) - { - if (!flowLibraryService.TryGetMethodInfo(actionNode.MethodDetails.AssemblyName, - actionNode.MethodDetails.MethodName, - out var methodInfo) || methodInfo is null) - { - return; - } - - var isRootNode = actionNode.IsRoot(); - - var instanceType = actionNode.MethodDetails.ActingInstanceType; - var returnType = methodInfo.ReturnType; - - var instanceName = instanceType.ToCamelCase();// $"instance_{instanceType.Name}"; - - var instanceTypeFullName = instanceType.FullName; - var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; - - #region 方法内部逻辑 - StringBuilder sb_invoke_login = new StringBuilder(); - if (actionNode.MethodDetails is null) return; - var param = methodInfo.GetParameters(); - var md = actionNode.MethodDetails; - var pds = actionNode.MethodDetails.ParameterDetailss; - if (param is null) return; - if (pds is null) return; - - bool isGetPreviousNode = false; - for (int index = 0; index < pds.Length; index++) - { - ParameterDetails? pd = pds[index]; - ParameterInfo parameterInfo = param[index]; - var paramtTypeFullName = parameterInfo.ParameterType.FullName; - - if (pd.IsExplicitData) - { - // 只能是 数值、 文本、枚举, 才能作为显式参数 - if (parameterInfo.ParameterType.IsValueType) - { - if (parameterInfo.ParameterType.IsEnum) - { - sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = global::{paramtTypeFullName}.{pd.DataValue}; // 获取当前节点的上一节点数据"); - } - else - { - var value = pd.DataValue.ToConvertValueType(parameterInfo.ParameterType); - sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据"); - - } - } - else if (parameterInfo.ParameterType == typeof(string)) - { - sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = \"{pd.DataValue}\"; // 获取当前节点的上一节点数据"); - } - else - { - // 处理表达式 - } - - } - else - { - #region 非显式设置的参数以正常方式获取 - if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) - { - var previousNode = $"previousNode{index}"; - var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; - sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{actionNode.Guid}\");"); // 获取运行时上一节点Guid - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); - } - else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) - { - if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) - { - var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; - var otherNodeReturnType = otherNode.MethodDetails.ReturnType; - if (otherNodeReturnType == typeof(object)) - { - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); - } - else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) - { - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); - } - else - { - // 获取的数据无法转换为目标方法入参类型 - throw new Exception("获取的数据无法转换为目标方法入参类型"); - } - } - else - { - // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 - throw new Exception("指定了Guid,但项目中不存在对应的节点"); - } - } - else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) - { - if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) // 获取指定节点 - { - var otherNodeReturnType = otherNode.MethodDetails.ReturnType; - var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{otherNode.MethodDetails.ReturnType.FullName}"; - if (otherNodeReturnType == typeof(object)) - { - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); - } - else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) - { - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {otherNode.ToNodeMethodName()}({flowContext}); // 获取指定节点的数据"); - } - else - { - // 获取的数据无法转换为目标方法入参类型 - throw new Exception("获取的数据无法转换为目标方法入参类型"); - } - - } - else - { - // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 - throw new Exception("指定了Guid,但项目中不存在对应的节点"); - } - } - #endregion - - } - } - - if (methodInfo.ReturnType == typeof(void)) - { - // 调用无返回值方法 - // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 - var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; - // 如果目标方法是异步的,则自动 await 进行等待 - invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; - sb_invoke_login.AppendCode(3, $"global::{invokeFunctionContext}(", false); - for (int index = 0; index < pds.Length; index++) - { - sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); - } - sb_invoke_login.AppendCode(0, $"); // 调用方法 {md.MethodAnotherName}"); - } - else - { - // 调用有返回值方法 - // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 - var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; - // 如果目标方法是异步的,则自动 await 进行等待 - invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; - sb_invoke_login.AppendCode(3, $"var result = {invokeFunctionContext}(", false); - for (int index = 0; index < pds.Length; index++) - { - sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); - } - sb_invoke_login.AppendCode(0, $"); // 调用方法 {md.MethodAnotherName}"); - - sb_invoke_login.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{actionNode.Guid}\", result);", false); // 更新数据 - //sb_invoke_login.AppendCode(3, $"return result;", false); - } - #endregion - - // global::{returnTypeFullName} - - var resultTypeName = actionNode.MethodDetails.IsAsync ? "async Task" : "void"; - - sb_main.AppendCode(2, $"[Description(\"{instanceTypeFullName}.{methodInfo.Name}\")]"); - sb_main.AppendCode(2, $"private {resultTypeName} {actionNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); - sb_main.AppendCode(2, $"{{"); - sb_main.AppendCode(0, sb_invoke_login.ToString()); - sb_main.AppendCode(2, $"}}"); // 方法结束 - sb_main.AppendLine(); // 方法结束 - - } - - /// - /// 生成[FlowCall]节点的方法调用 - /// - /// - /// - /// - /// - /// - private void CreateMethodCore_FlowCall(StringBuilder sb_main, SingleFlowCallNode flowCallNode, string? flowContextTypeName, string flowContext) - { - if (!flowApiMethodInfos.TryGetValue(flowCallNode, out var flowApiMethodInfo)) - { - return; - } - - if(flowCallNode.TargetNode is SingleScriptNode singleScriptNode) - { - if (!scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) - { - return; - } - - var instanceType = flowCallNode.MethodDetails.ActingInstanceType; - var returnType = singleScriptNode.MethodDetails.ReturnType; - - //var instanceName = instanceType.ToCamelCase();// $"instance_{instanceType.Name}"; - - //var instanceTypeFullName = instanceType.FullName; - var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; - - #region 方法内部逻辑 - StringBuilder sb_invoke_login = new StringBuilder(); - - if (flowCallNode.MethodDetails is null) return; - //var param = methodInfo.GetParameters(); - var md = flowCallNode.MethodDetails; - var pds = flowCallNode.MethodDetails.ParameterDetailss; - //if (param is null) return; - if (pds is null) return; - - /* for (int index = 0; index < pds.Length; index++) - { - ParameterDetails? pd = pds[index]; - ParameterInfo parameterInfo = param[index]; - var paramtTypeFullName = parameterInfo.ParameterType.FullName; - }*/ - - var flowDataName = $"flowData{flowApiMethodInfo.ApiMethodName}"; - var apiData = $"apiData{flowApiMethodInfo.ApiMethodName}"; - sb_invoke_login.AppendCode(3, $"global::{typeof(object).FullName} {flowDataName} = {flowContext}.GetFlowData(\"{flowApiMethodInfo.ApiMethodName}\").Value;"); - sb_invoke_login.AppendCode(3, $"if({flowDataName} is {flowApiMethodInfo.ParamTypeName} {apiData})"); - sb_invoke_login.AppendCode(3, $"{{"); - foreach (var info in flowApiMethodInfo.ParamInfos) - { - sb_invoke_login.AppendCode(4, $"global::{info.Type.FullName} {info.ParamName} = {apiData}.{info.ParamName.ToPascalCase()}; "); - } - var invokeParamContext = string.Join(", ", flowApiMethodInfo.ParamInfos.Select(x => x.ParamName)); - if (flowApiMethodInfo.IsVoid) - { - // 调用无返回值方法 - var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; - // 如果目标方法是异步的,则自动 await 进行等待 - sb_invoke_login.AppendCode(4, $"{(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); - } - else - { - // 调用有返回值方法 - var resultName = $"result{flowApiMethodInfo.ApiMethodName}"; - var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; - // 如果目标方法是异步的,则自动 await 进行等待 - sb_invoke_login.AppendCode(4, $"var {resultName} = {(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); - - sb_invoke_login.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{flowCallNode.TargetNode.Guid}\", {resultName});"); // 更新数据 - sb_invoke_login.AppendCode(4, $"{flowApiMethodInfo.ObjPoolName}.Return({apiData});"); // 归还到对象池 - //sb_invoke_login.AppendCode(3, $"return result;", false); - } - sb_invoke_login.AppendCode(3, $"}}"); - sb_invoke_login.AppendCode(3, $"else"); - sb_invoke_login.AppendCode(3, $"{{"); - sb_invoke_login.AppendCode(4, $"throw new Exception(\"接口参数类型异常\");"); - sb_invoke_login.AppendCode(3, $"}}"); - - - - - #endregion - - - var resultTypeName = flowCallNode.MethodDetails.IsAsync ? "async Task" : "void"; - - sb_main.AppendCode(2, $"[Description(\"脚本节点接口\")]"); - sb_main.AppendCode(2, $"private {resultTypeName} {flowCallNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); - sb_main.AppendCode(2, $"{{"); - sb_main.AppendCode(0, sb_invoke_login.ToString()); - sb_main.AppendCode(2, $"}} "); // 方法结束 - - sb_main.AppendLine(); // 方法结束 - } - else - { - if (!flowLibraryService.TryGetMethodInfo(flowCallNode.MethodDetails.AssemblyName, - flowCallNode.MethodDetails.MethodName, - out var methodInfo) || methodInfo is null) - { - return; - } - - var isRootNode = flowCallNode.IsRoot(); - - var instanceType = flowCallNode.MethodDetails.ActingInstanceType; - var returnType = methodInfo.ReturnType; - - var instanceName = instanceType.ToCamelCase();// $"instance_{instanceType.Name}"; - - var instanceTypeFullName = instanceType.FullName; - var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; - - #region 方法内部逻辑 - StringBuilder sb_invoke_login = new StringBuilder(); - - if (flowCallNode.MethodDetails is null) return; - //var param = methodInfo.GetParameters(); - var md = flowCallNode.MethodDetails; - var pds = flowCallNode.MethodDetails.ParameterDetailss; - //if (param is null) return; - if (pds is null) return; - - /* for (int index = 0; index < pds.Length; index++) - { - ParameterDetails? pd = pds[index]; - ParameterInfo parameterInfo = param[index]; - var paramtTypeFullName = parameterInfo.ParameterType.FullName; - }*/ - - var flowDataName = $"flowData{flowApiMethodInfo.ApiMethodName}"; - var apiData = $"apiData{flowApiMethodInfo.ApiMethodName}"; - sb_invoke_login.AppendCode(3, $"global::{typeof(object).FullName} {flowDataName} = {flowContext}.GetFlowData(\"{flowApiMethodInfo.ApiMethodName}\").Value;"); - sb_invoke_login.AppendCode(3, $"if({flowDataName} is {flowApiMethodInfo.ParamTypeName} {apiData})"); - sb_invoke_login.AppendCode(3, $"{{"); - foreach (var info in flowApiMethodInfo.ParamInfos) - { - sb_invoke_login.AppendCode(4, $"global::{info.Type.FullName} {info.ParamName} = {apiData}.{info.ParamName.ToPascalCase()}; "); - } - var invokeParamContext = string.Join(", ", flowApiMethodInfo.ParamInfos.Select(x => x.ParamName)); - if (flowApiMethodInfo.IsVoid) - { - // 调用无返回值方法 - // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 - var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; - // 如果目标方法是异步的,则自动 await 进行等待 - sb_invoke_login.AppendCode(4, $"{(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); - } - else - { - // 调用有返回值方法 - var resultName = $"result{flowApiMethodInfo.ApiMethodName}"; - // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 - var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; - // 如果目标方法是异步的,则自动 await 进行等待 - sb_invoke_login.AppendCode(4, $"var {resultName} = {(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); - - sb_invoke_login.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{flowCallNode.TargetNode.Guid}\", {resultName});"); // 更新数据 - sb_invoke_login.AppendCode(4, $"{flowApiMethodInfo.ObjPoolName}.Return({apiData});"); // 归还到对象池 - //sb_invoke_login.AppendCode(3, $"return result;", false); - } - sb_invoke_login.AppendCode(3, $"}}"); - sb_invoke_login.AppendCode(3, $"else"); - sb_invoke_login.AppendCode(3, $"{{"); - sb_invoke_login.AppendCode(4, $"throw new Exception(\"接口参数类型异常\");"); - sb_invoke_login.AppendCode(3, $"}}"); - - - - - #endregion - - - var resultTypeName = flowCallNode.MethodDetails.IsAsync ? "async Task" : "void"; - - sb_main.AppendCode(2, $"[Description(\"{instanceTypeFullName}.{methodInfo.Name}\")]"); - sb_main.AppendCode(2, $"private {resultTypeName} {flowCallNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); - sb_main.AppendCode(2, $"{{"); - sb_main.AppendCode(0, sb_invoke_login.ToString()); - sb_main.AppendCode(2, $"}} "); // 方法结束 - - sb_main.AppendLine(); // 方法结束 - } - - - } - - - /// - /// 生成[Script]节点的方法调用 - /// - /// - /// - /// - /// - private void CreateMethodCore_Script(StringBuilder sb_main, SingleScriptNode singleScriptNode, string? flowContextTypeName, string flowContext) - { - - - if (!scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) - { - return; - } - - var isRootNode = singleScriptNode.IsRoot(); - - - var returnType = scriptMethodInfo.ReturnType; - var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; - - #region 方法内部逻辑 - StringBuilder sb_invoke_login = new StringBuilder(); - if (singleScriptNode.MethodDetails is null) return; - var param = scriptMethodInfo.ParamInfos; - var md = singleScriptNode.MethodDetails; - var pds = singleScriptNode.MethodDetails.ParameterDetailss; - if (param is null) return; - if (pds is null) return; - - bool isGetPreviousNode = false; - for (int index = 0; index < pds.Length; index++) - { - ParameterDetails? pd = pds[index]; - SereinScriptMethodInfo.SereinScriptParamInfo parameterInfo = param[index]; - var paramtTypeFullName = parameterInfo.ParameterType.FullName; - - if (pd.IsExplicitData) - { - // 只能是 数值、 文本、枚举, 才能作为显式参数 - if (parameterInfo.ParameterType.IsValueType) - { - if (parameterInfo.ParameterType.IsEnum) - { - sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = global::{paramtTypeFullName}.{pd.DataValue}; // 获取当前节点的上一节点数据"); - } - else - { - var value = pd.DataValue.ToConvertValueType(parameterInfo.ParameterType); - sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据"); - - } - } - else if (parameterInfo.ParameterType == typeof(string)) - { - sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = \"{pd.DataValue}\"; // 获取当前节点的上一节点数据"); - } - else - { - // 处理表达式 - } - - } - else - { - #region 非显式设置的参数以正常方式获取 - if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) - { - var previousNode = $"previousNode{index}"; - var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; - sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{singleScriptNode.Guid}\");"); // 获取运行时上一节点Guid - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); - } - else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) - { - if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) - { - var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; - var otherNodeReturnType = otherNode.MethodDetails.ReturnType; - if (otherNodeReturnType == typeof(object)) - { - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); - } - else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) - { - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); - } - else - { - // 获取的数据无法转换为目标方法入参类型 - throw new Exception("获取的数据无法转换为目标方法入参类型"); - } - } - else - { - // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 - throw new Exception("指定了Guid,但项目中不存在对应的节点"); - } - } - else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) - { - if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) // 获取指定节点 - { - var otherNodeReturnType = otherNode.MethodDetails.ReturnType; - var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{otherNode.MethodDetails.ReturnType.FullName}"; - if (otherNodeReturnType == typeof(object)) - { - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); - } - else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) - { - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {otherNode.ToNodeMethodName()}({flowContext}); // 获取指定节点的数据"); - } - else - { - // 获取的数据无法转换为目标方法入参类型 - throw new Exception("获取的数据无法转换为目标方法入参类型"); - } - - } - else - { - // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 - throw new Exception("指定了Guid,但项目中不存在对应的节点"); - } - } - #endregion - - } - } - - if (scriptMethodInfo.ReturnType == typeof(void)) - { - // 调用无返回值方法 - // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 - var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; - // 如果目标方法是异步的,则自动 await 进行等待 - invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; - sb_invoke_login.AppendCode(3, $"global::{invokeFunctionContext}(", false); - for (int index = 0; index < pds.Length; index++) - { - sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); - } - sb_invoke_login.AppendCode(0, $"); // 调用生成的C#代码"); - } - else - { - // 调用有返回值方法 - // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 - var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; - // 如果目标方法是异步的,则自动 await 进行等待 - invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; - sb_invoke_login.AppendCode(3, $"var result = {invokeFunctionContext}(", false); - for (int index = 0; index < pds.Length; index++) - { - sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); - } - sb_invoke_login.AppendCode(0, $"); // 调用生成的C#代码"); - - sb_invoke_login.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{singleScriptNode.Guid}\", result);", false); // 更新数据 - //sb_invoke_login.AppendCode(3, $"return result;", false); - } - #endregion - - // global::{returnTypeFullName} - - var resultTypeName = singleScriptNode.MethodDetails.IsAsync ? "async Task" : "void"; - - sb_main.AppendCode(2, $"[Description(\"脚本节点\")]"); - sb_main.AppendCode(2, $"private {resultTypeName} {singleScriptNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); - sb_main.AppendCode(2, $"{{"); - sb_main.AppendCode(0, sb_invoke_login.ToString()); - sb_main.AppendCode(2, $"}}"); // 方法结束 - sb_main.AppendLine(); // 方法结束 - - - - - } /// /// 生成初始化方法(用于执行构造函数中无法完成的操作) @@ -929,6 +391,639 @@ namespace Serein.NodeFlow.Services sb.AppendCode(2, $"}}"); } + #region 节点方法生成 + + /// + /// 生成[Action]节点的方法调用 + /// + /// + /// + /// + /// + /// + private void CreateMethodCore_Action(StringBuilder sb_main, SingleActionNode actionNode, string? flowContextTypeName, string flowContext) + { + if (!flowLibraryService.TryGetMethodInfo(actionNode.MethodDetails.AssemblyName, + actionNode.MethodDetails.MethodName, + out var methodInfo) || methodInfo is null) + { + return; + } + + var isRootNode = actionNode.IsRoot(); + + var instanceType = actionNode.MethodDetails.ActingInstanceType; + var returnType = methodInfo.ReturnType; + + var instanceName = instanceType.ToCamelCase();// $"instance_{instanceType.Name}"; + + var instanceTypeFullName = instanceType.FullName; + var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; + + #region 方法内部逻辑 + StringBuilder sb_invoke_login = new StringBuilder(); + if (actionNode.MethodDetails is null) return; + var param = methodInfo.GetParameters(); + var md = actionNode.MethodDetails; + var pds = actionNode.MethodDetails.ParameterDetailss; + if (param is null) return; + if (pds is null) return; + + bool isGetPreviousNode = false; + for (int index = 0; index < pds.Length; index++) + { + ParameterDetails? pd = pds[index]; + ParameterInfo parameterInfo = param[index]; + var paramtTypeFullName = parameterInfo.ParameterType.FullName; + + if (pd.IsExplicitData) + { + // 只能是 数值、 文本、枚举, 才能作为显式参数 + if (parameterInfo.ParameterType.IsValueType) + { + if (parameterInfo.ParameterType.IsEnum) + { + sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = global::{paramtTypeFullName}.{pd.DataValue}; // 获取当前节点的上一节点数据"); + } + else + { + var value = pd.DataValue.ToConvertValueType(parameterInfo.ParameterType); + sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据"); + + } + } + else if (parameterInfo.ParameterType == typeof(string)) + { + sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = \"{pd.DataValue}\"; // 获取当前节点的上一节点数据"); + } + else + { + // 处理表达式 + } + + } + else + { + #region 非显式设置的参数以正常方式获取 + if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) + { + var previousNode = $"previousNode{index}"; + var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; + sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{actionNode.Guid}\");"); // 获取运行时上一节点Guid + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); + } + else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) + { + if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) + { + var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; + var otherNodeReturnType = otherNode.MethodDetails.ReturnType; + if (otherNodeReturnType == typeof(object)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); + } + else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); + } + else + { + // 获取的数据无法转换为目标方法入参类型 + throw new Exception("获取的数据无法转换为目标方法入参类型"); + } + } + else + { + // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 + throw new Exception("指定了Guid,但项目中不存在对应的节点"); + } + } + else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) + { + if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) // 获取指定节点 + { + var otherNodeReturnType = otherNode.MethodDetails.ReturnType; + var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{otherNode.MethodDetails.ReturnType.FullName}"; + if (otherNodeReturnType == typeof(object)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); + } + else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {otherNode.ToNodeMethodName()}({flowContext}); // 获取指定节点的数据"); + } + else + { + // 获取的数据无法转换为目标方法入参类型 + throw new Exception("获取的数据无法转换为目标方法入参类型"); + } + + } + else + { + // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 + throw new Exception("指定了Guid,但项目中不存在对应的节点"); + } + } + #endregion + + } + } + + if (methodInfo.ReturnType == typeof(void)) + { + // 调用无返回值方法 + // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 + var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; + // 如果目标方法是异步的,则自动 await 进行等待 + invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; + sb_invoke_login.AppendCode(3, $"global::{invokeFunctionContext}(", false); + for (int index = 0; index < pds.Length; index++) + { + sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); + } + sb_invoke_login.AppendCode(0, $"); // 调用方法 {md.MethodAnotherName}"); + } + else + { + // 调用有返回值方法 + // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 + var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; + // 如果目标方法是异步的,则自动 await 进行等待 + invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; + sb_invoke_login.AppendCode(3, $"var result = {invokeFunctionContext}(", false); + for (int index = 0; index < pds.Length; index++) + { + sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); + } + sb_invoke_login.AppendCode(0, $"); // 调用方法 {md.MethodAnotherName}"); + + sb_invoke_login.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{actionNode.Guid}\", result);", false); // 更新数据 + //sb_invoke_login.AppendCode(3, $"return result;", false); + } + #endregion + + // global::{returnTypeFullName} + + var resultTypeName = actionNode.MethodDetails.IsAsync ? "async Task" : "void"; + + sb_main.AppendCode(2, $"[Description(\"{instanceTypeFullName}.{methodInfo.Name}\")]"); + sb_main.AppendCode(2, $"private {resultTypeName} {actionNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); + sb_main.AppendCode(2, $"{{"); + sb_main.AppendCode(0, sb_invoke_login.ToString()); + sb_main.AppendCode(2, $"}}"); // 方法结束 + sb_main.AppendLine(); // 方法结束 + + } + + /// + /// 生成[FlowCall]节点的方法调用 + /// + /// + /// + /// + /// + /// + private void CreateMethodCore_FlowCall(StringBuilder sb_main, SingleFlowCallNode flowCallNode, string? flowContextTypeName, string flowContext) + { + if (!flowApiMethodInfos.TryGetValue(flowCallNode, out var flowApiMethodInfo)) + { + return; + } + + if (flowCallNode.TargetNode is SingleScriptNode singleScriptNode) + { + if (!scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) + { + return; + } + + var instanceType = flowCallNode.MethodDetails.ActingInstanceType; + var returnType = singleScriptNode.MethodDetails.ReturnType; + + //var instanceName = instanceType.ToCamelCase();// $"instance_{instanceType.Name}"; + + //var instanceTypeFullName = instanceType.FullName; + var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; + + #region 方法内部逻辑 + StringBuilder sb_invoke_login = new StringBuilder(); + + if (flowCallNode.MethodDetails is null) return; + //var param = methodInfo.GetParameters(); + var md = flowCallNode.MethodDetails; + var pds = flowCallNode.MethodDetails.ParameterDetailss; + //if (param is null) return; + if (pds is null) return; + + /* for (int index = 0; index < pds.Length; index++) + { + ParameterDetails? pd = pds[index]; + ParameterInfo parameterInfo = param[index]; + var paramtTypeFullName = parameterInfo.ParameterType.FullName; + }*/ + + var flowDataName = $"flowData{flowApiMethodInfo.ApiMethodName}"; + var apiData = $"apiData{flowApiMethodInfo.ApiMethodName}"; + sb_invoke_login.AppendCode(3, $"global::{typeof(object).FullName} {flowDataName} = {flowContext}.GetFlowData(\"{flowApiMethodInfo.ApiMethodName}\").Value;"); + sb_invoke_login.AppendCode(3, $"if({flowDataName} is {flowApiMethodInfo.ParamTypeName} {apiData})"); + sb_invoke_login.AppendCode(3, $"{{"); + foreach (var info in flowApiMethodInfo.ParamInfos) + { + sb_invoke_login.AppendCode(4, $"global::{info.Type.FullName} {info.ParamName} = {apiData}.{info.ParamName.ToPascalCase()}; "); + } + var invokeParamContext = string.Join(", ", flowApiMethodInfo.ParamInfos.Select(x => x.ParamName)); + if (flowApiMethodInfo.IsVoid) + { + // 调用无返回值方法 + var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; + // 如果目标方法是异步的,则自动 await 进行等待 + sb_invoke_login.AppendCode(4, $"{(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + } + else + { + // 调用有返回值方法 + var resultName = $"result{flowApiMethodInfo.ApiMethodName}"; + var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; + // 如果目标方法是异步的,则自动 await 进行等待 + sb_invoke_login.AppendCode(4, $"var {resultName} = {(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + + sb_invoke_login.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{flowCallNode.TargetNode.Guid}\", {resultName});"); // 更新数据 + sb_invoke_login.AppendCode(4, $"{flowApiMethodInfo.ObjPoolName}.Return({apiData});"); // 归还到对象池 + //sb_invoke_login.AppendCode(3, $"return result;", false); + } + sb_invoke_login.AppendCode(3, $"}}"); + sb_invoke_login.AppendCode(3, $"else"); + sb_invoke_login.AppendCode(3, $"{{"); + sb_invoke_login.AppendCode(4, $"throw new Exception(\"接口参数类型异常\");"); + sb_invoke_login.AppendCode(3, $"}}"); + + + + + #endregion + + + var resultTypeName = flowCallNode.MethodDetails.IsAsync ? "async Task" : "void"; + + sb_main.AppendCode(2, $"[Description(\"脚本节点接口\")]"); + sb_main.AppendCode(2, $"private {resultTypeName} {flowCallNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); + sb_main.AppendCode(2, $"{{"); + sb_main.AppendCode(0, sb_invoke_login.ToString()); + sb_main.AppendCode(2, $"}} "); // 方法结束 + + sb_main.AppendLine(); // 方法结束 + } + else + { + if (!flowLibraryService.TryGetMethodInfo(flowCallNode.MethodDetails.AssemblyName, + flowCallNode.MethodDetails.MethodName, + out var methodInfo) || methodInfo is null) + { + return; + } + + var isRootNode = flowCallNode.IsRoot(); + + var instanceType = flowCallNode.MethodDetails.ActingInstanceType; + var returnType = methodInfo.ReturnType; + + var instanceName = instanceType.ToCamelCase();// $"instance_{instanceType.Name}"; + + var instanceTypeFullName = instanceType.FullName; + var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; + + #region 方法内部逻辑 + StringBuilder sb_invoke_login = new StringBuilder(); + + if (flowCallNode.MethodDetails is null) return; + //var param = methodInfo.GetParameters(); + var md = flowCallNode.MethodDetails; + var pds = flowCallNode.MethodDetails.ParameterDetailss; + //if (param is null) return; + if (pds is null) return; + + /* for (int index = 0; index < pds.Length; index++) + { + ParameterDetails? pd = pds[index]; + ParameterInfo parameterInfo = param[index]; + var paramtTypeFullName = parameterInfo.ParameterType.FullName; + }*/ + + var flowDataName = $"flowData{flowApiMethodInfo.ApiMethodName}"; + var apiData = $"apiData{flowApiMethodInfo.ApiMethodName}"; + sb_invoke_login.AppendCode(3, $"global::{typeof(object).FullName} {flowDataName} = {flowContext}.GetFlowData(\"{flowApiMethodInfo.ApiMethodName}\").Value;"); + sb_invoke_login.AppendCode(3, $"if({flowDataName} is {flowApiMethodInfo.ParamTypeName} {apiData})"); + sb_invoke_login.AppendCode(3, $"{{"); + foreach (var info in flowApiMethodInfo.ParamInfos) + { + sb_invoke_login.AppendCode(4, $"global::{info.Type.FullName} {info.ParamName} = {apiData}.{info.ParamName.ToPascalCase()}; "); + } + var invokeParamContext = string.Join(", ", flowApiMethodInfo.ParamInfos.Select(x => x.ParamName)); + if (flowApiMethodInfo.IsVoid) + { + // 调用无返回值方法 + // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 + var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; + // 如果目标方法是异步的,则自动 await 进行等待 + sb_invoke_login.AppendCode(4, $"{(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + } + else + { + // 调用有返回值方法 + var resultName = $"result{flowApiMethodInfo.ApiMethodName}"; + // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 + var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; + // 如果目标方法是异步的,则自动 await 进行等待 + sb_invoke_login.AppendCode(4, $"var {resultName} = {(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + + sb_invoke_login.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{flowCallNode.TargetNode.Guid}\", {resultName});"); // 更新数据 + sb_invoke_login.AppendCode(4, $"{flowApiMethodInfo.ObjPoolName}.Return({apiData});"); // 归还到对象池 + //sb_invoke_login.AppendCode(3, $"return result;", false); + } + sb_invoke_login.AppendCode(3, $"}}"); + sb_invoke_login.AppendCode(3, $"else"); + sb_invoke_login.AppendCode(3, $"{{"); + sb_invoke_login.AppendCode(4, $"throw new Exception(\"接口参数类型异常\");"); + sb_invoke_login.AppendCode(3, $"}}"); + + + + + #endregion + + + var resultTypeName = flowCallNode.MethodDetails.IsAsync ? "async Task" : "void"; + + sb_main.AppendCode(2, $"[Description(\"{instanceTypeFullName}.{methodInfo.Name}\")]"); + sb_main.AppendCode(2, $"private {resultTypeName} {flowCallNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); + sb_main.AppendCode(2, $"{{"); + sb_main.AppendCode(0, sb_invoke_login.ToString()); + sb_main.AppendCode(2, $"}} "); // 方法结束 + + sb_main.AppendLine(); // 方法结束 + } + + + } + + /// + /// 生成[Script]节点的方法调用 + /// + /// + /// + /// + /// + private void CreateMethodCore_Script(StringBuilder sb_main, SingleScriptNode singleScriptNode, string? flowContextTypeName, string flowContext) + { + + + if (!scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) + { + return; + } + + var isRootNode = singleScriptNode.IsRoot(); + + + var returnType = scriptMethodInfo.ReturnType; + var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; + + #region 方法内部逻辑 + StringBuilder sb_invoke_login = new StringBuilder(); + if (singleScriptNode.MethodDetails is null) return; + var param = scriptMethodInfo.ParamInfos; + var md = singleScriptNode.MethodDetails; + var pds = singleScriptNode.MethodDetails.ParameterDetailss; + if (param is null) return; + if (pds is null) return; + + bool isGetPreviousNode = false; + for (int index = 0; index < pds.Length; index++) + { + ParameterDetails? pd = pds[index]; + SereinScriptMethodInfo.SereinScriptParamInfo parameterInfo = param[index]; + var paramtTypeFullName = parameterInfo.ParameterType.FullName; + + if (pd.IsExplicitData) + { + // 只能是 数值、 文本、枚举, 才能作为显式参数 + if (parameterInfo.ParameterType.IsValueType) + { + if (parameterInfo.ParameterType.IsEnum) + { + sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = global::{paramtTypeFullName}.{pd.DataValue}; // 获取当前节点的上一节点数据"); + } + else + { + var value = pd.DataValue.ToConvertValueType(parameterInfo.ParameterType); + sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据"); + + } + } + else if (parameterInfo.ParameterType == typeof(string)) + { + sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = \"{pd.DataValue}\"; // 获取当前节点的上一节点数据"); + } + else + { + // 处理表达式 + } + + } + else + { + #region 非显式设置的参数以正常方式获取 + if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) + { + var previousNode = $"previousNode{index}"; + var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; + sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{singleScriptNode.Guid}\");"); // 获取运行时上一节点Guid + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); + } + else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) + { + if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) + { + var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; + var otherNodeReturnType = otherNode.MethodDetails.ReturnType; + if (otherNodeReturnType == typeof(object)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); + } + else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); + } + else + { + // 获取的数据无法转换为目标方法入参类型 + throw new Exception("获取的数据无法转换为目标方法入参类型"); + } + } + else + { + // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 + throw new Exception("指定了Guid,但项目中不存在对应的节点"); + } + } + else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) + { + if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) // 获取指定节点 + { + var otherNodeReturnType = otherNode.MethodDetails.ReturnType; + var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{otherNode.MethodDetails.ReturnType.FullName}"; + if (otherNodeReturnType == typeof(object)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); + } + else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {otherNode.ToNodeMethodName()}({flowContext}); // 获取指定节点的数据"); + } + else + { + // 获取的数据无法转换为目标方法入参类型 + throw new Exception("获取的数据无法转换为目标方法入参类型"); + } + + } + else + { + // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 + throw new Exception("指定了Guid,但项目中不存在对应的节点"); + } + } + #endregion + + } + } + + if (scriptMethodInfo.ReturnType == typeof(void)) + { + // 调用无返回值方法 + // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 + var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; + // 如果目标方法是异步的,则自动 await 进行等待 + invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; + sb_invoke_login.AppendCode(3, $"global::{invokeFunctionContext}(", false); + for (int index = 0; index < pds.Length; index++) + { + sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); + } + sb_invoke_login.AppendCode(0, $"); // 调用生成的C#代码"); + } + else + { + // 调用有返回值方法 + // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 + var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; + // 如果目标方法是异步的,则自动 await 进行等待 + invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; + sb_invoke_login.AppendCode(3, $"var result = {invokeFunctionContext}(", false); + for (int index = 0; index < pds.Length; index++) + { + sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); + } + sb_invoke_login.AppendCode(0, $"); // 调用生成的C#代码"); + + sb_invoke_login.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{singleScriptNode.Guid}\", result);", false); // 更新数据 + //sb_invoke_login.AppendCode(3, $"return result;", false); + } + #endregion + + // global::{returnTypeFullName} + + var resultTypeName = singleScriptNode.MethodDetails.IsAsync ? "async Task" : "void"; + + sb_main.AppendCode(2, $"[Description(\"脚本节点\")]"); + sb_main.AppendCode(2, $"private {resultTypeName} {singleScriptNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); + sb_main.AppendCode(2, $"{{"); + sb_main.AppendCode(0, sb_invoke_login.ToString()); + sb_main.AppendCode(2, $"}}"); // 方法结束 + sb_main.AppendLine(); // 方法结束 + + + + + } + + #endregion + #region 全局节点的代码生成 + + private Dictionary globalDataInfos = []; + private const string FlowGlobalData = nameof(FlowGlobalData); + + private void GenerateGlobalData_InitSereinGlobalDataInfos(SingleGlobalDataNode[] globalDataNodes) + { + foreach(var node in globalDataNodes) + { + var keyName = node.KeyName; + var dataNode = node.DataNode; + if(dataNode is null) + { + throw new Exception($"全局数据节点[{node}]没有指定数据来源节点"); + } + var type = dataNode.MethodDetails.ReturnType; + if (type is null || type == typeof(void)) + { + throw new Exception($"全局数据节点[{node}]无返回值"); + } + globalDataInfos[node] = new SereinGlobalDataInfo + { + Node = node, + DataSourceNode = dataNode, + KeyName = keyName, + DataType = type, + }; + } + } + + /// + /// 生成数据实体类 + /// + /// + private void GenerateGlobalData_ToClass(StringBuilder sb) + { + var infos = globalDataInfos.Values.ToArray(); + sb.AppendCode(1, $"public sealed class {FlowGlobalData}"); + sb.AppendCode(1, $"{{"); + foreach (var info in infos) + { + var xmlDescription = $"{$"全局数据,来源于[{info.Node.MethodDetails?.MethodName}]{info.Node.Guid}".ToXmlComments(2)}"; + sb.AppendCode(2, xmlDescription); + sb.AppendCode(2, $"public global::{info.DataType.FullName} {info.KeyName} {{ get; set; }};"); + } + sb.AppendLine(); + sb.AppendCode(1, $"}}"); + } + + public class SereinGlobalDataInfo + { + /// + /// 全局数据节点 + /// + public SingleGlobalDataNode Node { get; set; } + + /// + /// 全局数据的来源节点 + /// + public IFlowNode DataSourceNode { get; set; } + + /// + /// 全局数据的键名 + /// + public string KeyName { get; set; } = string.Empty; + /// + /// 全局数据的类型 + /// + public Type DataType { get; set; } = typeof(object); + } + + + #endregion + + #region 脚本节点的代码生成 private Dictionary scriptMethodInfos = []; private void GenerateScript_InitSereinScriptMethodInfos(SingleScriptNode[] flowCallNodes) @@ -963,6 +1058,7 @@ namespace Serein.NodeFlow.Services /// private Dictionary flowApiMethodInfos = []; + /// /// 生成流程接口方法信息 /// @@ -1000,8 +1096,8 @@ namespace Serein.NodeFlow.Services } */ - sb.AppendCode(2, $"public interface IFlowApiInvoke"); - sb.AppendCode(2, $"{{"); + sb.AppendCode(1, $"public interface IFlowApiInvoke"); + sb.AppendCode(1, $"{{"); var infos = flowApiMethodInfos.Values.ToArray(); foreach (var info in infos) { @@ -1020,7 +1116,7 @@ namespace Serein.NodeFlow.Services sb.AppendCode(2, info.ToImpleMethodSignature(FlowApiMethodInfo.ParamType.HasContextAndToken));*/ } sb.AppendLine(); - sb.AppendCode(2, $"}}"); + sb.AppendCode(1, $"}}"); } @@ -1086,528 +1182,4 @@ namespace Serein.NodeFlow.Services - - - - - - - - - - - /// - /// 指示流程接口方法需要生成什么代码 - /// - internal class FlowApiMethodInfo - { - public FlowApiMethodInfo(SingleFlowCallNode singleFlowCallNode) - { - NodeModel = singleFlowCallNode; - } - public SingleFlowCallNode NodeModel { get; } // 流程调用节点 - public string ApiMethodName { get; set; } - public Type ReturnType { get; set; } - public List ParamInfos { get; set; } = []; - - public string ParamTypeName => $"FlowApiInvoke_{ApiMethodName}"; - - public class ParamInfo - { - public ParamInfo(Type type, string paramName) - { - Type = type; - ParamName = paramName; - } - - public Type Type { get; set; } - public string ParamName { get; set; } - public string Comments { get; set; } - } - - - public enum ParamType - { - Defute, // 仅使用方法参数 - HasToken, // 包含取消令牌和方法参数 - HasContextAndToken, // 包含上下文、取消令牌和方法参数 - } - public bool IsVoid => ReturnType == typeof(void); - public string ObjPoolName => $"_objPool{ParamTypeName.ToPascalCase()}"; - /// - /// 生成所需的参数类的签名代码 - /// - /// - public string ToParamterClassSignature() - { - StringBuilder sb = new StringBuilder(); - - - //sb.AppendCode(2, $"private static readonly global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}> {ObjPoolName} = "); - //sb.AppendCode(2, $" new global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}>("); - //sb.AppendCode(2, $" new global::Microsoft.Extensions.ObjectPool.DefaultPooledObjectPolicy<{ParamTypeName}>()); "); - sb.AppendLine(); - var classXmlComments = $"流程接口[{ApiMethodName}]需要的参数类".ToXmlComments(2); - sb.AppendCode(2, classXmlComments); - sb.AppendCode(2, $"public class {ParamTypeName}"); - sb.AppendCode(2, $"{{"); - for (int index = 0; index < ParamInfos.Count; index++) - { - ParamInfo? info = ParamInfos[index]; - var argXmlComments = $"[{index}]流程接口参数{(string.IsNullOrWhiteSpace(info.Comments) ? string.Empty : $",{info.Comments}。")}".ToXmlComments(2); - sb.AppendCode(3, argXmlComments); - sb.AppendCode(3, $"public global::{info.Type.FullName} {info.ParamName.ToPascalCase()} {{ get; set; }}"); - } - sb.AppendCode(2, $"}}"); - return sb.ToString(); - } - - public string ToObjPoolSignature() - { - StringBuilder sb = new StringBuilder(); - sb.AppendCode(2, $"private static readonly global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}> {ObjPoolName} = "); - sb.AppendCode(4, $"new global::Microsoft.Extensions.ObjectPool.DefaultObjectPool<{ParamTypeName}>("); - sb.AppendCode(5, $"new global::Microsoft.Extensions.ObjectPool.DefaultPooledObjectPolicy<{ParamTypeName}>()); "); - return sb.ToString(); - } - - /// - /// 生成接口的签名方法 - /// - /// - /// - /// - public string ToInterfaceMethodSignature(ParamType type) - { - var taskTypeFullName = $"global::System.Threading.Tasks.Task"; - var contextFullName = $"global::{typeof(IFlowContext).FullName}"; - var tokenFullName = $"global::{typeof(CancellationToken).FullName}"; - var returnContext = IsVoid ? taskTypeFullName : $"{taskTypeFullName}<{ReturnType.FullName}>"; - if (type == ParamType.Defute) - { - var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); - return $"{returnContext} {ApiMethodName}({paramSignature});"; - } - else if (type == ParamType.HasToken) - { - var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); - return $"{returnContext} {ApiMethodName}({tokenFullName} token, {paramSignature});"; - } - else if (type == ParamType.HasContextAndToken) - { - var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); - return $"{returnContext} {ApiMethodName}({contextFullName} flowContext, {tokenFullName} token, {paramSignature});"; - } - throw new Exception(); - } - - /// - /// 生成实现方法的签名代码 - /// - /// - /// - /// - public string ToImpleMethodSignature(ParamType type) - { - var taskTypeFullName = $"global::System.Threading.Tasks.Task"; - var contextApiFullName = $"global::{typeof(IFlowContext).FullName}"; - var contextImpleFullName = $"global::{typeof(FlowContext).FullName}"; - var tokenSourceFullName = $"global::{typeof(CancellationTokenSource).FullName}"; - var tokenFullName = $"global::{typeof(CancellationToken).FullName}"; - var flowContextPoolName = $"global::{typeof(LightweightFlowControl).FullName}"; - string flowEnvironment = nameof(flowEnvironment); - string flowContext = nameof(flowContext); - string token = nameof(token); - - var returnTypeContext = IsVoid ? taskTypeFullName : $"{taskTypeFullName}<{ReturnType.FullName}>"; - - StringBuilder sb = new StringBuilder(); - if (IsVoid) - { - if (type == ParamType.Defute) - { - var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{{{p.Type.FullName} {p.ParamName}")); - var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); - sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({paramSignature})"); - sb.AppendCode(2, $"{{"); - sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文"); - sb.AppendCode(3, $"{tokenSourceFullName} cts = new {tokenSourceFullName}(); // 创建取消令牌"); - sb.AppendCode(3, $"try"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"await {ApiMethodName}({flowContext}, cts.Token, {invokeParamSignature}); // 调用目标方法"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"catch (Exception)"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"throw;"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"finally"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); "); - sb.AppendCode(4, $"cts.{nameof(CancellationTokenSource.Dispose)}(); "); - sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(2, $"}}"); - return sb.ToString(); - } - else if (type == ParamType.HasToken) - { - var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{{{p.Type.FullName} {p.ParamName}")); - var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); - sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({tokenFullName} {token}, {paramSignature})"); - sb.AppendCode(2, $"{{"); - sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文"); - sb.AppendCode(3, $"try"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"await {ApiMethodName}({flowContext}, {token}, {invokeParamSignature}); // 调用目标方法"); - sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); "); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"catch (Exception)"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"throw;"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"finally"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(2, $"}}"); - return sb.ToString(); - } - else if (type == ParamType.HasContextAndToken) - { - var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{{{p.Type.FullName} {p.ParamName}")); - var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); - - - sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({contextApiFullName} {flowContext}, {tokenFullName} token, {paramSignature})"); - sb.AppendCode(2, $"{{"); - sb.AppendCode(3, $"token.ThrowIfCancellationRequested(); // 检查任务是否取消"); - sb.AppendCode(3, $"try"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(3, $"global::{ParamTypeName} data = {ObjPoolName}.Get(); // 从对象池获取一个对象"); - for (int index = 0; index < ParamInfos.Count; index++) - { - ParamInfo? info = ParamInfos[index]; - sb.AppendCode(4, $"data.{info.ParamName.ToPascalCase()} = {info.ParamName}; // [{index}] {info.Comments}"); - } - sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{ApiMethodName}\", data);"); - sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.SetPreviousNode)}(\"{NodeModel.TargetNode.Guid}\", \"{ApiMethodName}\");"); - sb.AppendCode(3, $"global::{typeof(CallNode).FullName} node = Get(\"{NodeModel.Guid}\");"); - sb.AppendCode(3, $"await node.{nameof(CallNode.StartFlowAsync)}({flowContext}, {token}); // 调用目标方法"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"catch (Exception)"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"throw;"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"finally"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(2, $"}}"); - return sb.ToString(); - } - } - else - { - string flowResult = nameof(flowResult); - if (type == ParamType.Defute) - { - //sb.AppendCode(3, $"{contextApiFullName} {flowContext} = new {contextImpleFullName}({flowEnvironment}); // 创建上下文"); - - var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); - var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); - sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({paramSignature})"); - sb.AppendCode(2, $"{{"); - sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文"); - sb.AppendCode(3, $"{tokenSourceFullName} cts = new {tokenSourceFullName}(); // 创建取消令牌"); - sb.AppendCode(3, $"try"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"{ReturnType.FullName} {flowResult} = await {ApiMethodName}({flowContext}, cts.{nameof(CancellationTokenSource.Token)}, {invokeParamSignature}); // 调用目标方法"); - sb.AppendCode(4, $"return {flowResult};"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"catch (Exception)"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"throw;"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"finally"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); "); - sb.AppendCode(4, $"cts.{nameof(CancellationTokenSource.Dispose)}(); "); - sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(2, $"}}"); - return sb.ToString(); - } - else if (type == ParamType.HasToken) - { - var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); - var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); - sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({tokenFullName} {token}, {paramSignature})"); - sb.AppendCode(2, $"{{"); - sb.AppendCode(3, $"{contextApiFullName} {flowContext} = {flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Allocate)}(); // 从对象池获取一个上下文"); - sb.AppendCode(3, $"try"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"{ReturnType.FullName} {flowResult} = await {ApiMethodName}({flowContext}, {token}, {invokeParamSignature}); // 调用目标方法"); - sb.AppendCode(4, $"return {flowResult};"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"catch (Exception)"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"throw;"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"finally"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.Reset)}(); "); - sb.AppendCode(4, $"{flowContextPoolName}.{nameof(LightweightFlowControl.FlowContextPool)}.{nameof(LightweightFlowControl.FlowContextPool.Free)}({flowContext}); // 释放上下文"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(2, $"}}"); - return sb.ToString(); - } - else if (type == ParamType.HasContextAndToken) - { - var paramSignature = string.Join(", ", ParamInfos.Select(p => $"global::{p.Type.FullName} {p.ParamName}")); - var invokeParamSignature = string.Join(", ", ParamInfos.Select(p => p.ParamName)); - sb.AppendCode(2, $"public async {returnTypeContext} {ApiMethodName}({contextApiFullName} {flowContext}, {tokenFullName} token, {paramSignature})"); - sb.AppendCode(2, $"{{"); - sb.AppendCode(3, $"token.ThrowIfCancellationRequested(); // 检查任务是否取消"); - sb.AppendCode(3, $"global::{ParamTypeName} data = {ObjPoolName}.Get(); // 从对象池获取一个对象"); - for (int index = 0; index < ParamInfos.Count; index++) - { - ParamInfo? info = ParamInfos[index]; - sb.AppendCode(4, $"data.{info.ParamName.ToPascalCase()} = {info.ParamName}; // [{index}] {info.Comments}"); // 进行赋值 - } - sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{ApiMethodName}\", data);"); - sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.SetPreviousNode)}(\"{NodeModel.Guid}\", \"{ApiMethodName}\");"); - sb.AppendCode(3, $"global::{typeof(CallNode).FullName} node = Get(\"{NodeModel.Guid}\");"); - sb.AppendCode(3, $"global::{typeof(FlowResult).FullName} {flowResult} = await node.{nameof(CallNode.StartFlowAsync)}({flowContext}, {token}); // 调用目标方法"); - if(ReturnType == typeof(object)) - { - sb.AppendCode(3, $"if ({flowResult}.{nameof(FlowResult.Value)} is null)"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"return null;"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"else", isWrapping :false); - } - sb.AppendCode(3, $"if ({flowResult}.{nameof(FlowResult.Value)} is global::{ReturnType.FullName} result)"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"return result;"); - sb.AppendCode(3, $"}}"); - sb.AppendCode(3, $"else"); - sb.AppendCode(3, $"{{"); - sb.AppendCode(4, $"throw new ArgumentNullException($\"类型转换失败,{{(flowResult.Value is null ? \"返回数据为 null\" : $\"返回数据与需求类型不匹配,当前返回类型为[{{flowResult.Value.GetType().FullName}}。\")}}\");"); - sb.AppendCode(3, $"}}"); - //sb.AppendCode(3, $"return {flowResult};"); - sb.AppendCode(2, $"}}"); - return sb.ToString(); - // throw new ArgumentNullException($"类型转换失败,{(flowResult.Value is null ? "返回数据为 null" : $"返回数据与需求类型不匹配,当前返回类型为[{flowResult.Value.GetType().FullName}。")}"); - } - } - - throw new Exception(); - - } - } - - internal static class CoreGenerateExtension - { - /// - /// 生成流程接口信息描述 - /// - /// - /// - public static FlowApiMethodInfo? ToFlowApiMethodInfo(this SingleFlowCallNode flowCallNode) - { - var targetNode = flowCallNode.TargetNode; - if (targetNode.ControlType is not (NodeControlType.Action or NodeControlType.Script)) return null; - if (flowCallNode.MethodDetails is null) return null; - if (string.IsNullOrWhiteSpace(flowCallNode.ApiGlobalName)) return null; - - - FlowApiMethodInfo flowApiMethodInfo = new FlowApiMethodInfo(flowCallNode); - flowApiMethodInfo.ReturnType = targetNode.ControlType == NodeControlType.Script ? typeof(object) - : flowCallNode.MethodDetails.ReturnType; - - flowApiMethodInfo.ApiMethodName = flowCallNode.ApiGlobalName; - - List list = []; - - int index = 0; - foreach (var pd in flowCallNode.MethodDetails.ParameterDetailss) - { - if (pd.DataType is null || string.IsNullOrWhiteSpace(pd.Name)) - { - return null; - } - if (pd.IsParams) - { - list.Add(new FlowApiMethodInfo.ParamInfo(pd.DataType, $"{pd.Name}{index++}")); - } - else - { - list.Add(new FlowApiMethodInfo.ParamInfo(pd.DataType, pd.Name)); - } - } - - flowApiMethodInfo.ParamInfos = list; - return flowApiMethodInfo; - } - - - - - - /// - /// 生成方法名称 - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ToNodeMethodName(this IFlowNode flowNode) - { - /*if (!flowLibraryService.TryGetMethodInfo(flowNode.MethodDetails.AssemblyName, - flowNode.MethodDetails.MethodName, - out var methodInfo)) - { - throw new Exception(); - }*/ - var guid = flowNode.Guid; - var tmp = guid.Replace("-", ""); - var methodName = $"FlowMethod_{tmp}"; - return methodName; - } - - - /// - /// 生成完全的xml注释 - /// - /// - /// - public static string ToXmlComments(this string context, int retractCount = 0) - { - StringBuilder sb = new StringBuilder(); - var startLine = "/// "; - var endLine = "/// "; - sb.AppendLine(startLine); - var rows = context.Split(Environment.NewLine); - string retract = new string(' ', retractCount * 4); - foreach (var row in rows) - { - // 处理转义 - var value = row.Replace("<", "<") - .Replace(">", ">"); - sb.AppendLine($"{retract}/// {value}"); - } - sb.AppendLine(endLine); - return sb.ToString(); - } - - /// - /// 生成类型的驼峰命名法名称(首字母小写) - /// - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ToCamelCase(this Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - // 获取类型名称(不包括命名空间) - string typeName = type.Name; - - if (string.IsNullOrEmpty(typeName)) - { - return string.Empty; - } - - // 处理泛型类型(去掉后面的`N) - int indexOfBacktick = typeName.IndexOf('`'); - if (indexOfBacktick > 0) - { - typeName = typeName.Substring(0, indexOfBacktick); - } - - // 如果是接口且以"I"开头,去掉第一个字母 - if (type.IsInterface && typeName.Length > 1 && typeName[0] == 'I' && char.IsUpper(typeName[1])) - { - typeName = typeName.Substring(1); - } - - // 转换为驼峰命名法:首字母小写,其余不变 - if (typeName.Length > 0) - { - return char.ToLowerInvariant(typeName[0]) + typeName.Substring(1); - } - - return typeName; - } - - /// - /// 生成类型的大驼峰命名法名称(PascalCase) - /// - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ToPascalCase(this Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - string typeName = type.Name; - - if (string.IsNullOrEmpty(typeName)) - { - return string.Empty; - } - - // 去掉泛型标记(如 `1) - int indexOfBacktick = typeName.IndexOf('`'); - if (indexOfBacktick > 0) - { - typeName = typeName.Substring(0, indexOfBacktick); - } - - // 如果是接口以 I 开头,且后面是大写,去掉前缀 I - if (type.IsInterface && typeName.Length > 1 && typeName[0] == 'I' && char.IsUpper(typeName[1])) - { - typeName = typeName.Substring(1); - } - - // 首字母转为大写(如果有需要) - if (typeName.Length > 0) - { - return char.ToUpperInvariant(typeName[0]) + typeName.Substring(1); - } - - return typeName; - } - - /// - /// 将字符串首字母大写(PascalCase) - /// - /// 原始文本 - /// 首字母大写的文本 - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ToPascalCase(this string text) - { - if (string.IsNullOrEmpty(text)) - { - return string.Empty; - } - - if (char.IsUpper(text[0])) - { - return text; // 已是大写 - } - - return char.ToUpperInvariant(text[0]) + text.Substring(1); - } - } - - - } diff --git a/NodeFlow/Services/FlowModelService.cs b/NodeFlow/Services/FlowModelService.cs index 9a54861..320615f 100644 --- a/NodeFlow/Services/FlowModelService.cs +++ b/NodeFlow/Services/FlowModelService.cs @@ -1,6 +1,7 @@ using Serein.Library; using Serein.Library.Api; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; namespace Serein.NodeFlow.Services { diff --git a/NodeFlow/Services/FlowWorkManagement.cs b/NodeFlow/Services/FlowWorkManagement.cs index abe2f7b..650f6a2 100644 --- a/NodeFlow/Services/FlowWorkManagement.cs +++ b/NodeFlow/Services/FlowWorkManagement.cs @@ -3,6 +3,7 @@ using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.NodeFlow.Tool; using System; using System.Collections.Concurrent; @@ -285,7 +286,7 @@ namespace Serein.NodeFlow.Services checkpoints["执行流程"] = sw.Elapsed; - if (context.IsRecordInvokeInfo) + if (context.IsRecordInvokeInfo && false) { var invokeInfos = context.GetAllInvokeInfos(); _ = Task.Delay(100).ContinueWith(async (task) => @@ -309,9 +310,7 @@ namespace Serein.NodeFlow.Services SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{total / invokeInfos.Count}"); SereinEnv.WriteLine(InfoType.INFO, $"总耗时:{total}"); } - - - }); + }); } @@ -324,6 +323,7 @@ namespace Serein.NodeFlow.Services _ = Task.Run(() => { + //var checkpoints = new Dictionary(); var last = TimeSpan.Zero; foreach (var kv in checkpoints) { diff --git a/Serein.Script/Node/AssignmentNode.cs b/Serein.Script/Node/AssignmentNode.cs index a8486b6..fd2521d 100644 --- a/Serein.Script/Node/AssignmentNode.cs +++ b/Serein.Script/Node/AssignmentNode.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml.Linq; namespace Serein.Script.Node { @@ -22,6 +23,13 @@ namespace Serein.Script.Node public ASTNode Value { get; } public AssignmentNode(ASTNode target, ASTNode value) => (Target, Value) = (target, value); + + public override string ToString() + { + return $"{Target} = {Value}"; + } + + } diff --git a/Serein.Script/Node/BinaryOperationNode.cs b/Serein.Script/Node/BinaryOperationNode.cs index 902a4bb..636645e 100644 --- a/Serein.Script/Node/BinaryOperationNode.cs +++ b/Serein.Script/Node/BinaryOperationNode.cs @@ -33,5 +33,10 @@ namespace Serein.Script.Node Operator = op; Right = right; } + + public override string ToString() + { + return $"({Left} {Operator} {Right})"; + } } } diff --git a/Serein.Script/Node/ClassTypeDefinitionNode.cs b/Serein.Script/Node/ClassTypeDefinitionNode.cs index ab2e39c..a7b1ab7 100644 --- a/Serein.Script/Node/ClassTypeDefinitionNode.cs +++ b/Serein.Script/Node/ClassTypeDefinitionNode.cs @@ -30,7 +30,11 @@ namespace Serein.Script.Node this.ClassType = className; } - + public override string ToString() + { + var p = string.Join(",", Propertys.Select(p => $"{p.Value}")); + return $"{ClassType}({p})"; + } /* /// diff --git a/Serein.Script/Node/CollectionIndexNode.cs b/Serein.Script/Node/CollectionIndexNode.cs index b6e9898..3fe318e 100644 --- a/Serein.Script/Node/CollectionIndexNode.cs +++ b/Serein.Script/Node/CollectionIndexNode.cs @@ -26,6 +26,12 @@ namespace Serein.Script.Node this.Collection = Collection; this.Index = indexValue; } + + + public override string ToString() + { + return $"{Collection}[{Index}]"; + } } /// @@ -48,5 +54,10 @@ namespace Serein.Script.Node this.Collection = collection; this.Value = value; } + + public override string ToString() + { + return $"{Collection} = {Value}"; + } } } diff --git a/Serein.Script/Node/CtorAssignmentNode.cs b/Serein.Script/Node/CtorAssignmentNode.cs index b8d8c52..744c9bb 100644 --- a/Serein.Script/Node/CtorAssignmentNode.cs +++ b/Serein.Script/Node/CtorAssignmentNode.cs @@ -41,6 +41,9 @@ namespace Serein.Script.Node Value = value; } - + public override string ToString() + { + return $"ctor {Class}.{MemberName} = {Value}"; + } } } diff --git a/Serein.Script/Node/FunctionCallNode.cs b/Serein.Script/Node/FunctionCallNode.cs index f4cc620..e65090a 100644 --- a/Serein.Script/Node/FunctionCallNode.cs +++ b/Serein.Script/Node/FunctionCallNode.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Text; using System.Threading.Tasks; @@ -26,6 +27,12 @@ namespace Serein.Script.Node FunctionName = functionName; Arguments = arguments; } + + public override string ToString() + { + var p = string.Join(",", Arguments.Select(p => $"{p}")); + return $"{FunctionName}({p})"; + } } } diff --git a/Serein.Script/Node/IdentifierNode.cs b/Serein.Script/Node/IdentifierNode.cs index 4b1ceb5..f8a1e05 100644 --- a/Serein.Script/Node/IdentifierNode.cs +++ b/Serein.Script/Node/IdentifierNode.cs @@ -16,5 +16,11 @@ namespace Serein.Script.Node /// public string Name { get; } public IdentifierNode(string name) => Name = name; + + + public override string ToString() + { + return $"let {Name}"; + } } } diff --git a/Serein.Script/Node/MemberAccessNode.cs b/Serein.Script/Node/MemberAccessNode.cs index 85ffdeb..f8eb0f4 100644 --- a/Serein.Script/Node/MemberAccessNode.cs +++ b/Serein.Script/Node/MemberAccessNode.cs @@ -26,5 +26,10 @@ namespace Serein.Script.Node Object = obj; MemberName = memberName; } + + public override string ToString() + { + return $"{Object}.{MemberName}"; + } } } diff --git a/Serein.Script/Node/MemberAssignmentNode.cs b/Serein.Script/Node/MemberAssignmentNode.cs index 9e5adf5..e5bd55e 100644 --- a/Serein.Script/Node/MemberAssignmentNode.cs +++ b/Serein.Script/Node/MemberAssignmentNode.cs @@ -31,5 +31,10 @@ namespace Serein.Script.Node MemberName = memberName; Value = value; } + + public override string ToString() + { + return $"{Object}.{MemberName} = {Value}"; + } } } diff --git a/Serein.Script/Node/MemberFunctionCallNode.cs b/Serein.Script/Node/MemberFunctionCallNode.cs index ef21d6a..9d8d949 100644 --- a/Serein.Script/Node/MemberFunctionCallNode.cs +++ b/Serein.Script/Node/MemberFunctionCallNode.cs @@ -32,5 +32,12 @@ namespace Serein.Script.Node FunctionName = functionName; Arguments = arguments; } + + + public override string ToString() + { + var p = string.Join(",", Arguments.Select(p => $"{p}")); + return $"{Object}.{FunctionName}({p})"; + } } } diff --git a/Serein.Script/Node/ObjectInstantiationNode.cs b/Serein.Script/Node/ObjectInstantiationNode.cs index 1ee4dd0..be60685 100644 --- a/Serein.Script/Node/ObjectInstantiationNode.cs +++ b/Serein.Script/Node/ObjectInstantiationNode.cs @@ -37,6 +37,13 @@ namespace Serein.Script.Node CtorAssignments = ctorAssignments; return this; } + + public override string ToString() + { + var arg = string.Join(",", Arguments.Select(p => $"{p}")); + var ctor_arg = string.Join(",", CtorAssignments.Select(p => $"{p}")); + return $"new {Type}({arg}){ctor_arg}"; + } } } diff --git a/Serein.Script/Node/TypeNode.cs b/Serein.Script/Node/TypeNode.cs index bb12ed4..6e1ca96 100644 --- a/Serein.Script/Node/TypeNode.cs +++ b/Serein.Script/Node/TypeNode.cs @@ -18,5 +18,9 @@ namespace Serein.Script.Node TypeName = typeName; } + public override string ToString() + { + return $"[type]{TypeName}"; + } } } diff --git a/Serein.Script/Node/ValueNode/BooleanNode.cs b/Serein.Script/Node/ValueNode/BooleanNode.cs index 1c6695a..c41d133 100644 --- a/Serein.Script/Node/ValueNode/BooleanNode.cs +++ b/Serein.Script/Node/ValueNode/BooleanNode.cs @@ -13,5 +13,10 @@ namespace Serein.Script.Node { public bool Value { get; } public BooleanNode(bool value) => Value = value; + + public override string ToString() + { + return $"{Value}"; + } } } diff --git a/Serein.Script/Node/ValueNode/CharNode.cs b/Serein.Script/Node/ValueNode/CharNode.cs index d6fad31..b717d25 100644 --- a/Serein.Script/Node/ValueNode/CharNode.cs +++ b/Serein.Script/Node/ValueNode/CharNode.cs @@ -13,5 +13,9 @@ namespace Serein.Script.Node { Value = char.Parse(value); } + public override string ToString() + { + return $"'{Value}'"; + } } } diff --git a/Serein.Script/Node/ValueNode/NullNode.cs b/Serein.Script/Node/ValueNode/NullNode.cs index 9806e3b..8d28dae 100644 --- a/Serein.Script/Node/ValueNode/NullNode.cs +++ b/Serein.Script/Node/ValueNode/NullNode.cs @@ -11,5 +11,9 @@ namespace Serein.Script.Node /// public class NullNode : ASTNode { + public override string ToString() + { + return $"Null"; + } } } diff --git a/Serein.Script/Node/ValueNode/NumberNode.cs b/Serein.Script/Node/ValueNode/NumberNode.cs index bdd8942..7ad7162 100644 --- a/Serein.Script/Node/ValueNode/NumberNode.cs +++ b/Serein.Script/Node/ValueNode/NumberNode.cs @@ -13,6 +13,11 @@ namespace Serein.Script.Node { public T Value { get; } public NumberNode(T value) => Value = value; + + public override string ToString() + { + return $"{Value}"; + } } diff --git a/Serein.Script/Node/ValueNode/StringNode.cs b/Serein.Script/Node/ValueNode/StringNode.cs index ebc140a..515df3e 100644 --- a/Serein.Script/Node/ValueNode/StringNode.cs +++ b/Serein.Script/Node/ValueNode/StringNode.cs @@ -55,6 +55,11 @@ namespace Serein.Script.Node } Value = output.ToString(); } + + public override string ToString() + { + return $"\"{Value}\""; + } } diff --git a/Serein.Script/SereinScript.cs b/Serein.Script/SereinScript.cs index b90a7b5..300ecb5 100644 --- a/Serein.Script/SereinScript.cs +++ b/Serein.Script/SereinScript.cs @@ -8,9 +8,6 @@ namespace Serein.Script public class SereinScript { - - - /// /// 类型分析 /// diff --git a/Serein.Script/SereinScriptMethodInfo.cs b/Serein.Script/SereinScriptMethodInfo.cs index 9e321d7..94ec2ae 100644 --- a/Serein.Script/SereinScriptMethodInfo.cs +++ b/Serein.Script/SereinScriptMethodInfo.cs @@ -1,21 +1,53 @@ namespace Serein.Script { + /// + /// 脚本方法信息 + /// public class SereinScriptMethodInfo { + /// + /// 类名 + /// public string ClassName { get; set; } + + /// + /// 方法名 + /// public string MethodName { get; set; } + /// + /// 返回类型 + /// public Type? ReturnType { get; set; } + /// + /// 是否异步 + /// public bool IsAsync { get; set; } + /// + /// 入参参数信息 + /// public List ParamInfos { get; set; } + /// + /// 对应的C#代码 + /// public string CsharpCode { get; set; } + /// + /// 入参信息 + /// public class SereinScriptParamInfo { + /// + /// 入参参数名称 + /// public string ParamName { get; set; } + + /// + /// 入参类型 + /// public Type ParameterType { get; set; } } } diff --git a/Serein.Script/SereinScriptToCsharpScript.cs b/Serein.Script/SereinScriptToCsharpScript.cs index cad4935..9bccff8 100644 --- a/Serein.Script/SereinScriptToCsharpScript.cs +++ b/Serein.Script/SereinScriptToCsharpScript.cs @@ -1,4 +1,5 @@ -using Serein.Script.Node; +using Serein.Library.Utils; +using Serein.Script.Node; using Serein.Script.Node.FlowControl; using System.Text; @@ -88,20 +89,38 @@ namespace Serein.Script Indent(); if(param is null || param.Count == 0) { + // 生成方法签名 AppendLine($"public static {returnContent} {mehtodName}()"); } else { + // 生成方法签名 AppendLine($"public static {returnContent} {mehtodName}({GetMethodParamster(param)})"); } AppendLine( "{"); Indent(); - foreach (var stmt in programNode.Statements) + // 生成变量节点 + var idfNodesTemp = _symbolInfos.Keys.Where(key => key is IdentifierNode) + .OfType().ToList() ; + var idfNodes = (param is null) switch { - ConvertCode(stmt); // 递归遍历 + true => idfNodesTemp.DistinctBy(n => n.Name).ToList(), + false => idfNodesTemp.DistinctBy(n => n.Name).DistinctByCondition(n => !param.ContainsKey(n.Name) ).ToList(), + }; + foreach (var idf in idfNodes) + { + var varName = idf.Name; + var varType = _symbolInfos[idf]; + AppendLine($"global::{varType.FullName} {varName} = default; // 变量"); + } + AppendLine(""); + + // 递归遍历节点生成代码 + foreach (var stmt in programNode.Statements) + { + ConvertCode(stmt); Append(";"); } - if (_symbolInfos[programNode] == typeof(void)) { AppendLine(""); @@ -192,8 +211,8 @@ namespace Serein.Script void ConvertCodeOfIdentifierNode(IdentifierNode identifierNode) { var varName = identifierNode.Name; - - if(_local.TryGetValue(varName, out var type)) + Append(varName); + /*if (_local.TryGetValue(varName, out var type)) { // 定义过,需要使用变量 Append(varName); @@ -211,7 +230,7 @@ namespace Serein.Script { throw new Exception($"加载符号表时,无法匹配 IdentifierNode 节点的类型。 name : {varName}"); } - } + }*/ } ConvertCodeOfIdentifierNode(identifierNode); break; @@ -227,7 +246,6 @@ namespace Serein.Script foreach(var item in ifNOde.TrueBranch) { ConvertCode(item); - //Append(";"); AppendLine(string.Empty); } Unindent(); @@ -235,10 +253,9 @@ namespace Serein.Script AppendLine("else"); AppendLine("{"); Indent(); - foreach (var item in ifNOde.TrueBranch) + foreach (var item in ifNOde.FalseBranch) { ConvertCode(item); - //Append(";"); AppendLine(string.Empty); } Unindent(); diff --git a/Serein.Script/SereinScriptTypeAnalysis.cs b/Serein.Script/SereinScriptTypeAnalysis.cs index fde6898..8292865 100644 --- a/Serein.Script/SereinScriptTypeAnalysis.cs +++ b/Serein.Script/SereinScriptTypeAnalysis.cs @@ -198,7 +198,7 @@ namespace Serein.Script var targetType = Analysis(assignmentNode.Target); var valueType = Analysis (assignmentNode.Value); if (!targetType.IsAssignableFrom(valueType)) - throw new Exception($"索引类型不匹配:需要 {targetType},实际为 {valueType}"); + throw new Exception($"赋值类型不匹配:需要 {targetType},实际为 {valueType}"); NodeSymbolInfos[assignmentNode.Value] = valueType; NodeSymbolInfos[assignmentNode.Target] = valueType; NodeSymbolInfos[assignmentNode] = typeof(void); // 赋值语句不产生类型 diff --git a/Workbench/Node/View/ConditionNodeControl.xaml.cs b/Workbench/Node/View/ConditionNodeControl.xaml.cs index 36bcaae..0e9d46b 100644 --- a/Workbench/Node/View/ConditionNodeControl.xaml.cs +++ b/Workbench/Node/View/ConditionNodeControl.xaml.cs @@ -1,5 +1,6 @@ using Serein.Library.Api; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Node.ViewModel; namespace Serein.Workbench.Node.View diff --git a/Workbench/Node/View/ExpOpNodeControl.xaml.cs b/Workbench/Node/View/ExpOpNodeControl.xaml.cs index b14f140..8112768 100644 --- a/Workbench/Node/View/ExpOpNodeControl.xaml.cs +++ b/Workbench/Node/View/ExpOpNodeControl.xaml.cs @@ -1,5 +1,6 @@ using Serein.Library.Api; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Node.ViewModel; namespace Serein.Workbench.Node.View diff --git a/Workbench/Node/View/FlowCallNodeControl.xaml.cs b/Workbench/Node/View/FlowCallNodeControl.xaml.cs index 3764706..4512c31 100644 --- a/Workbench/Node/View/FlowCallNodeControl.xaml.cs +++ b/Workbench/Node/View/FlowCallNodeControl.xaml.cs @@ -1,6 +1,7 @@ using Serein.Library; using Serein.Library.Api; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Node.ViewModel; using System.Windows; using System.Windows.Controls; diff --git a/Workbench/Node/View/GlobalDataControl.xaml.cs b/Workbench/Node/View/GlobalDataControl.xaml.cs index ab292a3..ed6a5f7 100644 --- a/Workbench/Node/View/GlobalDataControl.xaml.cs +++ b/Workbench/Node/View/GlobalDataControl.xaml.cs @@ -1,5 +1,6 @@ using Serein.Library.Api; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Api; using Serein.Workbench.Node.ViewModel; diff --git a/Workbench/Node/View/NetScriptNodeControl.xaml.cs b/Workbench/Node/View/NetScriptNodeControl.xaml.cs index c2b286b..cef6912 100644 --- a/Workbench/Node/View/NetScriptNodeControl.xaml.cs +++ b/Workbench/Node/View/NetScriptNodeControl.xaml.cs @@ -1,5 +1,6 @@ using Serein.Library.Api; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Node.ViewModel; using System; using System.Collections.Generic; @@ -27,8 +28,10 @@ namespace Serein.Workbench.Node.View { var env = App.GetService(); - base.ViewModel = new NetScriptNodeControlViewModel(new SingleNetScriptNode(env)); - base.ViewModel.IsEnabledOnView = false; + base.ViewModel = new NetScriptNodeControlViewModel(new SingleNetScriptNode(env)) + { + IsEnabledOnView = false + }; base.DataContext = ViewModel; InitializeComponent(); } diff --git a/Workbench/Node/View/ScriptNodeControl.xaml.cs b/Workbench/Node/View/ScriptNodeControl.xaml.cs index f6b0e0a..e542502 100644 --- a/Workbench/Node/View/ScriptNodeControl.xaml.cs +++ b/Workbench/Node/View/ScriptNodeControl.xaml.cs @@ -1,5 +1,6 @@ using Serein.Library.Api; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Node.ViewModel; using System; using System.Collections.Generic; diff --git a/Workbench/Node/ViewModel/ActionNodeControlViewModel.cs b/Workbench/Node/ViewModel/ActionNodeControlViewModel.cs index 8325690..2dbec20 100644 --- a/Workbench/Node/ViewModel/ActionNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/ActionNodeControlViewModel.cs @@ -1,4 +1,5 @@ using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Node.View; namespace Serein.Workbench.Node.ViewModel diff --git a/Workbench/Node/ViewModel/ConditionNodeControlViewModel.cs b/Workbench/Node/ViewModel/ConditionNodeControlViewModel.cs index 90bec05..0a545a0 100644 --- a/Workbench/Node/ViewModel/ConditionNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/ConditionNodeControlViewModel.cs @@ -1,4 +1,5 @@ using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Node.View; namespace Serein.Workbench.Node.ViewModel diff --git a/Workbench/Node/ViewModel/ExpOpNodeControlViewModel.cs b/Workbench/Node/ViewModel/ExpOpNodeControlViewModel.cs index d9aaa08..db4af16 100644 --- a/Workbench/Node/ViewModel/ExpOpNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/ExpOpNodeControlViewModel.cs @@ -1,4 +1,5 @@ using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Node.View; namespace Serein.Workbench.Node.ViewModel diff --git a/Workbench/Node/ViewModel/FlipflopNodeControlViewModel.cs b/Workbench/Node/ViewModel/FlipflopNodeControlViewModel.cs index 3ab4e2e..d7829e3 100644 --- a/Workbench/Node/ViewModel/FlipflopNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/FlipflopNodeControlViewModel.cs @@ -1,4 +1,5 @@ using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Node.View; namespace Serein.Workbench.Node.ViewModel diff --git a/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs b/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs index b3df1b6..ef5ed3c 100644 --- a/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs @@ -2,6 +2,7 @@ using Serein.Library; using Serein.Library.Api; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Api; using Serein.Workbench.Services; using Serein.Workbench.ViewModels; diff --git a/Workbench/Node/ViewModel/GlobalDataNodeControlViewModel.cs b/Workbench/Node/ViewModel/GlobalDataNodeControlViewModel.cs index a292b23..8bc19da 100644 --- a/Workbench/Node/ViewModel/GlobalDataNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/GlobalDataNodeControlViewModel.cs @@ -1,4 +1,5 @@ using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using System.Windows; using System.Windows.Input; diff --git a/Workbench/Node/ViewModel/NetScriptNodeControlViewModel.cs b/Workbench/Node/ViewModel/NetScriptNodeControlViewModel.cs index 3efce9e..f18b1f9 100644 --- a/Workbench/Node/ViewModel/NetScriptNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/NetScriptNodeControlViewModel.cs @@ -1,5 +1,6 @@ using Serein.NodeFlow.Model; using Serein.NodeFlow.Model.Library; +using Serein.NodeFlow.Model.Nodes; using Serein.Workbench.Themes; using System.Windows.Input; @@ -61,6 +62,7 @@ public class FlowLibrary // nodeModel.Env.WriteLine(InfoType.ERROR, ex.ToString()); //} }); + NodeModel1 = nodeModel; } private static void OnCompileComplete(FlowLibraryCache flowLibrary) @@ -84,7 +86,6 @@ public class FlowLibrary /// 打开编辑窗口 /// public ICommand CommandOpenScriptEdit { get; } - - + public NodeModelBase NodeModel1 { get; } } } diff --git a/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs b/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs index eb467d7..31ab71a 100644 --- a/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs @@ -1,6 +1,7 @@ using Serein.Library; using Serein.Library.Utils; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/Workbench/Node/ViewModel/UINodeControlViewModel.cs b/Workbench/Node/ViewModel/UINodeControlViewModel.cs index a441e03..7d70ce3 100644 --- a/Workbench/Node/ViewModel/UINodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/UINodeControlViewModel.cs @@ -2,6 +2,7 @@ using Serein.Library; using Serein.Library.Api; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Nodes; using System; using System.Collections.Generic; using System.Linq;