mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-02 15:50:47 +08:00
1. 修改了FlowResult的创建方式,新增了IsSuccess与Message,为后续进行流程日志追踪准备。
2. 修复了删除节点时,没有正确消除与之相关的参数获取关系。 3. IFlowNode新增了StartFlowAsync方法。 4. Library项目中FlowNodeExtension拓展类转移到了NodeFlow项目中。 5. 修改了Script自定义参数保存,对参数类型、返回值类型进行保存。 6. Library.Utils.BenchmarkHelpter中添加了Task、Task<>的重载方法。 7. NodeFlow项目中,FlowEnvironment默认使用通过NewtonsoftJson实现的JSON门户类。 8. NodeFlow项目中,FlowControl缓存了 FlowWorkOptions 选项实体,并且对于 FlowWorkManagement 进行了池化管理(但后续的项目中考虑重构流程任务运行时)。
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -120,5 +120,13 @@ namespace Serein.Library.Api
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// 以该节点开始执行流程,通常用于流程的入口节点。
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<FlowResult> StartFlowAsync(IFlowContext context, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ namespace Serein.Library
|
||||
/// <param name="data"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,23 +31,35 @@ namespace Serein.Library
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
/// <param name="context"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 空返回值
|
||||
/// 失败
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
/// <param name="context"></param>
|
||||
public FlowResult(string nodeGuid, IFlowContext context)
|
||||
/// <param name="message"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,16 +84,26 @@ namespace Serein.Library
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指示是否成功
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 执行结果消息(提示异常)
|
||||
/// </summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 来源节点Guid
|
||||
/// </summary>
|
||||
public string SourceNodeGuid{ get; }
|
||||
public string SourceNodeGuid{ get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 来源上下文Guid
|
||||
/// </summary>
|
||||
public string ContextGuid { get; }
|
||||
public string ContextGuid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据值
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Extension\FlowModelExtension.cs" />
|
||||
<Compile Remove="FlowNode\Attribute.cs" />
|
||||
<Compile Remove="FlowNode\NodeModelBaseData.cs" />
|
||||
<Compile Remove="FlowNode\NodeModelBaseFunc.cs" />
|
||||
|
||||
@@ -74,6 +74,36 @@ namespace Serein.Library.Utils
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒");
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒");
|
||||
}
|
||||
/// <summary>
|
||||
/// 运行指定异步方法多次并输出耗时的最大、最小和平均值。
|
||||
/// </summary>
|
||||
/// <param name="task">需要执行的异步方法</param>
|
||||
/// <param name="count">执行次数,默认10000</param>
|
||||
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} 毫秒");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行指定异步方法多次并输出耗时的最大、最小和平均值。
|
||||
@@ -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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 运行指定异步方法多次并输出耗时的最大、最小和平均值。
|
||||
/// </summary>
|
||||
/// <param name="task">需要执行的异步方法</param>
|
||||
/// <param name="count">执行次数,默认10000</param>
|
||||
public static async Task<TReult> BenchmarkAsync<TReult>(Task<TReult> 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} 毫秒:");
|
||||
|
||||
@@ -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<IFlowContext>(() => new FlowContext(flowEnvironment));
|
||||
flowTaskOptions = new FlowWorkOptions
|
||||
{
|
||||
FlowIOC = IOC,
|
||||
Environment = flowEnvironment, // 流程
|
||||
FlowContextPool = contexts, // 上下文对象池
|
||||
};
|
||||
flowTaskManagementPool = new ObjectPool<FlowWorkManagement>(()=> new FlowWorkManagement(flowTaskOptions));
|
||||
}
|
||||
|
||||
private ObjectPool<IFlowContext> contexts;
|
||||
private ObjectPool<FlowWorkManagement> flowTaskManagementPool;
|
||||
private FlowWorkOptions flowTaskOptions;
|
||||
|
||||
|
||||
|
||||
private FlowWorkManagement flowWorkManagement;
|
||||
private ISereinIOC externalIOC;
|
||||
private Action<ISereinIOC> setDefultMemberOnReset;
|
||||
@@ -170,30 +183,64 @@ namespace Serein.NodeFlow.Env
|
||||
/// <inheritdoc/>
|
||||
public async Task<TResult> StartFlowAsync<TResult>(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<string, TimeSpan>();
|
||||
|
||||
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;
|
||||
|
||||
@@ -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<FlowOperationService>() // 流程操作
|
||||
.Register<NodeMVVMService>() // 节点MVVM服务
|
||||
.Build();
|
||||
|
||||
// 设置JSON解析器
|
||||
JsonHelper.UseJsonProvider(new NewtonsoftJsonProvider());
|
||||
// 默认使用本地环境
|
||||
currentFlowEnvironment = ioc.Get<LocalFlowEnvironment>();
|
||||
currentFlowEnvironmentEvent = ioc.Get<IFlowEnvironmentEvent>();
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
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));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
405
NodeFlow/Model/Node/FlowModelExtension.cs
Normal file
405
NodeFlow/Model/Node/FlowModelExtension.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点方法拓展
|
||||
/// </summary>
|
||||
public static class FlowModelExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 导出为画布信息
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从画布信息加载
|
||||
/// </summary>
|
||||
/// <param name="canvasModel"></param>
|
||||
/// <param name="canvasInfo"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 输出方法参数信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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<ParameterData>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出为节点信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从节点信息加载节点
|
||||
/// </summary>
|
||||
/// <param name="nodeModel"></param>
|
||||
/// <param name="canvas"></param>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
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<ConnectionArgSourceType>(pdInfo.SourceType);
|
||||
pd.ArgDataSourceNodeGuid = pdInfo.SourceNodeGuid;
|
||||
|
||||
}
|
||||
|
||||
nodeModel.LoadCustomData(nodeInfo); // 加载自定义数据
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 视为流程接口调用
|
||||
/// </summary>
|
||||
/// <param name="flowCallNode"></param>
|
||||
/// <param name="param"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<TResult> ApiInvokeAsync<TResult>(this IFlowNode flowCallNode, Dictionary<string,object> 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}]。");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查监视表达式是否生效
|
||||
/// </summary>
|
||||
/// <param name="nodeModel">节点Moel</param>
|
||||
/// <param name="context">上下文</param>
|
||||
/// <param name="newData">新的数据</param>
|
||||
/// <returns></returns>
|
||||
/*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; // 替换数据
|
||||
}
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// 监视对象表达式中断
|
||||
/// </summary>
|
||||
/// <param name="nodeModel"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="monitorType"></param>
|
||||
/// <returns></returns>
|
||||
/*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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
//}
|
||||
}*/
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 不再中断
|
||||
/// </summary>
|
||||
public static void CancelInterrupt(IFlowNode nodeModel)
|
||||
{
|
||||
nodeModel.DebugSetting.IsInterrupt = false;
|
||||
nodeModel.DebugSetting.CancelInterrupt?.Invoke();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// 程序集更新,更新节点方法描述、以及所有入参描述的类型
|
||||
/// </summary>
|
||||
/// <param name="nodeModel">节点Model</param>
|
||||
/// <param name="newMd">新的方法描述</param>
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@ using System.Threading;
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 节点基类(数据)
|
||||
/// </summary>
|
||||
|
||||
@@ -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<Stack<IFlowNode>> flowStackPool = new ObjectPool<Stack<IFlowNode>>(() => new Stack<IFlowNode>());
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token">流程运行</param>
|
||||
/// <returns></returns>
|
||||
public async Task<FlowResult> StartFlowAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
#if false
|
||||
|
||||
/*
|
||||
var sw = Stopwatch.StartNew();
|
||||
var checkpoints = new Dictionary<string, TimeSpan>();
|
||||
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<string, TimeSpan>();
|
||||
sw.Restart();
|
||||
checkpoints.Clear();
|
||||
#endif
|
||||
IFlowNode? previousNode = null;
|
||||
IFlowNode? currentNode = null;
|
||||
FlowResult? flowResult = null;
|
||||
|
||||
IFlowNode nodeModel = this;
|
||||
Stack<IFlowNode> flowStack = flowStackPool.Allocate();
|
||||
flowStack.Push(nodeModel);
|
||||
//HashSet<IFlowNode> 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有参数
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<object[]> 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<object>[] mainArgTasks = new Task<object>[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<object>[] paramTasks = new Task<object>[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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Serein.NodeFlow.Model.Nodes
|
||||
|
||||
public override async Task<FlowResult> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -120,32 +120,28 @@ namespace Serein.NodeFlow.Model.Nodes
|
||||
/// <returns></returns>
|
||||
public override async Task<FlowResult> 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}。");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存项目时保存脚本代码
|
||||
/// 保存项目时保存脚本代码、方法入参类型、返回值类型
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
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; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 加载自定义数据
|
||||
/// </summary>
|
||||
@@ -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<ScriptArgInfo[]>(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; // 重置脚本改变标志
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载脚本代码
|
||||
/// </summary>
|
||||
@@ -261,7 +309,7 @@ namespace Serein.NodeFlow.Model.Nodes
|
||||
/// <returns></returns>
|
||||
public async Task<FlowResult> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Serein.NodeFlow.Model.Nodes
|
||||
|
||||
public override async Task<FlowResult> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, // 画布
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Serein.Extend.NewtonsoftJson\Serein.Extend.NewtonsoftJson.csproj" />
|
||||
<ProjectReference Include="..\Serein.Library.MyGenerator\Serein.Library.NodeGenerator.csproj" OutputItemType="Analyzer" />
|
||||
|
||||
<ProjectReference Include="..\Library\Serein.Library.csproj" />
|
||||
|
||||
@@ -290,7 +290,7 @@ namespace Serein.NodeFlow.Services
|
||||
/// <returns></returns>
|
||||
public List<FlowLibraryInfo> 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
|
||||
|
||||
|
||||
@@ -274,47 +274,18 @@ namespace Serein.NodeFlow.Services
|
||||
/// <returns></returns>
|
||||
public async Task<FlowResult> StartFlowInSelectNodeAsync(IFlowNode startNode)
|
||||
{
|
||||
var pool = WorkOptions.FlowContextPool;
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var checkpoints = new Dictionary<string, TimeSpan>();
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user