From 48289dae1105bd98a1da2785dc89936855089e8d Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Wed, 30 Jul 2025 11:29:12 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E4=BF=AE=E6=94=B9=E4=BA=86FlowResult?= =?UTF-8?q?=E7=9A=84=E5=88=9B=E5=BB=BA=E6=96=B9=E5=BC=8F=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=BA=86IsSuccess=E4=B8=8EMessage=EF=BC=8C=E4=B8=BA?= =?UTF-8?q?=E5=90=8E=E7=BB=AD=E8=BF=9B=E8=A1=8C=E6=B5=81=E7=A8=8B=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=BF=BD=E8=B8=AA=E5=87=86=E5=A4=87=E3=80=82=202.=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E5=88=A0=E9=99=A4=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E6=B2=A1=E6=9C=89=E6=AD=A3=E7=A1=AE=E6=B6=88?= =?UTF-8?q?=E9=99=A4=E4=B8=8E=E4=B9=8B=E7=9B=B8=E5=85=B3=E7=9A=84=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E8=8E=B7=E5=8F=96=E5=85=B3=E7=B3=BB=E3=80=82=203.=20I?= =?UTF-8?q?FlowNode=E6=96=B0=E5=A2=9E=E4=BA=86StartFlowAsync=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E3=80=82=204.=20Library=E9=A1=B9=E7=9B=AE=E4=B8=ADFlo?= =?UTF-8?q?wNodeExtension=E6=8B=93=E5=B1=95=E7=B1=BB=E8=BD=AC=E7=A7=BB?= =?UTF-8?q?=E5=88=B0=E4=BA=86NodeFlow=E9=A1=B9=E7=9B=AE=E4=B8=AD=E3=80=82?= =?UTF-8?q?=205.=20=20=E4=BF=AE=E6=94=B9=E4=BA=86Script=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=8F=82=E6=95=B0=E4=BF=9D=E5=AD=98=EF=BC=8C=E5=AF=B9?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E7=B1=BB=E5=9E=8B=E3=80=81=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=80=BC=E7=B1=BB=E5=9E=8B=E8=BF=9B=E8=A1=8C=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E3=80=82=206.=20Library.Utils.BenchmarkHelpter=E4=B8=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86Task=E3=80=81Task<>=E7=9A=84?= =?UTF-8?q?=E9=87=8D=E8=BD=BD=E6=96=B9=E6=B3=95=E3=80=82=207.=20NodeFlow?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E4=B8=AD=EF=BC=8CFlowEnvironment=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E4=BD=BF=E7=94=A8=E9=80=9A=E8=BF=87NewtonsoftJson?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=9A=84JSON=E9=97=A8=E6=88=B7=E7=B1=BB?= =?UTF-8?q?=E3=80=82=208.=20NodeFlow=E9=A1=B9=E7=9B=AE=E4=B8=AD=EF=BC=8CFl?= =?UTF-8?q?owControl=E7=BC=93=E5=AD=98=E4=BA=86=20FlowWorkOptions=20?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E5=AE=9E=E4=BD=93=EF=BC=8C=E5=B9=B6=E4=B8=94?= =?UTF-8?q?=E5=AF=B9=E4=BA=8E=20FlowWorkManagement=20=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E4=BA=86=E6=B1=A0=E5=8C=96=E7=AE=A1=E7=90=86=EF=BC=88=E4=BD=86?= =?UTF-8?q?=E5=90=8E=E7=BB=AD=E7=9A=84=E9=A1=B9=E7=9B=AE=E4=B8=AD=E8=80=83?= =?UTF-8?q?=E8=99=91=E9=87=8D=E6=9E=84=E6=B5=81=E7=A8=8B=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=97=B6=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Api/IFlowContext.cs | 3 +- Library/Api/IFlowNode.cs | 8 + Library/FlowNode/FlowContext.cs | 2 +- Library/FlowNode/FlowResult.cs | 44 +- .../FlowNode/LightweightFlowEnvironment.cs | 2 +- Library/Serein.Library.csproj | 1 + Library/Utils/BenchmarkHelpers.cs | 62 +++ NodeFlow/Env/FlowControl.cs | 83 +++- NodeFlow/Env/FlowEnvironment.cs | 6 +- NodeFlow/Env/LocalFlowEnvironment.cs | 14 +- NodeFlow/Model/Node/FlowModelExtension.cs | 405 ++++++++++++++++++ NodeFlow/Model/Node/NodeModelBaseData.cs | 2 - NodeFlow/Model/Node/NodeModelBaseFunc.cs | 285 +++++++++++- NodeFlow/Model/Node/SingleConditionNode.cs | 4 +- NodeFlow/Model/Node/SingleExpOpNode.cs | 6 +- NodeFlow/Model/Node/SingleFlipflopNode.cs | 2 +- NodeFlow/Model/Node/SingleGlobalDataNode.cs | 20 +- NodeFlow/Model/Node/SingleScriptNode.cs | 62 ++- NodeFlow/Model/Node/SingleUINode.cs | 4 +- .../ChangeNodeConnectionOperation.cs | 4 +- .../Model/Operation/CreateCanvasOperation.cs | 1 + .../Model/Operation/RemoveCanvasOperation.cs | 1 + .../Model/Operation/RemoveNodeOperation.cs | 5 + NodeFlow/Serein.NodeFlow.csproj | 1 + NodeFlow/Services/FlowLibraryService.cs | 2 +- NodeFlow/Services/FlowWorkManagement.cs | 37 +- Workbench/Services/FlowNodeService.cs | 5 +- 27 files changed, 965 insertions(+), 106 deletions(-) create mode 100644 NodeFlow/Model/Node/FlowModelExtension.cs diff --git a/Library/Api/IFlowContext.cs b/Library/Api/IFlowContext.cs index fe35e20..2645b0a 100644 --- a/Library/Api/IFlowContext.cs +++ b/Library/Api/IFlowContext.cs @@ -265,7 +265,8 @@ namespace Serein.Library.Api } else { - Result = value.ToString(); + var type = value.GetType(); + Result = $"{type.FullName}::{value}"; } } public void UploadParameters(object[] values = null) diff --git a/Library/Api/IFlowNode.cs b/Library/Api/IFlowNode.cs index 69dcef7..9987c12 100644 --- a/Library/Api/IFlowNode.cs +++ b/Library/Api/IFlowNode.cs @@ -120,5 +120,13 @@ namespace Serein.Library.Api /// /// Task ExecutingAsync(IFlowContext context, CancellationToken token); + + /// + /// 以该节点开始执行流程,通常用于流程的入口节点。 + /// + /// + /// + /// + Task StartFlowAsync(IFlowContext context, CancellationToken token); } } diff --git a/Library/FlowNode/FlowContext.cs b/Library/FlowNode/FlowContext.cs index 1cd41b9..27d599d 100644 --- a/Library/FlowNode/FlowContext.cs +++ b/Library/FlowNode/FlowContext.cs @@ -224,7 +224,7 @@ namespace Serein.Library /// public void AddOrUpdate(string nodeModel, object data) { - var flowData = new FlowResult(nodeModel, this, data); + var flowData = FlowResult.OK(nodeModel, this, data); dictNodeFlowData.AddOrUpdate(nodeModel, _ => flowData, (o, n) => flowData); } diff --git a/Library/FlowNode/FlowResult.cs b/Library/FlowNode/FlowResult.cs index 74eb432..cd6d4ba 100644 --- a/Library/FlowNode/FlowResult.cs +++ b/Library/FlowNode/FlowResult.cs @@ -31,23 +31,35 @@ namespace Serein.Library /// /// /// - public FlowResult(string nodeGuid, IFlowContext context, object value) + public static FlowResult OK(string nodeGuid, IFlowContext context, object value) { - this.SourceNodeGuid = nodeGuid; - this.ContextGuid = context.Guid; - this.Value = value; + FlowResult flowResult = new FlowResult + { + SourceNodeGuid = nodeGuid, + ContextGuid = context.Guid, + Value = value, + IsSuccess = true, + }; + return flowResult; } /// - /// 空返回值 + /// 失败 /// /// /// - public FlowResult(string nodeGuid, IFlowContext context) + /// + public static FlowResult Fail(string nodeGuid, IFlowContext context, string message) { - this.SourceNodeGuid = nodeGuid; - this.ContextGuid = context.Guid; - this.Value = Unit.Default; + FlowResult flowResult = new FlowResult + { + SourceNodeGuid = nodeGuid, + ContextGuid = context.Guid, + Value = Unit.Default, + IsSuccess = true, + Message = message, + }; + return flowResult; } /// @@ -72,16 +84,26 @@ namespace Serein.Library return false; } + /// + /// 指示是否成功 + /// + public bool IsSuccess { get; set; } + /// + /// 执行结果消息(提示异常) + /// + public string Message { get; set; } = string.Empty; /// /// 来源节点Guid /// - public string SourceNodeGuid{ get; } + public string SourceNodeGuid{ get; private set; } + /// /// 来源上下文Guid /// - public string ContextGuid { get; } + public string ContextGuid { get; private set; } + /// /// 数据值 /// diff --git a/Library/FlowNode/LightweightFlowEnvironment.cs b/Library/FlowNode/LightweightFlowEnvironment.cs index ee143e1..21b509d 100644 --- a/Library/FlowNode/LightweightFlowEnvironment.cs +++ b/Library/FlowNode/LightweightFlowEnvironment.cs @@ -300,7 +300,7 @@ namespace Serein.Library } catch (Exception ex) { - flowResult = new FlowResult(currentNode.Guid, context); + flowResult = FlowResult.Fail(currentNode.Guid, context, ex.Message); context.Env.WriteLine(InfoType.ERROR, $"节点[{currentNode}]异常:" + ex); context.NextOrientation = ConnectionInvokeType.IsError; context.ExceptionOfRuning = ex; diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index a9448bb..7e5539e 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -55,6 +55,7 @@ + diff --git a/Library/Utils/BenchmarkHelpers.cs b/Library/Utils/BenchmarkHelpers.cs index 19d3441..f19c43e 100644 --- a/Library/Utils/BenchmarkHelpers.cs +++ b/Library/Utils/BenchmarkHelpers.cs @@ -74,6 +74,36 @@ namespace Serein.Library.Utils SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒"); SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒"); } + /// + /// 运行指定异步方法多次并输出耗时的最大、最小和平均值。 + /// + /// 需要执行的异步方法 + /// 执行次数,默认10000 + public static async Task BenchmarkAsync(Task task, int count = 10000) + { + double max = double.MinValue; + double min = double.MaxValue; + double total = 0; + + for (int i = 0; i < count; i++) + { + var sw = Stopwatch.StartNew(); + await task; + sw.Stop(); + + double ms = sw.Elapsed.TotalMilliseconds; + if (ms > max) max = ms; + if (ms < min) min = ms; + total += ms; + } + + double avg = total / count; + SereinEnv.WriteLine(InfoType.INFO, $"运行 {count} 次:"); + SereinEnv.WriteLine(InfoType.INFO, $"总耗时 :{total} 毫秒:"); + SereinEnv.WriteLine(InfoType.INFO, $"最大耗时:{max} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒"); + } /// /// 运行指定异步方法多次并输出耗时的最大、最小和平均值。 @@ -99,6 +129,38 @@ namespace Serein.Library.Utils //Console.WriteLine($"第{count}次: 耗时 {ms} ms"); } + double avg = total / count; + SereinEnv.WriteLine(InfoType.INFO, $"运行 {count} 次:"); + SereinEnv.WriteLine(InfoType.INFO, $"总耗时 :{total} 毫秒:"); + SereinEnv.WriteLine(InfoType.INFO, $"最大耗时:{max} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒"); + return result; + } + /// + /// 运行指定异步方法多次并输出耗时的最大、最小和平均值。 + /// + /// 需要执行的异步方法 + /// 执行次数,默认10000 + public static async Task BenchmarkAsync(Task task, int count = 10000) + { + double max = double.MinValue; + double min = double.MaxValue; + double total = 0; + TReult result = default; + for (int i = 0; i < count; i++) + { + var sw = Stopwatch.StartNew(); + result = await task; + sw.Stop(); + + double ms = sw.Elapsed.TotalMilliseconds; + if (ms > max) max = ms; + if (ms < min) min = ms; + total += ms; + //Console.WriteLine($"第{count}次: 耗时 {ms} ms"); + } + double avg = total / count; SereinEnv.WriteLine(InfoType.INFO, $"运行 {count} 次:"); SereinEnv.WriteLine(InfoType.INFO, $"总耗时 :{total} 毫秒:"); diff --git a/NodeFlow/Env/FlowControl.cs b/NodeFlow/Env/FlowControl.cs index ebe3eae..886c82d 100644 --- a/NodeFlow/Env/FlowControl.cs +++ b/NodeFlow/Env/FlowControl.cs @@ -7,6 +7,7 @@ using Serein.NodeFlow.Services; using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -38,9 +39,21 @@ namespace Serein.NodeFlow.Env this.UIContextOperation = UIContextOperation; contexts = new ObjectPool(() => new FlowContext(flowEnvironment)); + flowTaskOptions = new FlowWorkOptions + { + FlowIOC = IOC, + Environment = flowEnvironment, // 流程 + FlowContextPool = contexts, // 上下文对象池 + }; + flowTaskManagementPool = new ObjectPool(()=> new FlowWorkManagement(flowTaskOptions)); } private ObjectPool contexts; + private ObjectPool flowTaskManagementPool; + private FlowWorkOptions flowTaskOptions; + + + private FlowWorkManagement flowWorkManagement; private ISereinIOC externalIOC; private Action setDefultMemberOnReset; @@ -170,30 +183,64 @@ namespace Serein.NodeFlow.Env /// public async Task StartFlowAsync(string startNodeGuid) { - var flowTaskOptions = new FlowWorkOptions - { - FlowIOC = IOC, - Environment = flowEnvironment, // 流程 - FlowContextPool = contexts, // 上下文对象池 - }; - var flowTaskManagement = new FlowWorkManagement(flowTaskOptions); - if (!flowModelService.TryGetNodeModel(startNodeGuid, out var nodeModel) || nodeModel is SingleFlipflopNode) + var sw = Stopwatch.StartNew(); + var checkpoints = new Dictionary(); + + var flowTaskManagement = flowTaskManagementPool.Allocate(); + + if (!flowModelService.TryGetNodeModel(startNodeGuid, out IFlowNode? nodeModel)) { - throw new Exception(); + throw new Exception($"节点不存在【{startNodeGuid}】"); + } + if(nodeModel is SingleFlipflopNode) + { + throw new Exception("不能从[Flipflop]节点开始"); } -#if DEBUG - - FlowResult flowResult = await BenchmarkHelpers.BenchmarkAsync(async () => - await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel)); -#else - //FlowResult flowResult = await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel); - FlowResult flowResult = await BenchmarkHelpers.BenchmarkAsync(async () => await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel)); - -#endif + var flowContextPool = flowTaskManagement.WorkOptions.FlowContextPool; + var context = flowContextPool.Allocate(); + checkpoints["准备调用环境"] = sw.Elapsed; + var flowResult = await nodeModel.StartFlowAsync(context, flowTaskManagement.WorkOptions.CancellationTokenSource.Token); // 开始运行时从选定节点开始运行 + 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; + } + //await BenchmarkHelpers.BenchmarkAsync(flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel)); + if (context.IsRecordInvokeInfo) + { + var invokeInfos = context.GetAllInvokeInfos(); + _ = Task.Delay(100).ContinueWith(async (task) => + { + await task; + if (invokeInfos.Count < 255) + { + foreach (var info in invokeInfos) + { + SereinEnv.WriteLine(InfoType.INFO, info.ToString()); + } + } + else + { + double total = 0; + for (int i = 0; i < invokeInfos.Count; i++) + { + total += invokeInfos[i].TS.TotalSeconds; + } + SereinEnv.WriteLine(InfoType.INFO, $"运行次数:{invokeInfos.Count}"); + SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{total / invokeInfos.Count}"); + SereinEnv.WriteLine(InfoType.INFO, $"总耗时:{total}"); + } + }); + } + context.Reset(); + flowContextPool.Free(context); + flowTaskManagementPool.Free(flowTaskManagement); if (flowResult.Value is TResult result) { return result; diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index a5fdd7e..93918fa 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -1,4 +1,5 @@ -using Serein.Library; +using Serein.Extend.NewtonsoftJson; +using Serein.Library; using Serein.Library.Api; using Serein.Library.FlowNode; using Serein.Library.Utils; @@ -62,6 +63,9 @@ namespace Serein.NodeFlow.Env .Register() // 流程操作 .Register() // 节点MVVM服务 .Build(); + + // 设置JSON解析器 + JsonHelper.UseJsonProvider(new NewtonsoftJsonProvider()); // 默认使用本地环境 currentFlowEnvironment = ioc.Get(); currentFlowEnvironmentEvent = ioc.Get(); diff --git a/NodeFlow/Env/LocalFlowEnvironment.cs b/NodeFlow/Env/LocalFlowEnvironment.cs index f312ff4..42e96b6 100644 --- a/NodeFlow/Env/LocalFlowEnvironment.cs +++ b/NodeFlow/Env/LocalFlowEnvironment.cs @@ -1,8 +1,10 @@ using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; +using Serein.NodeFlow.Model.Nodes; using Serein.NodeFlow.Services; using Serein.NodeFlow.Tool; +using System; using System.Text; namespace Serein.NodeFlow.Env @@ -269,8 +271,16 @@ namespace Serein.NodeFlow.Env /// public void SaveProject() { - var project = GetProjectInfoAsync().GetAwaiter().GetResult(); - Event.OnProjectSaving(new ProjectSavingEventArgs(project)); + Task.Run(async () => + { + var project = await GetProjectInfoAsync(); + + await SereinEnv.TriggerEvent(() => + { + Event.OnProjectSaving(new ProjectSavingEventArgs(project)); + }); + }); + } /// diff --git a/NodeFlow/Model/Node/FlowModelExtension.cs b/NodeFlow/Model/Node/FlowModelExtension.cs new file mode 100644 index 0000000..0b545a1 --- /dev/null +++ b/NodeFlow/Model/Node/FlowModelExtension.cs @@ -0,0 +1,405 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Nodes +{ + /// + /// 节点方法拓展 + /// + public static class FlowModelExtension + { + /// + /// 导出为画布信息 + /// + /// + /// + public static FlowCanvasDetailsInfo ToInfo(this FlowCanvasDetails model) + { + return new FlowCanvasDetailsInfo + { + Guid = model.Guid, + Height = model.Height, + Width = model.Width, + Name = model.Name, + ScaleX = model.ScaleX, + ScaleY = model.ScaleY, + ViewX = model.ViewX, + ViewY = model.ViewY, + StartNode = model.StartNode?.Guid, + }; + } + + /// + /// 从画布信息加载 + /// + /// + /// + public static void LoadInfo(this FlowCanvasDetails canvasModel, FlowCanvasDetailsInfo canvasInfo) + { + canvasModel.Guid = canvasInfo.Guid; + canvasModel.Height = canvasInfo.Height; + canvasModel.Width = canvasInfo.Width; + canvasModel.Name = canvasInfo.Name; + canvasModel.ScaleX = canvasInfo.ScaleX; + canvasModel.ScaleY = canvasInfo.ScaleY; + canvasModel.ViewX = canvasInfo.ViewX; + canvasModel.ViewY = canvasInfo.ViewY; + if(canvasModel.Env.TryGetNodeModel(canvasInfo.StartNode,out var nodeModel)) + { + canvasModel.StartNode = nodeModel; + } + } + + /// + /// 输出方法参数信息 + /// + /// + public static ParameterData[] SaveParameterInfo(this IFlowNode nodeModel) + { + if (nodeModel.MethodDetails is null || nodeModel.MethodDetails.ParameterDetailss == null) + { + return new ParameterData[0]; + } + + if (nodeModel.MethodDetails.ParameterDetailss.Length > 0) + { + return nodeModel.MethodDetails.ParameterDetailss + .Select(it => new ParameterData + { + SourceNodeGuid = it.ArgDataSourceNodeGuid, + SourceType = it.ArgDataSourceType.ToString(), + State = it.IsExplicitData, + ArgName = it.Name, + Value = it.DataValue, + + }) + .ToArray(); + } + else + { + return Array.Empty(); + } + } + + /// + /// 导出为节点信息 + /// + /// + public static NodeInfo ToInfo(this IFlowNode nodeModel) + { + // if (MethodDetails == null) return null; + /*var trueNodes = nodeModel.SuccessorNodes[ConnectionInvokeType.IsSucceed].Select(item => item.Guid); // 真分支 + var falseNodes = nodeModel.SuccessorNodes[ConnectionInvokeType.IsFail].Select(item => item.Guid);// 假分支 + var errorNodes = nodeModel.SuccessorNodes[ConnectionInvokeType.IsError].Select(item => item.Guid);// 异常分支 + var upstreamNodes = nodeModel.SuccessorNodes[ConnectionInvokeType.Upstream].Select(item => item.Guid);// 上游分支*/ + + var successorNodes = nodeModel.SuccessorNodes.ToDictionary(kv => kv.Key, kv => kv.Value.Select(item => item.Guid).ToArray()); // 后继分支 + var previousNodes = nodeModel.PreviousNodes.ToDictionary(kv => kv.Key, kv => kv.Value.Select(item => item.Guid).ToArray()); // 后继分支 + + + // 生成参数列表 + ParameterData[] parameterDatas = nodeModel.SaveParameterInfo(); + + var nodeInfo = new NodeInfo + { + CanvasGuid = nodeModel.CanvasDetails.Guid, + Guid = nodeModel.Guid, + IsPublic = nodeModel.IsPublic, + AssemblyName = nodeModel.MethodDetails.AssemblyName, + MethodName = nodeModel.MethodDetails?.MethodName, + Label = nodeModel.MethodDetails?.MethodAnotherName, + Type = nodeModel.ControlType.ToString(), //this.GetType().ToString(), + /*TrueNodes = trueNodes.ToArray(), + FalseNodes = falseNodes.ToArray(), + UpstreamNodes = upstreamNodes.ToArray(), + ErrorNodes = errorNodes.ToArray(),*/ + ParameterData = parameterDatas, + Position = nodeModel.Position, + IsProtectionParameter = nodeModel.DebugSetting.IsProtectionParameter, + IsInterrupt = nodeModel.DebugSetting.IsInterrupt, + IsEnable = nodeModel.DebugSetting.IsEnable, + ParentNodeGuid = nodeModel.ContainerNode?.Guid, + ChildNodeGuids = nodeModel.ChildrenNode.Select(item => item.Guid).ToArray(), + SuccessorNodes = successorNodes, + PreviousNodes = previousNodes, + }; + nodeInfo.Position.X = Math.Round(nodeInfo.Position.X, 1); + nodeInfo.Position.Y = Math.Round(nodeInfo.Position.Y, 1); + nodeInfo = nodeModel.SaveCustomData(nodeInfo); + return nodeInfo; + } + + /// + /// 从节点信息加载节点 + /// + /// + /// + /// + /// + public static void LoadInfo(this IFlowNode nodeModel, NodeInfo nodeInfo) + { + nodeModel.Guid = nodeInfo.Guid; + nodeModel.Position = nodeInfo.Position ?? new PositionOfUI(0, 0);// 加载位置信息 + var md = nodeModel.MethodDetails; // 当前节点的方法说明 + nodeModel.DebugSetting.IsProtectionParameter = nodeInfo.IsProtectionParameter; // 保护参数 + nodeModel.DebugSetting.IsInterrupt = nodeInfo.IsInterrupt; // 是否中断 + nodeModel.DebugSetting.IsEnable = nodeInfo.IsEnable; // 是否使能 + nodeModel.IsPublic = nodeInfo.IsPublic; // 是否全局公开 + if (md != null) + { + if (md.ParameterDetailss == null) + { + md.ParameterDetailss = new ParameterDetails[0]; + } + + var pds = md.ParameterDetailss; // 当前节点的入参描述数组 + #region 类库方法型节点加载参数 + if (nodeInfo.ParameterData.Length > pds.Length && md.HasParamsArg) + { + // 保存的参数信息项数量大于方法本身的方法入参数量(可能存在可变入参) + var length = nodeInfo.ParameterData.Length - pds.Length; // 需要扩容的长度 + nodeModel.MethodDetails.ParameterDetailss = ArrayHelper.Expansion(pds, length); // 扩容入参描述数组 + pds = md.ParameterDetailss; // 当前节点的入参描述数组 + var startParmsPd = pds[md.ParamsArgIndex]; // 获取可变入参参数描述 + for (int i = md.ParamsArgIndex + 1; i <= md.ParamsArgIndex + length; i++) + { + pds[i] = startParmsPd.CloneOfModel(nodeModel); + pds[i].Index = pds[i - 1].Index + 1; + pds[i].IsParams = true; + } + } + + for (int i = 0; i < nodeInfo.ParameterData.Length; i++) + { + if (i >= pds.Length && nodeModel.ControlType != NodeControlType.FlowCall) + { + nodeModel.Env.WriteLine(InfoType.ERROR, $"保存的参数数量大于方法此时的入参参数数量:[{nodeInfo.Guid}][{nodeInfo.MethodName}]"); + break; + } + var pd = pds[i]; + ParameterData pdInfo = nodeInfo.ParameterData[i]; + pd.IsExplicitData = pdInfo.State; + pd.DataValue = pdInfo.Value; + pd.ArgDataSourceType = EnumHelper.ConvertEnum(pdInfo.SourceType); + pd.ArgDataSourceNodeGuid = pdInfo.SourceNodeGuid; + + } + + nodeModel.LoadCustomData(nodeInfo); // 加载自定义数据 + + #endregion + } + } + + + + /// + /// 视为流程接口调用 + /// + /// + /// + /// + public static async Task ApiInvokeAsync(this IFlowNode flowCallNode, Dictionary param) + { + var pds = flowCallNode.MethodDetails.ParameterDetailss; + if (param.Keys.Count != pds.Length) + { + throw new ArgumentNullException($"参数数量不一致。传入参数数量:{param.Keys.Count}。接口入参数量:{pds.Length}。"); + } + + var context = new FlowContext(flowCallNode.Env); + + for (int index = 0; index < pds.Length; index++) + { + ParameterDetails pd = pds[index]; + if (param.TryGetValue(pd.Name, out var value)) + { + context.SetParamsTempData(flowCallNode.Guid, index, value); // 设置入参参数 + } + } + var cts = new CancellationTokenSource(); + var flowResult = await flowCallNode.StartFlowAsync(context, cts.Token); + cts?.Cancel(); + cts?.Dispose(); + context.Exit(); + if (flowResult.Value is TResult result) + { + return result; + } + else if (flowResult is FlowResult && flowResult is TResult result2) + { + return result2; + } + else + { + throw new ArgumentNullException($"类型转换失败,流程返回数据与泛型不匹配,当前返回类型为[{flowResult.Value.GetType().FullName}]。"); + } + } + + /// + /// 检查监视表达式是否生效 + /// + /// 节点Moel + /// 上下文 + /// 新的数据 + /// + /*public static async Task CheckExpInterrupt(this NodeModelBase nodeModel, IDynamicContext context, object newData = null) + { + string guid = nodeModel.Guid; + context.AddOrUpdate(guid, newData); // 上下文中更新数据 + if (newData is null) + { + } + else + { + await nodeModel.MonitorObjExpInterrupt(context, newData, 0); // 首先监视对象 + await nodeModel.MonitorObjExpInterrupt(context, newData, 1); // 然后监视节点 + //nodeModel.FlowData = newData; // 替换数据 + } + }*/ + + /// + /// 监视对象表达式中断 + /// + /// + /// + /// + /// + /// + /*private static async Task MonitorObjExpInterrupt(this NodeModelBase nodeModel, IDynamicContext context, object data, int monitorType) + { + MonitorObjectEventArgs.ObjSourceType sourceType; + string key; + if (monitorType == 0) + { + key = data?.GetType()?.FullName; + sourceType = MonitorObjectEventArgs.ObjSourceType.IOCObj; + } + else + { + key = nodeModel.Guid; + sourceType = MonitorObjectEventArgs.ObjSourceType.IOCObj; + } + if (string.IsNullOrEmpty(key)) + { + return; + } + //(var isMonitor, var exps) = await context.Env.CheckObjMonitorStateAsync(key); + //if (isMonitor) // 如果新的数据处于查看状态,通知UI进行更新?交给运行环境判断? + //{ + // context.Env.MonitorObjectNotification(nodeModel.Guid, data, sourceType); // 对象处于监视状态,通知UI更新数据显示 + // if (exps.Length > 0) + // { + // // 表达式环境下判断是否需要执行中断 + // bool isExpInterrupt = false; + // string exp = ""; + // // 判断执行监视表达式,直到为 true 时退出 + // for (int i = 0; i < exps.Length && !isExpInterrupt; i++) + // { + // exp = exps[i]; + // if (string.IsNullOrEmpty(exp)) continue; + // // isExpInterrupt = SereinConditionParser.To(data, exp); + // } + + // if (isExpInterrupt) // 触发中断 + // { + // nodeModel.DebugSetting.IsInterrupt = true; + // if (await context.Env.SetNodeInterruptAsync(nodeModel.Guid,true)) + // { + // context.Env.TriggerInterrupt(nodeModel.Guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Exp); + // var cancelType = await nodeModel.DebugSetting.GetInterruptTask(); + // await Console.Out.WriteLineAsync($"[{data}]中断已{cancelType},开始执行后继分支"); + // nodeModel.DebugSetting.IsInterrupt = false; + // } + // } + // } + + //} + }*/ + + + /// + /// 不再中断 + /// + public static void CancelInterrupt(IFlowNode nodeModel) + { + nodeModel.DebugSetting.IsInterrupt = false; + nodeModel.DebugSetting.CancelInterrupt?.Invoke(); + } + +#if DEBUG + /// + /// 程序集更新,更新节点方法描述、以及所有入参描述的类型 + /// + /// 节点Model + /// 新的方法描述 + public static void UploadMethod(this IFlowNode nodeModel, MethodDetails newMd) + { + var thisMd = nodeModel.MethodDetails; + + thisMd.ActingInstanceType = newMd.ActingInstanceType; // 更新方法需要的类型 + + var thisPds = thisMd.ParameterDetailss; + var newPds = newMd.ParameterDetailss; + // 当前存在可变参数,且新的方法也存在可变参数,需要把可变参数的数目与值传递过去 + if (thisMd.HasParamsArg && newMd.HasParamsArg) + { + int paramsLength = thisPds.Length - thisMd.ParamsArgIndex - 1; // 确定扩容长度 + newMd.ParameterDetailss = ArrayHelper.Expansion(newPds, paramsLength);// 为新方法的入参参数描述进行扩容 + newPds = newMd.ParameterDetailss; + int index = newMd.ParamsArgIndex; // 记录 + var templatePd = newPds[newMd.ParamsArgIndex]; // 新的入参模板 + for (int i = thisMd.ParamsArgIndex; i < thisPds.Length; i++) + { + ParameterDetails thisPd = thisPds[i]; + var newPd = templatePd.CloneOfModel(nodeModel); // 复制参数描述 + newPd.Index = i + 1; // 更新索引 + newPd.IsParams = true; + newPd.DataValue = thisPd.DataValue; // 保留参数值 + newPd.ArgDataSourceNodeGuid = thisPd.ArgDataSourceNodeGuid; // 保留参数来源信息 + newPd.ArgDataSourceType = thisPd.ArgDataSourceType; // 保留参数来源信息 + newPd.IsParams = thisPd.IsParams; // 保留显式参数设置 + newPds[index++] = newPd; + } + } + + + var thidPdLength = thisMd.HasParamsArg ? thisMd.ParamsArgIndex : thisPds.Length; + // 遍历当前的参数描述(不包含可变参数),找到匹配项,复制必要的数据进行保留 + for (int i = 0; i < thisPds.Length; i++) + { + ParameterDetails thisPd = thisPds[i]; + var newPd = newPds.FirstOrDefault(t_newPd => !t_newPd.IsParams // 不为可变参数 + && t_newPd.Name.Equals(thisPd.Name, StringComparison.OrdinalIgnoreCase) // 存在相同名称 + && t_newPd.DataType.Name.Equals(thisPd.DataType.Name) // 存在相同入参类型名称(以类型作为区分) + ); + if (newPd != null) // 如果匹配上了 + { + newPd.DataValue = thisPd.DataValue; // 保留参数值 + newPd.ArgDataSourceNodeGuid = thisPd.ArgDataSourceNodeGuid; // 保留参数来源信息 + newPd.ArgDataSourceType = thisPd.ArgDataSourceType; // 保留参数来源信息 + newPd.IsParams = thisPd.IsParams; // 保留显式参数设置 + } + } + thisMd.ReturnType = newMd.ReturnType; + nodeModel.MethodDetails = newMd; + + } +#endif + + + + + + + + + } +} diff --git a/NodeFlow/Model/Node/NodeModelBaseData.cs b/NodeFlow/Model/Node/NodeModelBaseData.cs index f904ce2..00e3a8f 100644 --- a/NodeFlow/Model/Node/NodeModelBaseData.cs +++ b/NodeFlow/Model/Node/NodeModelBaseData.cs @@ -10,8 +10,6 @@ using System.Threading; namespace Serein.NodeFlow.Model.Nodes { - - /// /// 节点基类(数据) /// diff --git a/NodeFlow/Model/Node/NodeModelBaseFunc.cs b/NodeFlow/Model/Node/NodeModelBaseFunc.cs index 9a997bc..17c252e 100644 --- a/NodeFlow/Model/Node/NodeModelBaseFunc.cs +++ b/NodeFlow/Model/Node/NodeModelBaseFunc.cs @@ -1,5 +1,7 @@ using Serein.Library; using Serein.Library.Api; +using Serein.Library.Utils; +using System.Diagnostics; namespace Serein.NodeFlow.Model.Nodes { @@ -72,7 +74,7 @@ namespace Serein.NodeFlow.Model.Nodes { object[] args = await this.GetParametersAsync(context, token); var result = await dd.InvokeAsync(null, args); - var flowReslt = new FlowResult(this.Guid, context, result); + var flowReslt = FlowResult.OK(this.Guid, context, result); return flowReslt; } else @@ -85,7 +87,7 @@ namespace Serein.NodeFlow.Model.Nodes } object[] args = await this.GetParametersAsync(context, token); var result = await dd.InvokeAsync(instance, args); - var flowReslt = new FlowResult(this.Guid, context, result); + var flowReslt = FlowResult.OK(this.Guid, context, result); return flowReslt; } @@ -93,6 +95,285 @@ namespace Serein.NodeFlow.Model.Nodes } + private readonly static ObjectPool> flowStackPool = new ObjectPool>(() => new Stack()); + + /// + /// 开始执行 + /// + /// + /// 流程运行 + /// + public async Task StartFlowAsync(IFlowContext context, CancellationToken token) + { +#if false + + /* + 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(); + sw.Restart(); + checkpoints.Clear(); +#endif + IFlowNode? previousNode = null; + IFlowNode? currentNode = null; + FlowResult? flowResult = null; + + IFlowNode nodeModel = this; + Stack flowStack = flowStackPool.Allocate(); + flowStack.Push(nodeModel); + //HashSet processedNodes = processedNodesPool.Allocate() ; // 用于记录已处理上游节点的节点 +#if false + checkpoints[$"[{nodeModel?.Guid}]\t流程开始准备"] = sw.Elapsed; +#endif + try + { + while (true) + { + + #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 false + checkpoints[$"[{currentNode.Guid}]\t创建调用信息"] = sw.Elapsed; +#endif + + Label_NotRecordInvoke: + context.NextOrientation = ConnectionInvokeType.IsSucceed; // 默认执行成功 + + try + { + flowResult = await currentNode.ExecutingAsync(context, token); + } + catch (Exception ex) + { + flowResult = FlowResult.Fail(currentNode.Guid, context, ex.Message); + context.Env.WriteLine(InfoType.ERROR, $"节点[{currentNode.Guid}]异常:" + ex); + context.NextOrientation = ConnectionInvokeType.IsError; + context.ExceptionOfRuning = ex; + } +#if false + 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 false + checkpoints[$"[{currentNode.Guid}]\t更新调用信息"] = sw.Elapsed; +#endif + + #region 执行完成时更新栈 + context.AddOrUpdateFlowData(currentNode.Guid, flowResult); // 上下文中更新数据 +#if false + 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 false + 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 false + checkpoints[$"[{currentNode.Guid}]\t上游分支推入栈中"] = sw.Elapsed; +#endif + #endregion + + + + + #region 执行完成后检查 + if (flowStack.Count == 0) + { + break; // 说明流程到了终点 + } + + if (context.RunState == RunState.Completion) + { + flowResult = null; + currentNode.Env.WriteLine(InfoType.INFO, $"流程执行到节点[{currentNode.Guid}]时提前结束,未能获取到流程结果。"); + break; // 流程执行完成,返回结果 + } + + if (token.IsCancellationRequested) + { + flowResult = null; + currentNode.Env.WriteLine(InfoType.INFO, "流程执行到节点[{currentNode.Guid}]时被取消,未能获取到流程结果。"); + break; + } + + #endregion + } + } + finally + { + flowStackPool.Free(flowStack); + + } + +#if false + var theTS = sw.Elapsed; + checkpoints[$"[{nodeModel?.Guid}]\t流程完毕"] = theTS; + var last = TimeSpan.Zero; + foreach (var kv in checkpoints) + { + SereinEnv.WriteLine(InfoType.INFO, $"{kv.Key} 耗时: {(kv.Value - last).TotalMilliseconds} ms"); + last = kv.Value; + } + + SereinEnv.WriteLine(InfoType.INFO, $"总耗时:{theTS.TotalSeconds} ms"); + SereinEnv.WriteLine(InfoType.INFO, $"------"); + +#endif + return flowResult; + } + + + + /// + /// 获取所有参数 + /// + /// + /// + /// + public async Task GetParametersAsync(IFlowContext context, CancellationToken token) + { + IFlowNode nodeModel = this; + var md = nodeModel.MethodDetails; + var pds = md.ParameterDetailss; + + if (pds.Length == 0) + return []; + + object[] args; + object[] paramsArgs = null; // 改为强类型数组 object[] + + int paramsArgIndex = md.ParamsArgIndex; + + if (paramsArgIndex >= 0) + { + // 可变参数数量 + int paramsLength = pds.Length - paramsArgIndex; + + // 用 object[] 表示可变参数数组(如果类型固定也可以用 int[] 等) + paramsArgs = new object[paramsLength]; + + // 方法参数中占位,最后一项是 object[] + args = new object[paramsArgIndex + 1]; + args[paramsArgIndex] = paramsArgs; + } + else + { + args = new object[pds.Length]; + } + + // 并发处理常规参数 + Task[] mainArgTasks = new Task[paramsArgIndex >= 0 ? paramsArgIndex : pds.Length]; + + for (int i = 0; i < mainArgTasks.Length; i++) + { + var pd = pds[i]; + mainArgTasks[i] = pd.ToMethodArgData(context); + } + + await Task.WhenAll(mainArgTasks); + + for (int i = 0; i < mainArgTasks.Length; i++) + { + args[i] = mainArgTasks[i].Result; + } + + // 并发处理 params 类型的入参参数 + if (paramsArgs != null) + { + int paramsLength = paramsArgs.Length; + Task[] paramTasks = new Task[paramsLength]; + + for (int i = 0; i < paramsLength; i++) + { + var pd = pds[paramsArgIndex + i]; + paramTasks[i] = pd.ToMethodArgData(context); + } + + await Task.WhenAll(paramTasks); + + for (int i = 0; i < paramsLength; i++) + { + paramsArgs[i] = paramTasks[i].Result; + } + + args[args.Length - 1] = paramsArgs; + } + + return args; + } + } diff --git a/NodeFlow/Model/Node/SingleConditionNode.cs b/NodeFlow/Model/Node/SingleConditionNode.cs index f314449..bebe12c 100644 --- a/NodeFlow/Model/Node/SingleConditionNode.cs +++ b/NodeFlow/Model/Node/SingleConditionNode.cs @@ -111,7 +111,7 @@ namespace Serein.NodeFlow.Model.Nodes { if (token.IsCancellationRequested) { - return new FlowResult(this.Guid, context); + return FlowResult.Fail(this.Guid, context, "流程已通过token取消"); } // 接收上一节点参数or自定义参数内容 object? parameter; @@ -179,7 +179,7 @@ namespace Serein.NodeFlow.Model.Nodes SereinEnv.WriteLine(InfoType.INFO, $"{Expression} -> " + context.NextOrientation); //return result; - return new FlowResult(this.Guid, context, judgmentResult); + return FlowResult.OK(this.Guid, context, parameter); } diff --git a/NodeFlow/Model/Node/SingleExpOpNode.cs b/NodeFlow/Model/Node/SingleExpOpNode.cs index 857b3fc..e513450 100644 --- a/NodeFlow/Model/Node/SingleExpOpNode.cs +++ b/NodeFlow/Model/Node/SingleExpOpNode.cs @@ -90,7 +90,7 @@ namespace Serein.NodeFlow.Model.Nodes public override async Task ExecutingAsync(IFlowContext context, CancellationToken token) { - if(token.IsCancellationRequested) return new FlowResult(this.Guid, context); + if(token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消"); object? parameter = null;// context.TransmissionData(this); // 表达式节点使用上一节点数据 var pd = MethodDetails.ParameterDetailss[0]; @@ -131,13 +131,13 @@ namespace Serein.NodeFlow.Model.Nodes { var result = await GetValueExpressionAsync(context, parameter, Expression); context.NextOrientation = ConnectionInvokeType.IsSucceed; - return new FlowResult(this.Guid, context, result); + return FlowResult.OK(this.Guid, context, result); } catch (Exception ex) { context.NextOrientation = ConnectionInvokeType.IsError; context.ExceptionOfRuning = ex; - return new FlowResult(this.Guid, context); + return FlowResult.Fail(this.Guid, context, ex.Message); } } diff --git a/NodeFlow/Model/Node/SingleFlipflopNode.cs b/NodeFlow/Model/Node/SingleFlipflopNode.cs index 506905d..242878c 100644 --- a/NodeFlow/Model/Node/SingleFlipflopNode.cs +++ b/NodeFlow/Model/Node/SingleFlipflopNode.cs @@ -65,7 +65,7 @@ namespace Serein.NodeFlow.Model.Nodes throw new FlipflopException(MethodDetails.MethodName + "触发器超时触发。Guid" + Guid); } object result = dynamicFlipflopContext.Value; - var flowReslt = new FlowResult(this.Guid, context, result); + var flowReslt = FlowResult.OK(this.Guid, context, result); return flowReslt; } diff --git a/NodeFlow/Model/Node/SingleGlobalDataNode.cs b/NodeFlow/Model/Node/SingleGlobalDataNode.cs index 698a07a..4e7074d 100644 --- a/NodeFlow/Model/Node/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/Node/SingleGlobalDataNode.cs @@ -120,32 +120,28 @@ namespace Serein.NodeFlow.Model.Nodes /// public override async Task ExecutingAsync(IFlowContext context, CancellationToken token) { - if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); + if (token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消"); if (string.IsNullOrEmpty(KeyName)) { context.NextOrientation = ConnectionInvokeType.IsError; - SereinEnv.WriteLine(InfoType.ERROR, $"全局数据的KeyName不能为空[{this.Guid}]"); - return new FlowResult(this.Guid, context); + return FlowResult.Fail(this.Guid, context, $"全局数据的KeyName不能为空[{this.Guid}]"); } if (DataNode is null) { context.NextOrientation = ConnectionInvokeType.IsError; - SereinEnv.WriteLine(InfoType.ERROR, $"全局数据节点没有设置数据来源[{this.Guid}]"); - return new FlowResult(this.Guid, context); + return FlowResult.Fail(this.Guid, context, $"全局数据节点没有设置数据来源[{this.Guid}]"); } - try + + var result = await DataNode.ExecutingAsync(context, token); + if (result.IsSuccess) { - - var result = await DataNode.ExecutingAsync(context, token); SereinEnv.AddOrUpdateFlowGlobalData(KeyName, result.Value); return result; } - catch (Exception ex) + else { - context.NextOrientation = ConnectionInvokeType.IsError; - context.ExceptionOfRuning = ex; - return new FlowResult(this.Guid, context); + return FlowResult.Fail(this.Guid, context, $"全局数据节点[{this.Guid}]执行失败,原因:{result.Message}。"); } } diff --git a/NodeFlow/Model/Node/SingleScriptNode.cs b/NodeFlow/Model/Node/SingleScriptNode.cs index 62915c5..f627226 100644 --- a/NodeFlow/Model/Node/SingleScriptNode.cs +++ b/NodeFlow/Model/Node/SingleScriptNode.cs @@ -1,5 +1,6 @@ using Serein.Library; using Serein.Library.Api; +using Serein.Library.Utils; using Serein.Script; using Serein.Script.Node.FlowControl; using System; @@ -8,6 +9,7 @@ using System.Diagnostics; using System.Dynamic; using System.Linq; using System.Linq.Expressions; +using System.Reactive; using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -105,18 +107,38 @@ namespace Serein.NodeFlow.Model.Nodes } /// - /// 保存项目时保存脚本代码 + /// 保存项目时保存脚本代码、方法入参类型、返回值类型 /// /// /// public override NodeInfo SaveCustomData(NodeInfo nodeInfo) { + var paramsTypeName = MethodDetails.ParameterDetailss.Select(pd => + { + return new ScriptArgInfo + { + Index = pd.Index, + ArgName = pd.Name, + ArgType = pd.DataType.FullName, + }; + }).ToArray(); + dynamic data = new ExpandoObject(); data.Script = Script ?? ""; + data.ParamsTypeName = paramsTypeName; + data.ReturnTypeName = MethodDetails.ReturnType; nodeInfo.CustomData = data; return nodeInfo; } + private class ScriptArgInfo + { + public int Index { get; set; } + public string? ArgName { get; set; } + public string? ArgType { get; set; } + } + + /// /// 加载自定义数据 /// @@ -124,18 +146,44 @@ namespace Serein.NodeFlow.Model.Nodes public override void LoadCustomData(NodeInfo nodeInfo) { this.Script = nodeInfo.CustomData?.Script ?? ""; + + var paramCount = Math.Min(MethodDetails.ParameterDetailss.Length, nodeInfo.ParameterData.Length); // 更新变量名 - for (int i = 0; i < Math.Min(this.MethodDetails.ParameterDetailss.Length, nodeInfo.ParameterData.Length); i++) + for (int i = 0; i < paramCount; i++) { - this.MethodDetails.ParameterDetailss[i].Name = nodeInfo.ParameterData[i].ArgName; + var pd = MethodDetails.ParameterDetailss[i]; + pd.Name = nodeInfo.ParameterData[i].ArgName; + } + + try + { + string paramsTypeNameJson = nodeInfo.CustomData?.ParamsTypeName.ToString() ?? "[]"; + ScriptArgInfo[] array = JsonHelper.Deserialize(paramsTypeNameJson); + + string returnTypeName = nodeInfo.CustomData?.ReturnTypeName ?? typeof(object); + Type?[] argType = array.Select(item => string.IsNullOrWhiteSpace(item.ArgType) ? null : Type.GetType(item.ArgType) ?? typeof(Unit)).ToArray(); + Type? resType = Type.GetType(returnTypeName); + for (int i = 0; i < paramCount; i++) + { + var pd = MethodDetails.ParameterDetailss[i]; + pd.DataType = argType[i]; + } + MethodDetails.ReturnType = resType; + } + catch (Exception ex) + { + SereinEnv.WriteLine(InfoType.ERROR ,$"加载脚本自定义数据类型信息时发生异常:{ex.Message}"); } //ReloadScript();// 加载时重新解析 IsScriptChanged = false; // 重置脚本改变标志 - } + + + + /// /// 重新加载脚本代码 /// @@ -261,7 +309,7 @@ namespace Serein.NodeFlow.Model.Nodes /// public async Task ExecutingAsync(NodeModelBase flowCallNode, IFlowContext context, CancellationToken token) { - if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); + if (token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消"); var @params = await flowCallNode.GetParametersAsync(context, token); IScriptInvokeContext scriptContext = new ScriptInvokeContext(context); @@ -284,11 +332,11 @@ namespace Serein.NodeFlow.Model.Nodes var envEvent = context.Env.Event; envEvent.FlowRunComplete += onFlowStop; // 防止运行后台流程 - if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); + if (token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消"); var result = await sereinScript.InterpreterAsync(scriptContext); // 从入口节点执行 envEvent.FlowRunComplete -= onFlowStop; - return new FlowResult(this.Guid, context, result); + return FlowResult.OK(this.Guid, context, result); } } diff --git a/NodeFlow/Model/Node/SingleUINode.cs b/NodeFlow/Model/Node/SingleUINode.cs index ce6dc83..49f0c7a 100644 --- a/NodeFlow/Model/Node/SingleUINode.cs +++ b/NodeFlow/Model/Node/SingleUINode.cs @@ -17,7 +17,7 @@ namespace Serein.NodeFlow.Model.Nodes public override async Task ExecutingAsync(IFlowContext context, CancellationToken token) { - if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); + if (token.IsCancellationRequested) return FlowResult.Fail(this.Guid, context, "流程已通过token取消"); if(Adapter is null) { @@ -40,7 +40,7 @@ namespace Serein.NodeFlow.Model.Nodes iflowContorl.OnExecuting(data); } - return new FlowResult(this.Guid, context); + return FlowResult.OK(this.Guid, context, null); } } } diff --git a/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs b/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs index 8f07db9..e6a26c1 100644 --- a/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs +++ b/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs @@ -185,7 +185,7 @@ namespace Serein.NodeFlow.Model.Operation return false; } - if (ToNode.ControlType != NodeControlType.GlobalData) + if (ToNode.ControlType is not (NodeControlType.GlobalData or NodeControlType.ExpCondition or NodeControlType.ExpOp)) { if (ToNode.MethodDetails.ParameterDetailss is null) @@ -226,7 +226,7 @@ namespace Serein.NodeFlow.Model.Operation if (!checkTypeState) // 类型检查不通过 { - SereinEnv.WriteLine(InfoType.ERROR, "创建失败,目标节点没有合适的入参接收返回值"); + SereinEnv.WriteLine(InfoType.ERROR, "连接失败,目标节点没有合适的入参接收返回值"); return false; } #endregion diff --git a/NodeFlow/Model/Operation/CreateCanvasOperation.cs b/NodeFlow/Model/Operation/CreateCanvasOperation.cs index 595946f..54bdc4c 100644 --- a/NodeFlow/Model/Operation/CreateCanvasOperation.cs +++ b/NodeFlow/Model/Operation/CreateCanvasOperation.cs @@ -1,5 +1,6 @@ using Serein.Library; using Serein.Library.Api; +using Serein.NodeFlow.Model.Nodes; using System; using System.Collections.Generic; using System.Linq; diff --git a/NodeFlow/Model/Operation/RemoveCanvasOperation.cs b/NodeFlow/Model/Operation/RemoveCanvasOperation.cs index 0d5464b..2b21ce6 100644 --- a/NodeFlow/Model/Operation/RemoveCanvasOperation.cs +++ b/NodeFlow/Model/Operation/RemoveCanvasOperation.cs @@ -1,5 +1,6 @@ using Serein.Library; using Serein.Library.Api; +using Serein.NodeFlow.Model.Nodes; using System; using System.Collections.Generic; using System.Linq; diff --git a/NodeFlow/Model/Operation/RemoveNodeOperation.cs b/NodeFlow/Model/Operation/RemoveNodeOperation.cs index 62cc7ce..e812d87 100644 --- a/NodeFlow/Model/Operation/RemoveNodeOperation.cs +++ b/NodeFlow/Model/Operation/RemoveNodeOperation.cs @@ -1,5 +1,6 @@ using Serein.Library; using Serein.Library.Api; +using System.Reflection.Metadata; namespace Serein.NodeFlow.Model.Operation { @@ -121,6 +122,8 @@ namespace Serein.NodeFlow.Model.Operation foreach (var parameter in pds) { if (!parameter.ArgDataSourceNodeGuid.Equals(flowNode.Guid)) continue; + parameter.ArgDataSourceNodeGuid = string.Empty; + parameter.ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 找到了对应的入参控制点了 var e = new NodeConnectChangeEventArgs( CanvasGuid, // 画布 @@ -145,6 +148,8 @@ namespace Serein.NodeFlow.Model.Operation if (string.IsNullOrWhiteSpace(pd.ArgDataSourceNodeGuid)) continue; if(flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var argSourceNode)) { + pd.ArgDataSourceNodeGuid = string.Empty; + pd.ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 找到了对应的入参控制点了 var e = new NodeConnectChangeEventArgs( CanvasGuid, // 画布 diff --git a/NodeFlow/Serein.NodeFlow.csproj b/NodeFlow/Serein.NodeFlow.csproj index 23071fc..95807ca 100644 --- a/NodeFlow/Serein.NodeFlow.csproj +++ b/NodeFlow/Serein.NodeFlow.csproj @@ -77,6 +77,7 @@ + diff --git a/NodeFlow/Services/FlowLibraryService.cs b/NodeFlow/Services/FlowLibraryService.cs index b99ae75..2dbb313 100644 --- a/NodeFlow/Services/FlowLibraryService.cs +++ b/NodeFlow/Services/FlowLibraryService.cs @@ -290,7 +290,7 @@ namespace Serein.NodeFlow.Services /// public List GetAllLibraryInfo() { - return _flowLibraryCaches.Values.Select(library => library.ToInfo()).ToList(); + return _flowLibraryCaches.Values.Where(lib => lib.FullName != "Serein.Library.dll").Select(library => library.ToInfo()).ToList(); } #endregion diff --git a/NodeFlow/Services/FlowWorkManagement.cs b/NodeFlow/Services/FlowWorkManagement.cs index 650f6a2..e700cd1 100644 --- a/NodeFlow/Services/FlowWorkManagement.cs +++ b/NodeFlow/Services/FlowWorkManagement.cs @@ -274,47 +274,18 @@ namespace Serein.NodeFlow.Services /// public async Task StartFlowInSelectNodeAsync(IFlowNode startNode) { + var pool = WorkOptions.FlowContextPool; + var sw = Stopwatch.StartNew(); var checkpoints = new Dictionary(); - var pool = WorkOptions.FlowContextPool; - var token = WorkOptions.CancellationTokenSource.Token; + var context = pool.Allocate(); checkpoints["准备Context"] = sw.Elapsed; - var result = await startNode.StartFlowAsync(context, token); // 开始运行时从选定节点开始运行 + var result = await startNode.StartFlowAsync(context, WorkOptions.CancellationTokenSource.Token); // 开始运行时从选定节点开始运行 checkpoints["执行流程"] = sw.Elapsed; - - if (context.IsRecordInvokeInfo && false) - { - var invokeInfos = context.GetAllInvokeInfos(); - _ = Task.Delay(100).ContinueWith(async (task) => - { - await task; - if(invokeInfos.Count < 255) - { - foreach (var info in invokeInfos) - { - SereinEnv.WriteLine(InfoType.INFO, info.ToString()); - } - } - else - { - double total = 0; - for (int i = 0; i < invokeInfos.Count; i++) - { - total += invokeInfos[i].TS.TotalSeconds; - } - SereinEnv.WriteLine(InfoType.INFO, $"运行次数:{invokeInfos.Count}"); - SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{total / invokeInfos.Count}"); - SereinEnv.WriteLine(InfoType.INFO, $"总耗时:{total}"); - } - }); - - - } - context.Reset(); checkpoints["重置流程"] = sw.Elapsed; diff --git a/Workbench/Services/FlowNodeService.cs b/Workbench/Services/FlowNodeService.cs index 73949a5..df6b1f7 100644 --- a/Workbench/Services/FlowNodeService.cs +++ b/Workbench/Services/FlowNodeService.cs @@ -3,15 +3,12 @@ using Newtonsoft.Json.Linq; using Serein.Library; using Serein.Library.Api; using Serein.Workbench.Api; -using Serein.Workbench.Node; using Serein.Workbench.Node.View; using Serein.Workbench.Node.ViewModel; using Serein.Workbench.ViewModels; using Serein.Workbench.Views; -using System; -using System.Reactive; using System.Text; -using System.Threading.Tasks; +using Serein.NodeFlow.Model.Nodes; using System.Windows; using System.Windows.Controls; using System.Windows.Media;