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