mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-02 15:50:47 +08:00
完善节点图的代码生成
This commit is contained in:
@@ -87,7 +87,15 @@ namespace Serein.Library.Api
|
||||
/// </summary>
|
||||
/// <param name="nodeModel"></param>
|
||||
/// <param name="flowData"></param>
|
||||
void AddOrUpdate(string nodeModel, FlowResult flowData);
|
||||
void AddOrUpdateFlowData(string nodeModel, FlowResult flowData);
|
||||
|
||||
/// <summary>
|
||||
/// 添加或更新当前节点的数据
|
||||
/// </summary>
|
||||
/// <param name="nodeModel"></param>
|
||||
/// <param name="data"></param>
|
||||
void AddOrUpdate(string nodeModel, object data);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 重置流程状态(用于对象池回收)
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Serein.Library.Api
|
||||
/// </summary>
|
||||
/// <param name="startNodeGuid"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid);
|
||||
Task<TResult> StartFlowAsync<TResult>(string startNodeGuid);
|
||||
|
||||
/// <summary>
|
||||
/// 结束运行
|
||||
|
||||
@@ -236,7 +236,7 @@ namespace Serein.Library
|
||||
#endregion
|
||||
|
||||
#region 执行完成时更新栈
|
||||
context.AddOrUpdate(currentNode.Guid, flowResult); // 上下文中更新数据
|
||||
context.AddOrUpdateFlowData(currentNode.Guid, flowResult); // 上下文中更新数据
|
||||
|
||||
// 首先将指定类别后继分支的所有节点逆序推入栈中
|
||||
var nextNodes = currentNode.SuccessorNodes[context.NextOrientation];
|
||||
|
||||
@@ -217,12 +217,23 @@ namespace Serein.Library
|
||||
/// </summary>
|
||||
/// <param name="nodeModel">节点</param>
|
||||
/// <param name="flowData">新的数据</param>
|
||||
public void AddOrUpdate(string nodeModel, FlowResult flowData)
|
||||
public void AddOrUpdateFlowData(string nodeModel, FlowResult flowData)
|
||||
{
|
||||
// this.dictNodeFlowData.TryGetValue(nodeGuid, out var oldFlowData);
|
||||
dictNodeFlowData.AddOrUpdate(nodeModel, _ => flowData, (o,n ) => flowData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加或更新当前节点的数据
|
||||
/// </summary>
|
||||
/// <param name="nodeModel"></param>
|
||||
/// <param name="data"></param>
|
||||
public void AddOrUpdate(string nodeModel, object data)
|
||||
{
|
||||
var flowData = new FlowResult(nodeModel, this, data);
|
||||
dictNodeFlowData.AddOrUpdate(nodeModel, _ => flowData, (o, n) => flowData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上一节点数据透传到下一节点
|
||||
/// </summary>
|
||||
|
||||
@@ -1,101 +1,89 @@
|
||||
using Microsoft.VisualBasic;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Microsoft.VisualBasic;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.FlowNode
|
||||
namespace Serein.Library
|
||||
{
|
||||
|
||||
|
||||
/*public class FlowControl
|
||||
/*
|
||||
public class CallNodeLookup : IFlowCallTree
|
||||
{
|
||||
private DynamicFlowTemplate dynamicFlowTemplate;
|
||||
private LightweightEnvironment environment;
|
||||
public FlowControl(DynamicFlowTemplate dynamicFlowTemplate,
|
||||
LightweightEnvironment environment)
|
||||
private static readonly string[] _keys = new[]
|
||||
{
|
||||
var callTree = new CallTree()
|
||||
callTree.SetMainNode("node1_guid");
|
||||
"Start", // 0
|
||||
"Stop", // 1
|
||||
"Reset", // 2
|
||||
"Pause", // 3
|
||||
"Resume", // 4
|
||||
"Check", // 5
|
||||
"Init", // 6
|
||||
"Load", // 7
|
||||
"Save", // 8
|
||||
"Clear" // 9
|
||||
};
|
||||
|
||||
callTree.AddCallNode("node1_guid", (context) => DynamicFlowTemplate.Method(context, ...));
|
||||
callTree.AddCallNode("node2_guid", (context) => DynamicFlowTemplate.Method(context, ...));
|
||||
callTree.AddCallNode("node3_guid", (context) => DynamicFlowTemplate.Method(context, ...));
|
||||
callTree.AddCallNode("node4_guid", (context) => DynamicFlowTemplate.Method(context, ...));
|
||||
private static readonly CallNode[] _values = new CallNode[10];
|
||||
|
||||
callTree["node1_guid"].AddFlowUpstream("node2_guid");
|
||||
callTree["node1_guid"].AddFlowSucceed("node2_guid");
|
||||
callTree["node2_guid"].AddFlowSucceed("node3_guid");
|
||||
callTree["node2_guid"].AddFlowError("node4_guid");
|
||||
|
||||
LightweightEnvironment.LoadProject(callTree);
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
/*public class DynamicFlowTemplate
|
||||
{
|
||||
private readonly class1_type class1_instance;
|
||||
|
||||
public DynamicFlowTemplate(class1_type class_instance){
|
||||
this.class1_instance = class1_instance;
|
||||
static CallNodeLookup()
|
||||
{
|
||||
*//*_values[0] = new CallNode("Start");
|
||||
_values[1] = new CallNode("Stop");
|
||||
_values[2] = new CallNode("Reset");
|
||||
_values[3] = new CallNode("Pause");
|
||||
_values[4] = new CallNode("Resume");
|
||||
_values[5] = new CallNode("Check");
|
||||
_values[6] = new CallNode("Init");
|
||||
_values[7] = new CallNode("Load");
|
||||
_values[8] = new CallNode("Save");
|
||||
_values[9] = new CallNode("Clear");*//*
|
||||
}
|
||||
|
||||
[Description("node1_guid")]
|
||||
[Description("assembly_name.class_name.method_name")]
|
||||
private [methodReturnType / void] NodeMethod_[method_name](IDynamicContext context, type1 param1, type2 param2)
|
||||
// 最小冲突哈希函数(简单示例,固定键集有效)
|
||||
private static int PerfectHash(string key)
|
||||
{
|
||||
class1_instance.method(params);
|
||||
context.SetData("node_guid", null);
|
||||
return key switch
|
||||
{
|
||||
"Start" => 0,
|
||||
"Stop" => 1,
|
||||
"Reset" => 2,
|
||||
"Pause" => 3,
|
||||
"Resume" => 4,
|
||||
"Check" => 5,
|
||||
"Init" => 6,
|
||||
"Load" => 7,
|
||||
"Save" => 8,
|
||||
"Clear" => 9,
|
||||
_ => -1
|
||||
};
|
||||
}
|
||||
|
||||
[Description("node2_guid")]
|
||||
[Description("assembly_name.class_name.method_name")]
|
||||
private [methodReturnType] NodeMethod_[method_name](IDynamicContext context, type1 param1)
|
||||
public CallNode Get(string key)
|
||||
{
|
||||
param1 = context.GetData("node1_guid") as type1;
|
||||
var result = class1_instance.method(params, param1);
|
||||
context.SetData("node2_guid", result);
|
||||
}
|
||||
|
||||
|
||||
[Description("node3_guid")]
|
||||
public [methodReturnType] FlowCall_[method_name](IDynamicContext context, type1 param1, type2 param2)
|
||||
{
|
||||
param1 = context.GetData("node3_guid") as type1;
|
||||
var result = class1_instance.method(params, param1);
|
||||
context.SetData("node3_guid", result);
|
||||
int index = PerfectHash(key);
|
||||
if (index >= 0 && _keys[index] == key)
|
||||
return _values[index];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public class CallTree
|
||||
public class FlowCallTree : IFlowCallTree
|
||||
{
|
||||
public CallTree(string canvasGuid, string startNodeGuid)
|
||||
{
|
||||
_canvas_guid = canvasGuid;
|
||||
_start_node_guid = startNodeGuid;
|
||||
}
|
||||
|
||||
private readonly SortedDictionary<string, CallNode> _callNodes = new SortedDictionary<string,CallNode>();
|
||||
//private readonly Dictionary<string, CallNode> _callNodes = new Dictionary<string,CallNode>();
|
||||
|
||||
private readonly ConcurrentDictionary<string, CallNode> _callNodes = new ConcurrentDictionary<string,CallNode>();
|
||||
private readonly string _start_node_guid;
|
||||
private readonly string _canvas_guid ;
|
||||
public CallNode this[string index]
|
||||
{
|
||||
get
|
||||
@@ -106,45 +94,57 @@ namespace Serein.Library.FlowNode
|
||||
set
|
||||
{
|
||||
// 设置指定索引的值
|
||||
_callNodes.AddOrUpdate(index, value, (key, oldValue) => value);
|
||||
_callNodes.Add(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCallNode(string nodeGuid, Action<IDynamicContext> action)
|
||||
{
|
||||
var node = new CallNode(this, nodeGuid, action);
|
||||
var node = new CallNode(nodeGuid, action);
|
||||
_callNodes[nodeGuid] = node;
|
||||
}
|
||||
public void AddCallNode(string nodeGuid, Func<IDynamicContext, Task> func)
|
||||
{
|
||||
var node = new CallNode(this, nodeGuid, func);
|
||||
var node = new CallNode(nodeGuid, func);
|
||||
_callNodes[nodeGuid] = node;
|
||||
}
|
||||
|
||||
public CallNode Get(string key)
|
||||
{
|
||||
return _callNodes.TryGetValue(key, out CallNode callNode) ? callNode : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public class CallNode
|
||||
{
|
||||
|
||||
private readonly Func<IDynamicContext, Task> func;
|
||||
private readonly CallTree callTree;
|
||||
private readonly Action<IDynamicContext> action;
|
||||
private Func<IDynamicContext, Task> taskFunc;
|
||||
private Action<IDynamicContext> action;
|
||||
|
||||
public CallNode(CallTree callTree, string nodeGuid, Action<IDynamicContext> action)
|
||||
{
|
||||
this.callTree = callTree;
|
||||
Guid = nodeGuid;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public CallNode(CallTree callTree, string nodeGuid, Func<IDynamicContext, Task> func)
|
||||
public CallNode(string nodeGuid)
|
||||
{
|
||||
Guid = nodeGuid;
|
||||
this.func = func;
|
||||
Init();
|
||||
}
|
||||
public CallNode(string nodeGuid, Action<IDynamicContext> action)
|
||||
{
|
||||
Guid = nodeGuid;
|
||||
this.action = action;
|
||||
Init();
|
||||
}
|
||||
|
||||
public CallNode(string nodeGuid, Func<IDynamicContext, Task> func)
|
||||
{
|
||||
Guid = nodeGuid;
|
||||
this.taskFunc = func;
|
||||
Init();
|
||||
}
|
||||
|
||||
|
||||
private void Init()
|
||||
{
|
||||
@@ -157,6 +157,16 @@ namespace Serein.Library.FlowNode
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetAction(Action<IDynamicContext> action)
|
||||
{
|
||||
this.action = action;
|
||||
}
|
||||
public void SetAction(Func<IDynamicContext, Task> taskFunc)
|
||||
{
|
||||
this.taskFunc = taskFunc;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 对应的节点
|
||||
@@ -173,28 +183,30 @@ namespace Serein.Library.FlowNode
|
||||
public Dictionary<ConnectionInvokeType, List<CallNode>> SuccessorNodes { get; private set; }
|
||||
|
||||
|
||||
public CallNode AddChildNodeUpstream(string nodeGuid)
|
||||
public CallNode AddChildNodeUpstream(CallNode callNode)
|
||||
{
|
||||
var connectionInvokeType = ConnectionInvokeType.Upstream;
|
||||
PreviousNodes[connectionInvokeType].Add(callTree[nodeGuid]);
|
||||
SuccessorNodes[connectionInvokeType].Add(callNode);
|
||||
return this;
|
||||
}
|
||||
public CallNode AddChildNodeSucceed(string nodeGuid)
|
||||
public CallNode AddChildNodeSucceed(CallNode callNode)
|
||||
{
|
||||
var connectionInvokeType = ConnectionInvokeType.IsSucceed;
|
||||
PreviousNodes[connectionInvokeType].Add(callTree[nodeGuid]);
|
||||
var connectionInvokeType = ConnectionInvokeType.IsSucceed;
|
||||
SuccessorNodes[connectionInvokeType].Add(callNode);
|
||||
|
||||
return this;
|
||||
}
|
||||
public CallNode AddChildNodeFail(string nodeGuid)
|
||||
public CallNode AddChildNodeFail(CallNode callNode)
|
||||
{
|
||||
var connectionInvokeType = ConnectionInvokeType.IsFail;
|
||||
PreviousNodes[connectionInvokeType].Add(callTree[nodeGuid]);
|
||||
var connectionInvokeType = ConnectionInvokeType.IsFail;
|
||||
SuccessorNodes[connectionInvokeType].Add(callNode);
|
||||
|
||||
return this;
|
||||
}
|
||||
public CallNode AddChildNodeError(string nodeGuid)
|
||||
public CallNode AddChildNodeError(CallNode callNode)
|
||||
{
|
||||
var connectionInvokeType = ConnectionInvokeType.IsError;
|
||||
PreviousNodes[connectionInvokeType].Add(callTree[nodeGuid]);
|
||||
SuccessorNodes[connectionInvokeType].Add(callNode);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -216,9 +228,9 @@ namespace Serein.Library.FlowNode
|
||||
{
|
||||
action(context);
|
||||
}
|
||||
else if (func is not null)
|
||||
else if (taskFunc is not null)
|
||||
{
|
||||
await func(context);
|
||||
await taskFunc(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -226,6 +238,10 @@ namespace Serein.Library.FlowNode
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly DefaultObjectPool<Stack<CallNode>> _stackPool = new DefaultObjectPool<Stack<CallNode>>(new DefaultPooledObjectPolicy<Stack<CallNode>>());
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行
|
||||
/// </summary>
|
||||
@@ -234,10 +250,8 @@ namespace Serein.Library.FlowNode
|
||||
/// <returns></returns>
|
||||
public async Task<FlowResult> StartFlowAsync(IDynamicContext context, CancellationToken token)
|
||||
{
|
||||
Stack<CallNode> stack = new Stack<CallNode>();
|
||||
HashSet<CallNode> processedNodes = new HashSet<CallNode>(); // 用于记录已处理上游节点的节点
|
||||
var stack = _stackPool.Get();
|
||||
stack.Push(this);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
@@ -253,7 +267,6 @@ namespace Serein.Library.FlowNode
|
||||
try
|
||||
{
|
||||
await currentNode.InvokeAsync(context, token);
|
||||
|
||||
if (context.NextOrientation == ConnectionInvokeType.None) // 没有手动设置时,进行自动设置
|
||||
{
|
||||
context.NextOrientation = ConnectionInvokeType.IsSucceed;
|
||||
@@ -269,7 +282,6 @@ namespace Serein.Library.FlowNode
|
||||
#endregion
|
||||
|
||||
#region 执行完成时更新栈
|
||||
context.AddOrUpdate(currentNode.Guid, flowResult); // 上下文中更新数据
|
||||
|
||||
// 首先将指定类别后继分支的所有节点逆序推入栈中
|
||||
var nextNodes = currentNode.SuccessorNodes[context.NextOrientation];
|
||||
@@ -293,35 +305,51 @@ namespace Serein.Library.FlowNode
|
||||
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
_stackPool.Return(stack);
|
||||
flowResult = context.GetFlowData(currentNode.Guid);
|
||||
return flowResult; // 说明流程到了终点
|
||||
}
|
||||
|
||||
if (context.RunState == RunState.Completion)
|
||||
{
|
||||
|
||||
_stackPool.Return(stack);
|
||||
context.Env.WriteLine(InfoType.INFO, $"流程执行到节点[{currentNode.Guid}]时提前结束,将返回当前执行结果。");
|
||||
flowResult = context.GetFlowData(currentNode.Guid); _stackPool.Return(stack);
|
||||
return flowResult; // 流程执行完成,返回结果
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
_stackPool.Return(stack);
|
||||
throw new Exception($"流程执行到节点[{currentNode.Guid}]时被取消,未能获取到流程结果。");
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface IFlowCallTree
|
||||
{
|
||||
CallNode Get(string key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轻量级流程控制器
|
||||
/// </summary>
|
||||
public class LightweightFlowControl : IFlowControl
|
||||
{
|
||||
private readonly Dictionary<string, CallTree> callTree = new Dictionary<string, CallTree>();
|
||||
private readonly IFlowCallTree flowCallTree;
|
||||
private readonly IFlowEnvironment flowEnvironment;
|
||||
|
||||
public LightweightFlowControl()
|
||||
public LightweightFlowControl(IFlowCallTree flowCallTree, IFlowEnvironment flowEnvironment)
|
||||
{
|
||||
this.flowCallTree = flowCallTree;
|
||||
this.flowEnvironment = flowEnvironment;
|
||||
}
|
||||
|
||||
public Task<object> InvokeAsync(string apiGuid, Dictionary<string, object> dict)
|
||||
@@ -334,9 +362,41 @@ namespace Serein.Library.FlowNode
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid)
|
||||
|
||||
//private readonly DefaultObjectPool<IDynamicContext> _stackPool = new DefaultObjectPool<IDynamicContext>(new DynamicContext(this));
|
||||
|
||||
|
||||
public async Task<TResult> StartFlowAsync<TResult>(string startNodeGuid)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
IDynamicContext context = new DynamicContext(flowEnvironment);
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
#if DEBUG
|
||||
|
||||
FlowResult flowResult = await BenchmarkHelpers.BenchmarkAsync(async () =>
|
||||
{
|
||||
var node = flowCallTree.Get(startNodeGuid);
|
||||
var flowResult = await node.StartFlowAsync(context, cts.Token);
|
||||
return flowResult;
|
||||
});
|
||||
#else
|
||||
var node = flowCallTree.Get(startNodeGuid);
|
||||
FlowResult flowResult = await node.StartFlowAsync(context, cts.Token);
|
||||
#endif
|
||||
|
||||
cts?.Cancel();
|
||||
cts?.Dispose();
|
||||
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}]。");
|
||||
}
|
||||
}
|
||||
public async Task<bool> StartFlowAsync(string[] canvasGuids)
|
||||
{
|
||||
@@ -501,6 +561,14 @@ namespace Serein.Library.FlowNode
|
||||
/// </summary>
|
||||
public class LightweightFlowEnvironment : IFlowEnvironment
|
||||
{
|
||||
|
||||
public LightweightFlowEnvironment(IFlowEnvironmentEvent lightweightFlowEnvironmentEvent)
|
||||
{
|
||||
this.Event = lightweightFlowEnvironmentEvent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
@@ -513,7 +581,7 @@ namespace Serein.Library.FlowNode
|
||||
|
||||
public IFlowControl FlowControl => throw new NotImplementedException();
|
||||
|
||||
public IFlowEnvironmentEvent Event => throw new NotImplementedException();
|
||||
public IFlowEnvironmentEvent Event { get; private set; }
|
||||
|
||||
public string EnvName => throw new NotImplementedException();
|
||||
|
||||
|
||||
@@ -82,6 +82,12 @@ namespace Serein.Library
|
||||
[PropertyInfo]
|
||||
private int _paramsArgIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为异步方法(如果为异步方法,则返回值类型为Task或Task<T>)
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private bool _isAsync = false;
|
||||
|
||||
/// <summary>
|
||||
/// 出参类型
|
||||
/// </summary>
|
||||
@@ -269,6 +275,7 @@ namespace Serein.Library
|
||||
ParameterDetailsInfos = this.ParameterDetailss?.Select(p => p.ToInfo()).ToArray(),
|
||||
ReturnTypeFullName = this.ReturnType?.FullName,
|
||||
IsParamsArgIndex = this.ParamsArgIndex,
|
||||
IsAsync = this.IsAsync,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -291,6 +298,7 @@ namespace Serein.Library
|
||||
MethodLockName = this.MethodLockName, // 拷贝
|
||||
ParamsArgIndex = this.ParamsArgIndex, // 拷贝
|
||||
ParameterDetailss = this.ParameterDetailss?.Select(p => p?.CloneOfModel(nodeModel)).ToArray(), // 拷贝属于节点方法的新入参描述
|
||||
IsAsync = this.IsAsync, // 拷贝
|
||||
};
|
||||
|
||||
return md;
|
||||
|
||||
@@ -38,10 +38,15 @@ namespace Serein.Library
|
||||
public ParameterDetailsInfo[] ParameterDetailsInfos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选参数信息
|
||||
/// 可选参数信息(-1表示不存在)
|
||||
/// </summary>
|
||||
public int IsParamsArgIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为异步方法
|
||||
/// </summary>
|
||||
public bool IsAsync{ get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出参类型
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<SatelliteResourceLanguages>no</SatelliteResourceLanguages>
|
||||
|
||||
|
||||
<Optimize>true</Optimize>
|
||||
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>.\obj\g</CompilerGeneratedFilesOutputPath>
|
||||
|
||||
@@ -15,13 +15,13 @@ namespace Serein.Library
|
||||
/// </summary>
|
||||
|
||||
[DynamicFlow(Name ="[基础功能]")]
|
||||
public class SereinBaseFunction
|
||||
public static class SereinBaseFunction
|
||||
{
|
||||
|
||||
|
||||
|
||||
[NodeAction(NodeType.Action, "键值对组装")]
|
||||
private Dictionary<string, object> SereinKvDataCollectionNode(string argName,
|
||||
public static Dictionary<string, object> SereinKvDataCollectionNode(string argName,
|
||||
params object[] value)
|
||||
{
|
||||
|
||||
@@ -36,13 +36,13 @@ namespace Serein.Library
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Action, "数组组装")]
|
||||
private object[] SereinListDataCollectionNode(params object[] value)
|
||||
public static object[] SereinListDataCollectionNode(params object[] value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Action, "输出")]
|
||||
private object[] SereinConsoleNode(params object[] value)
|
||||
public static object[] SereinConsoleNode(params object[] value)
|
||||
{
|
||||
foreach (var item in value)
|
||||
{
|
||||
@@ -52,7 +52,7 @@ namespace Serein.Library
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Action, "逻辑分支")]
|
||||
private object SereinLogicalBranch([NodeParam(IsExplicit = false)]bool @bool,
|
||||
public static object SereinLogicalBranch([NodeParam(IsExplicit = false)]bool @bool,
|
||||
object t_value,
|
||||
object f_value)
|
||||
{
|
||||
@@ -60,7 +60,7 @@ namespace Serein.Library
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Action, "文本拼接")]
|
||||
private string SereinTextJoin(params object[] value)
|
||||
public static string SereinTextJoin(params object[] value)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (var item in value)
|
||||
@@ -84,7 +84,7 @@ namespace Serein.Library
|
||||
|
||||
|
||||
[NodeAction(NodeType.Action, "键值对动态构建对象")]
|
||||
private object SereinKvDataToObject(Dictionary<string, object> dict,
|
||||
public static object SereinKvDataToObject(Dictionary<string, object> dict,
|
||||
string classTypeName = "newClass_dynamic",
|
||||
bool IsPrint = false)
|
||||
{
|
||||
@@ -105,7 +105,7 @@ namespace Serein.Library
|
||||
|
||||
|
||||
[NodeAction(NodeType.Action, "设置/更新全局数据")]
|
||||
private object SereinAddOrUpdateFlowGlobalData(string name, object data)
|
||||
public static object SereinAddOrUpdateFlowGlobalData(string name, object data)
|
||||
{
|
||||
SereinEnv.AddOrUpdateFlowGlobalData(name, data);
|
||||
return data;
|
||||
|
||||
133
Library/Utils/BenchmarkHelpers.cs
Normal file
133
Library/Utils/BenchmarkHelpers.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 代码计时工具类
|
||||
/// </summary>
|
||||
public static class BenchmarkHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行指定异步方法多次并输出耗时的最大、最小和平均值。
|
||||
/// </summary>
|
||||
/// <param name="func">需要执行的异步方法</param>
|
||||
/// <param name="count">执行次数,默认10000</param>
|
||||
public static void Benchmark(Action action, 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();
|
||||
action();
|
||||
sw.Stop();
|
||||
|
||||
double ms = sw.Elapsed.TotalMilliseconds;
|
||||
if (ms > max) max = ms;
|
||||
if (ms < min) min = ms;
|
||||
total += ms;
|
||||
}
|
||||
|
||||
double avg = total / count;
|
||||
|
||||
Console.WriteLine($"运行 {count} 次:");
|
||||
Console.WriteLine($"最大耗时:{max} 毫秒");
|
||||
Console.WriteLine($"最小耗时:{min} 毫秒");
|
||||
Console.WriteLine($"平均耗时:{avg} 毫秒");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行指定异步方法多次并输出耗时的最大、最小和平均值。
|
||||
/// </summary>
|
||||
/// <param name="func">需要执行的异步方法</param>
|
||||
/// <param name="count">执行次数,默认10000</param>
|
||||
public static async Task BenchmarkAsync(Func<Task> func, 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 func();
|
||||
sw.Stop();
|
||||
|
||||
double ms = sw.Elapsed.TotalMilliseconds;
|
||||
if (ms > max) max = ms;
|
||||
if (ms < min) min = ms;
|
||||
total += ms;
|
||||
}
|
||||
|
||||
double avg = total / count;
|
||||
Console.WriteLine($"运行 {count} 次:");
|
||||
Console.WriteLine($"总耗时 :{total} 毫秒:");
|
||||
Console.WriteLine($"最大耗时:{max} 毫秒");
|
||||
Console.WriteLine($"最小耗时:{min} 毫秒");
|
||||
Console.WriteLine($"平均耗时:{avg} 毫秒");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行指定异步方法多次并输出耗时的最大、最小和平均值。
|
||||
/// </summary>
|
||||
/// <param name="func">需要执行的异步方法</param>
|
||||
/// <param name="count">执行次数,默认10000</param>
|
||||
public static async Task<TReult> BenchmarkAsync<TReult>(Func<Task<TReult>> func, 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 func();
|
||||
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;
|
||||
Console.WriteLine($"运行 {count} 次:");
|
||||
Console.WriteLine($"总耗时 :{total} 毫秒:");
|
||||
Console.WriteLine($"最大耗时:{max} 毫秒");
|
||||
Console.WriteLine($"最小耗时:{min} 毫秒");
|
||||
Console.WriteLine($"平均耗时:{avg} 毫秒");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行指定异步方法多次并输出耗时的最大、最小和平均值。
|
||||
/// </summary>
|
||||
/// <param name="func">需要执行的异步方法</param>
|
||||
public static async Task<TReult> BenchmarkAsync<TReult>(Func<Task<TReult>> func)
|
||||
{
|
||||
double max = double.MinValue;
|
||||
double min = double.MaxValue;
|
||||
double total = 0;
|
||||
TReult result = default;
|
||||
var sw = Stopwatch.StartNew();
|
||||
result = await func();
|
||||
sw.Stop();
|
||||
|
||||
double ms = sw.Elapsed.TotalMilliseconds;
|
||||
if (ms > max) max = ms;
|
||||
if (ms < min) min = ms;
|
||||
total += ms;
|
||||
|
||||
Console.WriteLine($"运行1次耗时 :{total} 毫秒:");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ namespace Serein.Library.Utils
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Func<object>> _registerCallback;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 未完成注入的实例集合。
|
||||
/// 键:需要的类型名称
|
||||
@@ -83,7 +84,6 @@ namespace Serein.Library.Utils
|
||||
/// 向容器注册类型,并指定其实例成员
|
||||
/// </summary>
|
||||
/// <typeparam name="T">需要注册的类型</typeparam>
|
||||
/// <param name="getInstance">获取实例的回调函数</param>
|
||||
/// <returns></returns>
|
||||
public ISereinIOC Register<T>()
|
||||
{
|
||||
@@ -117,6 +117,7 @@ namespace Serein.Library.Utils
|
||||
public ISereinIOC Register<TService, TImplementation>(Func<TService> getInstance)
|
||||
where TImplementation : TService
|
||||
{
|
||||
|
||||
RegisterType(typeof(TService).FullName, typeof(TImplementation), () => getInstance.Invoke());
|
||||
return this;
|
||||
}
|
||||
@@ -130,6 +131,7 @@ namespace Serein.Library.Utils
|
||||
public ISereinIOC Register<TService, TImplementation>()
|
||||
where TImplementation : TService
|
||||
{
|
||||
|
||||
RegisterType(typeof(TService).FullName, typeof(TImplementation));
|
||||
return this;
|
||||
}
|
||||
@@ -137,7 +139,7 @@ namespace Serein.Library.Utils
|
||||
|
||||
#endregion
|
||||
|
||||
#region 示例的创建
|
||||
#region 实例的创建
|
||||
/// <summary>
|
||||
/// 用于临时实例的创建,不登记到IOC容器中,依赖项注入失败时也不记录。
|
||||
/// </summary>
|
||||
@@ -335,7 +337,8 @@ namespace Serein.Library.Utils
|
||||
{
|
||||
if (!dependencyMap[IOC_MAIN].Contains(type.FullName))
|
||||
{
|
||||
dependencyMap[IOC_MAIN].Add(type.FullName);
|
||||
//dependencyMap[IOC_MAIN].Add(type.FullName);
|
||||
dependencyMap[IOC_MAIN].Add(typeFullName);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -459,6 +462,7 @@ namespace Serein.Library.Utils
|
||||
/// <returns></returns>
|
||||
private object CreateInstance(string typeName)
|
||||
{
|
||||
|
||||
if (!_typeMappings.TryGetValue(typeName, out var type)) // 获取类型
|
||||
{
|
||||
return null;
|
||||
@@ -535,8 +539,8 @@ namespace Serein.Library.Utils
|
||||
/// <returns></returns>
|
||||
public ISereinIOC Build()
|
||||
{
|
||||
var dependencyTree = BuildDependencyTree(); // 生成类型依赖关系
|
||||
var creationOrder = GetCreationOrder(dependencyTree); // 生成创建顺序
|
||||
Dictionary<string, List<string>> dependencyTree = BuildDependencyTree(); // 生成类型依赖关系
|
||||
List<string> creationOrder = GetCreationOrder(dependencyTree); // 生成创建顺序
|
||||
|
||||
// 输出创建顺序
|
||||
Debug.WriteLine("创建顺序: " + string.Join($"{Environment.NewLine}↓ {Environment.NewLine}", creationOrder));
|
||||
@@ -544,7 +548,10 @@ namespace Serein.Library.Utils
|
||||
// 创建对象
|
||||
foreach (var typeName in creationOrder)
|
||||
{
|
||||
|
||||
if (typeName.Equals("Serein.Library.LightweightFlowEnvironment"))
|
||||
{
|
||||
|
||||
}
|
||||
if (_dependencies.ContainsKey(typeName))
|
||||
{
|
||||
continue;
|
||||
@@ -554,13 +561,15 @@ namespace Serein.Library.Utils
|
||||
continue;
|
||||
}
|
||||
var value = CreateInstance(typeName);
|
||||
if(value is null)
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.ERROR, $"IOC容器无法创建对象:{typeName}");
|
||||
continue;
|
||||
}
|
||||
_dependencies[typeName] = value;
|
||||
OnIOCMembersChanged?.Invoke(new IOCMembersChangedEventArgs(typeName, value));
|
||||
|
||||
}
|
||||
_typeMappings.Clear();
|
||||
return this;
|
||||
|
||||
@@ -3,7 +3,6 @@ using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.NodeFlow.Model;
|
||||
using Serein.NodeFlow.Services;
|
||||
using Serein.NodeFlow.Tool;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -126,7 +125,6 @@ namespace Serein.NodeFlow.Env
|
||||
};
|
||||
|
||||
|
||||
|
||||
flowWorkManagement = new FlowWorkManagement(flowTaskOptions);
|
||||
var cts = new CancellationTokenSource();
|
||||
try
|
||||
@@ -152,7 +150,7 @@ namespace Serein.NodeFlow.Env
|
||||
/// </summary>
|
||||
/// <param name="startNodeGuid"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid)
|
||||
public async Task<TResult> StartFlowAsync<TResult>(string startNodeGuid)
|
||||
{
|
||||
|
||||
var flowTaskOptions = new FlowWorkOptions
|
||||
@@ -162,19 +160,34 @@ namespace Serein.NodeFlow.Env
|
||||
};
|
||||
var flowTaskManagement = new FlowWorkManagement(flowTaskOptions);
|
||||
|
||||
if (true || flowEnvironment.FlowState == RunState.Running || FlipFlopState == RunState.Running)
|
||||
if (!flowModelService.TryGetNodeModel(startNodeGuid, out var nodeModel) || nodeModel is SingleFlipflopNode)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
if (!flowModelService.TryGetNodeModel(startNodeGuid, out var nodeModel) || nodeModel is SingleFlipflopNode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel);
|
||||
return true;
|
||||
#if DEBUG
|
||||
|
||||
FlowResult flowResult = await BenchmarkHelpers.BenchmarkAsync(async () =>
|
||||
{
|
||||
var flowResult = await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel);
|
||||
return flowResult;
|
||||
});
|
||||
#else
|
||||
FlowResult flowResult = await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel);
|
||||
#endif
|
||||
|
||||
|
||||
if (flowResult.Value is TResult result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else if (flowResult is FlowResult && flowResult is TResult result2)
|
||||
{
|
||||
return result2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
throw new ArgumentNullException($"类型转换失败,流程返回数据与泛型不匹配,当前返回类型为[{flowResult.Value.GetType().FullName}]。");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +344,17 @@ namespace Serein.NodeFlow.Env
|
||||
}
|
||||
}
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
|
||||
#if DEBUG
|
||||
|
||||
FlowResult flowResult = await BenchmarkHelpers.BenchmarkAsync(async () =>
|
||||
{
|
||||
var flowResult = await flowCallNode.StartFlowAsync(context, cts.Token);
|
||||
return flowResult;
|
||||
});
|
||||
#else
|
||||
var flowResult = await flowCallNode.StartFlowAsync(context, cts.Token);
|
||||
#endif
|
||||
cts?.Cancel();
|
||||
cts?.Dispose();
|
||||
if (flowResult.Value is TResult result)
|
||||
|
||||
@@ -5,7 +5,6 @@ using Serein.Library.Utils;
|
||||
using Serein.NodeFlow.Model;
|
||||
using Serein.NodeFlow.Model.Operation;
|
||||
using Serein.NodeFlow.Services;
|
||||
using Serein.NodeFlow.Tool;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using static Serein.Library.Api.IFlowEnvironment;
|
||||
@@ -261,7 +260,7 @@ namespace Serein.NodeFlow.Env
|
||||
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Create,
|
||||
JunctionOfConnectionType = JunctionOfConnectionType.Arg,
|
||||
};
|
||||
flowOperationService.Execute(operation);
|
||||
flowOperationService.Execute(operation);
|
||||
}
|
||||
|
||||
public void RemoveInvokeConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
|
||||
@@ -335,6 +334,15 @@ namespace Serein.NodeFlow.Env
|
||||
|
||||
public void SetStartNode(string canvasGuid, string nodeGuid)
|
||||
{
|
||||
|
||||
IOperation operation = new SetStartNodeOperation
|
||||
{
|
||||
CanvasGuid = canvasGuid,
|
||||
NewNodeGuid = nodeGuid,
|
||||
};
|
||||
_ = flowOperationService.Execute(operation);
|
||||
|
||||
return;
|
||||
if (!TryGetCanvasModel(canvasGuid, out var canvasModel) || !TryGetNodeModel(nodeGuid, out var newStartNodeModel))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,6 @@ using Serein.Library.Api;
|
||||
using Serein.Library.FlowNode;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.NodeFlow.Services;
|
||||
using Serein.NodeFlow.Tool;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein.NodeFlow.Env
|
||||
@@ -58,9 +57,9 @@ namespace Serein.NodeFlow.Env
|
||||
.Register<IFlowControl, FlowControl>()
|
||||
.Register<LocalFlowEnvironment>()
|
||||
.Register<FlowModelService>()
|
||||
.Register<FlowLibraryService>()
|
||||
.Register<FlowOperationService>()
|
||||
.Register<NodeMVVMService>()
|
||||
.Register<FlowLibraryService>()
|
||||
.Build();
|
||||
// 默认使用本地环境
|
||||
currentFlowEnvironment = ioc.Get<LocalFlowEnvironment>();
|
||||
@@ -283,9 +282,9 @@ namespace Serein.NodeFlow.Env
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid)
|
||||
public async Task<TResult> StartFlowAsync<TResult>(string startNodeGuid)
|
||||
{
|
||||
return await currentFlowEnvironment.FlowControl.StartFlowFromSelectNodeAsync(startNodeGuid);
|
||||
return await currentFlowEnvironment.FlowControl.StartFlowAsync<TResult>(startNodeGuid);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -29,6 +29,24 @@ namespace Serein.NodeFlow
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为根节点
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsRoot(this IFlowNode node)
|
||||
{
|
||||
var cts = NodeStaticConfig.ConnectionTypes;
|
||||
foreach (var ct in cts)
|
||||
{
|
||||
if (node.PreviousNodes[ct].Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建节点
|
||||
@@ -89,6 +107,8 @@ namespace Serein.NodeFlow
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 触发器运行后状态转为对应的后继分支类别
|
||||
/// </summary>
|
||||
@@ -107,6 +127,8 @@ namespace Serein.NodeFlow
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 判断 触发器节点 是否存在上游分支
|
||||
/// </summary>
|
||||
@@ -136,19 +158,27 @@ namespace Serein.NodeFlow
|
||||
/// <param name="retractCount">缩进次数(4个空格)</param>
|
||||
/// <param name="code">要添加的代码</param>
|
||||
/// <returns>字符串构建器本身</returns>
|
||||
public static StringBuilder AddCode(this StringBuilder sb,
|
||||
public static StringBuilder AppendCode(this StringBuilder sb,
|
||||
int retractCount = 0,
|
||||
string code = null)
|
||||
string code = null,
|
||||
bool isWrapping = true)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
var retract = new string(' ', retractCount * 4);
|
||||
sb.AppendLine(retract + code);
|
||||
string retract = new string(' ', retractCount * 4);
|
||||
sb.Append(retract);
|
||||
if (isWrapping)
|
||||
{
|
||||
sb.AppendLine(code);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(code);
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ namespace Serein.NodeFlow.Model
|
||||
/// <returns>节点传回数据对象</returns>
|
||||
public virtual async Task<FlowResult> ExecutingAsync(IDynamicContext context, CancellationToken token)
|
||||
{
|
||||
|
||||
// 执行触发检查是否需要中断
|
||||
if (DebugSetting.IsInterrupt)
|
||||
{
|
||||
@@ -131,7 +132,6 @@ namespace Serein.NodeFlow.Model
|
||||
|
||||
throw new Exception($"节点{this.Guid}不存在对应委托");
|
||||
}
|
||||
|
||||
var instance = Env.IOC.Get(md.ActingInstanceType);
|
||||
if (instance is null)
|
||||
{
|
||||
|
||||
@@ -266,7 +266,7 @@ namespace Serein.NodeFlow.Model
|
||||
// 此处代码与SereinFlow.Library.FlowNode.ParameterDetails
|
||||
// ToMethodArgData()方法中判断流程接口节点分支逻辑耦合
|
||||
// 不要轻易修改
|
||||
context.AddOrUpdate(targetNode.Guid, flowData);
|
||||
context.AddOrUpdateFlowData(targetNode.Guid, flowData);
|
||||
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
|
||||
{
|
||||
if (this.SuccessorNodes[ctType] == null) continue;
|
||||
|
||||
@@ -298,25 +298,37 @@ namespace Serein.NodeFlow.Model.Operation
|
||||
/// </summary>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private async Task<bool> CreateArgConnection()
|
||||
{
|
||||
{/*
|
||||
IFlowNode fromNodeControl = ToNode;
|
||||
IFlowNode toNodeControl = ToNode;
|
||||
IFlowNode toNodeControl = ToNode;*/
|
||||
ConnectionArgSourceType type = ConnectionArgSourceType;
|
||||
int index = ArgIndex;
|
||||
|
||||
|
||||
|
||||
/*FromNode.NeedResultNodes[type].Remove(ToNode); // 从起始节点的参数来源中移除目标节点
|
||||
if (FromNode.Guid == ToNode.Guid) // 不能连接到自己
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.ERROR, $"起始节点与目标节点不能是同一个节点" +
|
||||
$"{Environment.NewLine}起始节点:{FromNode.Guid}" +
|
||||
$"{Environment.NewLine}目标节点:{ToNode.Guid}");
|
||||
return false;
|
||||
}*/
|
||||
var toNodeArgSourceGuid = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid; // 目标节点对应参数可能已经有其它连接
|
||||
var toNodeArgSourceType = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType;
|
||||
|
||||
if (FromNode.Guid == toNodeArgSourceGuid
|
||||
&& toNodeArgSourceType == ConnectionArgSourceType)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"节点之间已建立过连接关系" +
|
||||
if (FromNode.NeedResultNodes[type].Contains(ToNode))
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"节点之间已建立过连接关系" +
|
||||
$"起始节点:{FromNode.Guid}" +
|
||||
$"目标节点:{ToNode.Guid}" +
|
||||
$"参数索引:{ArgIndex}" +
|
||||
$"参数类型:{ConnectionArgSourceType}");
|
||||
return false;
|
||||
}
|
||||
FromNode.NeedResultNodes[type].Add(ToNode);
|
||||
await TriggerEvent(() =>
|
||||
{
|
||||
flowEnvironmentEvent.OnNodeConnectChanged(
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.NodeFlow.Services;
|
||||
using Serein.NodeFlow.Tool;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
@@ -115,9 +115,8 @@ namespace Serein.NodeFlow.Model.Operation
|
||||
#endregion
|
||||
|
||||
#region 移除参数获取关系
|
||||
// 需要找到有哪些节点的入参参数,被设置为了该节点,然后将其删除
|
||||
// 因为节点自身没有记录哪些节点选取了自己作为参数来源节点,所以需要遍历所有节点
|
||||
|
||||
// 遍历需要该节点返回值的节点,移除与其的连接
|
||||
foreach (var item in flowNode.NeedResultNodes)
|
||||
{
|
||||
var connectionType = item.Key; // 参数来源连接类型
|
||||
@@ -128,14 +127,14 @@ namespace Serein.NodeFlow.Model.Operation
|
||||
if (md is null) continue;
|
||||
var pds = md.ParameterDetailss;
|
||||
if (pds is null || pds.Length == 0) continue;
|
||||
foreach(var parameter in pds)
|
||||
foreach (var parameter in pds)
|
||||
{
|
||||
if (!parameter.ArgDataSourceNodeGuid.Equals(flowNode.Guid)) continue;
|
||||
// 找到了对应的入参控制点了
|
||||
var e = new NodeConnectChangeEventArgs(
|
||||
CanvasGuid, // 画布
|
||||
flowNode.Guid, // 被移除的节点Guid
|
||||
argNode.Guid, // 子节点Guid
|
||||
flowNode.Guid, // 数据来源节点(被移除的节点Guid)
|
||||
argNode.Guid, // 需要数据的节点
|
||||
parameter.Index, // 作用在第几个参数上,用于指示移除第几个参数的连线
|
||||
JunctionOfConnectionType.Arg, // 指示移除的是参数连接线
|
||||
connectionType, // 对应的连接关系
|
||||
@@ -148,6 +147,34 @@ namespace Serein.NodeFlow.Model.Operation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历该节点参数详情,获取来源节点,移除与其的连接
|
||||
|
||||
if (flowNode.MethodDetails?.ParameterDetailss != null)
|
||||
{
|
||||
var pds = flowNode.MethodDetails.ParameterDetailss;
|
||||
foreach (var pd in pds)
|
||||
{
|
||||
if(flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var argSourceNode))
|
||||
{
|
||||
// 找到了对应的入参控制点了
|
||||
var e = new NodeConnectChangeEventArgs(
|
||||
CanvasGuid, // 画布
|
||||
argSourceNode.Guid, // 数据来源节点
|
||||
flowNode.Guid, // 需要数据的节点(被移除的节点Guid)
|
||||
pd.Index, // 作用在第几个参数上,用于指示移除第几个参数的连线
|
||||
JunctionOfConnectionType.Arg, // 指示移除的是参数连接线
|
||||
pd.ArgDataSourceType, // 对应的连接关系
|
||||
NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线
|
||||
EventArgs.Add(e); // 缓存事件参数
|
||||
await TriggerEvent(() =>
|
||||
{
|
||||
flowEnvironmentEvent.OnNodeConnectChanged(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
flowModelService.RemoveNodeModel(flowNode); // 从记录中移除
|
||||
@@ -163,7 +190,9 @@ namespace Serein.NodeFlow.Model.Operation
|
||||
// 为了避免直接修改 ObservableCollection 集合导致异常产生,故而使用UI线程上下文操作运行
|
||||
await TriggerEvent(() =>
|
||||
{
|
||||
flowCanvasDetails?.Nodes.Remove(flowNode);
|
||||
var lsit = flowCanvasDetails.Nodes.ToList();
|
||||
lsit.Remove(flowNode);
|
||||
flowCanvasDetails.Nodes = lsit;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||
|
||||
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>.\obj\g</CompilerGeneratedFilesOutputPath>
|
||||
|
||||
@@ -13,12 +13,12 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Serein.NodeFlow.Tool
|
||||
namespace Serein.NodeFlow.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 管理加载在运行环境中的外部程序集
|
||||
/// </summary>
|
||||
internal class FlowLibraryService
|
||||
public class FlowLibraryService
|
||||
{
|
||||
public FlowLibraryService(IFlowEnvironment flowEnvironment)
|
||||
{
|
||||
@@ -111,6 +111,32 @@ namespace Serein.NodeFlow.Tool
|
||||
/// <param name="methodName">方法名称</param>
|
||||
/// <param name="md">返回的方法描述</param>
|
||||
/// <returns>是否获取成功</returns>
|
||||
public bool TryGetMethodInfo(string assemblyName, string methodName, [MaybeNullWhen(false)] out MethodInfo methodInfo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assemblyName) || string.IsNullOrEmpty(methodName))
|
||||
{
|
||||
methodInfo = null;
|
||||
return false;
|
||||
}
|
||||
if (_myFlowLibrarys.TryGetValue(assemblyName, out var flowLibrary)
|
||||
&& flowLibrary.MethodInfos.TryGetValue(methodName, out methodInfo))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
methodInfo = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取方法描述
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">程序集名称</param>
|
||||
/// <param name="methodName">方法名称</param>
|
||||
/// <param name="md">返回的方法描述</param>
|
||||
/// <returns>是否获取成功</returns>
|
||||
public bool TryGetMethodDetails(string assemblyName, string methodName, [MaybeNullWhen(false)] out MethodDetails md)
|
||||
{
|
||||
if (_myFlowLibrarys.TryGetValue(assemblyName, out var flowLibrary)
|
||||
@@ -238,7 +264,7 @@ namespace Serein.NodeFlow.Tool
|
||||
/// <summary>
|
||||
/// 基础依赖
|
||||
/// </summary>
|
||||
public readonly static string SereinBaseLibrary = $"{nameof(Serein)}.{nameof(Serein.Library)}.dll";
|
||||
public readonly static string SereinBaseLibrary = $"{nameof(Serein)}.{nameof(Library)}.dll";
|
||||
|
||||
//private (NodeLibraryInfo, List<MethodDetailsInfo>) LoadDllNodeInfo(Assembly assembly)
|
||||
//{
|
||||
@@ -331,7 +357,7 @@ namespace Serein.NodeFlow.Tool
|
||||
string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
if (!string.IsNullOrEmpty(assemblyPath))
|
||||
{
|
||||
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
|
||||
var assembly = Default.LoadFromAssemblyPath(assemblyPath);
|
||||
//var assembly = LoadFromAssemblyPath(assemblyPath);
|
||||
return assembly;
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
using Serein.Library;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.NodeFlow.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
@@ -16,10 +21,12 @@ namespace Serein.NodeFlow.Services
|
||||
public class FlowModelService
|
||||
{
|
||||
private readonly IFlowEnvironment environment;
|
||||
private readonly FlowLibraryService flowLibraryService;
|
||||
|
||||
public FlowModelService(IFlowEnvironment environment)
|
||||
public FlowModelService(IFlowEnvironment environment, FlowLibraryService flowLibraryService )
|
||||
{
|
||||
this.environment = environment;
|
||||
this.flowLibraryService = flowLibraryService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -114,53 +121,646 @@ namespace Serein.NodeFlow.Services
|
||||
return flowCanvasDetails.Nodes.Count > 0;
|
||||
}
|
||||
|
||||
#region 代码生成
|
||||
|
||||
public void ToCsharpCoreFile()
|
||||
public string ToCsharpCoreFile()
|
||||
{
|
||||
// TODO: 实现将流程模型转换为C# Core文件的逻辑
|
||||
// 遍历每个画布
|
||||
int canvas_index = 0;
|
||||
|
||||
#region 生成类和方法
|
||||
#if false
|
||||
HashSet<Type> assemblyFlowClasss = new HashSet<Type>(); // 用于创建依赖注入项
|
||||
assemblyFlowClasss.Add(typeof(IFlowCallTree)); // 调用树
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
foreach (var canvas in FlowCanvass.Values)
|
||||
{
|
||||
if (canvas.StartNode is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int flowTemplateId = canvas_index++;
|
||||
string flowTemplateClassName = $"FlowTemplate{flowTemplateId}";
|
||||
string flowTemplateClassName = $"FlowTemplate_{canvas.Guid.Replace("-", "")}";
|
||||
|
||||
HashSet<Type> flowClasss = new HashSet<Type>();
|
||||
|
||||
flowClasss.Add(typeof(IFlowCallTree)); // 调用树
|
||||
// 收集程序集信息
|
||||
foreach (var node in canvas.Nodes)
|
||||
{
|
||||
var instanceType = node.MethodDetails.ActingInstanceType;
|
||||
if(instanceType is not null)
|
||||
{
|
||||
flowClasss.Add(instanceType);
|
||||
assemblyFlowClasss.Add(instanceType);
|
||||
if (instanceType is not null)
|
||||
{
|
||||
flowClasss.Add(instanceType);
|
||||
assemblyFlowClasss.Add(instanceType);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成方法信息
|
||||
stringBuilder.AppendCode(0, $"public class {flowTemplateClassName}");
|
||||
stringBuilder.AppendCode(0, $"{{");
|
||||
|
||||
// 构造函数及依赖注入字段
|
||||
GenerateCtor(stringBuilder, flowTemplateClassName, flowClasss);
|
||||
//GenerateNodeIndexLookup(stringBuilder, flowTemplateClassName, );
|
||||
GenerateInitMethod(stringBuilder);
|
||||
GenerateCallTree(stringBuilder, canvas);
|
||||
|
||||
// 节点生成方法信息
|
||||
foreach (var node in canvas.Nodes)
|
||||
{
|
||||
var instanceType = node.MethodDetails.ActingInstanceType;
|
||||
var returnType = node.MethodDetails.ReturnType;
|
||||
var methodName = node.MethodDetails.MethodAnotherName;
|
||||
|
||||
GenerateMethod(stringBuilder, node);
|
||||
}
|
||||
stringBuilder.AppendCode(0, $"}}");
|
||||
|
||||
}
|
||||
#else
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
HashSet<Type> assemblyFlowClasss = new HashSet<Type>(); // 用于创建依赖注入项
|
||||
assemblyFlowClasss.Add(typeof(IFlowCallTree)); // 调用树
|
||||
var flowNodes = NodeModels.Values.ToArray();
|
||||
// 收集程序集信息
|
||||
foreach (var node in flowNodes)
|
||||
{
|
||||
var instanceType = node.MethodDetails.ActingInstanceType;
|
||||
if (instanceType is not null)
|
||||
{
|
||||
assemblyFlowClasss.Add(instanceType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
string flowTemplateClassName = $"FlowTemplate"; // 类名
|
||||
stringBuilder.AppendCode(0, $"public class {flowTemplateClassName} : global::{typeof(IFlowCallTree).FullName}");
|
||||
stringBuilder.AppendCode(0, $"{{");
|
||||
GenerateCtor(stringBuilder, flowTemplateClassName, assemblyFlowClasss); // 生成构造方法
|
||||
GenerateInitMethod(stringBuilder); // 生成初始化方法
|
||||
GenerateCallTree(stringBuilder, flowNodes); // 生成调用树
|
||||
GenerateNodeIndexLookup(stringBuilder, flowTemplateClassName, flowNodes); // 初始化节点缓存
|
||||
foreach (var node in flowNodes)
|
||||
{
|
||||
GenerateMethod(stringBuilder, node); // 生成每个节点的方法
|
||||
}
|
||||
stringBuilder.AppendCode(0, $"}}");
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
|
||||
var instanceTypeFullName = instanceType.FullName;
|
||||
var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName;
|
||||
|
||||
string methodContext = $"private {returnTypeFullName} NodeMethod_{methodName}({nameof(IDynamicContext)} context)";
|
||||
SereinEnv.WriteLine(InfoType.INFO, methodContext);
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成构造函数代码
|
||||
/// </summary>
|
||||
/// <param name="sb"></param>
|
||||
/// <param name="className"></param>
|
||||
/// <param name="assemblyFlowClasss"></param>
|
||||
/// <returns></returns>
|
||||
private void GenerateCtor(StringBuilder sb, string className, HashSet<Type> assemblyFlowClasss)
|
||||
{
|
||||
if (assemblyFlowClasss.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var instanceTypes = assemblyFlowClasss.Where(x => !IsStaticClass(x)).ToArray();
|
||||
|
||||
for (int index = 0; index < instanceTypes.Length; index++)
|
||||
{
|
||||
var type = instanceTypes[index];
|
||||
var ctor_parms_name = GetCamelCase(type);
|
||||
sb.AppendCode(2, $"private readonly global::{type.FullName} {ctor_parms_name};");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendCode(2, $"public {className}(", false);
|
||||
for (int index = 0; index < instanceTypes.Length; index++)
|
||||
{
|
||||
var type = instanceTypes[index];
|
||||
var ctor_parms_name = GetCamelCase(type);
|
||||
sb.Append($"global::{type.FullName} {ctor_parms_name}{(index < instanceTypes.Length - 1 ? "," : "")}");
|
||||
}
|
||||
sb.AppendCode(0, $")");
|
||||
sb.AppendCode(2, $"{{");
|
||||
for (int index = 0; index < instanceTypes.Length; index++)
|
||||
{
|
||||
var type = instanceTypes[index];
|
||||
var ctor_parms_name = GetCamelCase(type);
|
||||
sb.AppendCode(3, $"this.{ctor_parms_name} = {ctor_parms_name};");
|
||||
}
|
||||
sb.AppendLine();
|
||||
sb.AppendCode(3, $"Init();"); // 初始化调用树
|
||||
sb.AppendCode(2, $"}}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成方法调用逻辑
|
||||
/// </summary>
|
||||
/// <param name="sb_main"></param>
|
||||
/// <param name="flowNode"></param>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private void GenerateMethod(StringBuilder sb_main, IFlowNode flowNode)
|
||||
{
|
||||
string? dynamicContextTypeName = typeof(IDynamicContext).FullName;
|
||||
string? flowContext = nameof(flowContext);
|
||||
|
||||
if (flowNode.ControlType == NodeControlType.Action)
|
||||
{
|
||||
#region 生成 Action 节点类型的调用过程
|
||||
if (!flowLibraryService.TryGetMethodInfo(flowNode.MethodDetails.AssemblyName,
|
||||
flowNode.MethodDetails.MethodName,
|
||||
out var methodInfo) || methodInfo is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var isRootNode = flowNode.IsRoot();
|
||||
|
||||
var instanceType = flowNode.MethodDetails.ActingInstanceType;
|
||||
var returnType = methodInfo.ReturnType;
|
||||
|
||||
var instanceName = GetCamelCase(instanceType);// $"instance_{instanceType.Name}";
|
||||
|
||||
var instanceTypeFullName = instanceType.FullName;
|
||||
var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName;
|
||||
|
||||
#region 方法内部逻辑
|
||||
StringBuilder sb_invoke_login = new StringBuilder();
|
||||
if (flowNode.MethodDetails is null) return;
|
||||
var param = methodInfo.GetParameters();
|
||||
var md = flowNode.MethodDetails;
|
||||
var pds = flowNode.MethodDetails.ParameterDetailss;
|
||||
if (param is null) return;
|
||||
if (pds is null) return;
|
||||
|
||||
bool isGetPreviousNode = false;
|
||||
for (int index = 0; index < pds.Length; index++)
|
||||
{
|
||||
ParameterDetails? pd = pds[index];
|
||||
ParameterInfo parameterInfo = param[index];
|
||||
var paramtTypeFullName = parameterInfo.ParameterType.FullName;
|
||||
|
||||
if (pd.IsExplicitData)
|
||||
{
|
||||
// 只能是 数值、 文本、枚举, 才能作为显式参数
|
||||
if (parameterInfo.ParameterType.IsValueType)
|
||||
{
|
||||
if (parameterInfo.ParameterType.IsEnum)
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = global::{paramtTypeFullName}.{pd.DataValue}; // 获取当前节点的上一节点数据");
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = pd.DataValue.ToConvert(parameterInfo.ParameterType);
|
||||
sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据");
|
||||
|
||||
}
|
||||
}
|
||||
else if (parameterInfo.ParameterType == typeof(string))
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = \"{pd.DataValue}\"; // 获取当前节点的上一节点数据");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理表达式
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
#region 非显式设置的参数以正常方式获取
|
||||
if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData)
|
||||
{
|
||||
var previousNode = $"previousNode{index}";
|
||||
var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}";
|
||||
sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{flowNode.Guid}\");"); // 获取运行时上一节点Guid
|
||||
sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IDynamicContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据");
|
||||
}
|
||||
else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData)
|
||||
{
|
||||
if (this.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode))
|
||||
{
|
||||
var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}";
|
||||
var otherNodeReturnType = otherNode.MethodDetails.ReturnType;
|
||||
if (otherNodeReturnType == typeof(object))
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IDynamicContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据");
|
||||
}
|
||||
else if (pd.DataType.IsAssignableFrom(otherNodeReturnType))
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {flowContext}.{nameof(IDynamicContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 获取的数据无法转换为目标方法入参类型
|
||||
throw new Exception("获取的数据无法转换为目标方法入参类型");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 指定了Guid,但项目中不存在对应的节点,需要抛出异常
|
||||
throw new Exception("指定了Guid,但项目中不存在对应的节点");
|
||||
}
|
||||
}
|
||||
else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke)
|
||||
{
|
||||
if (this.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) // 获取指定节点
|
||||
{
|
||||
var otherNodeReturnType = otherNode.MethodDetails.ReturnType;
|
||||
var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{otherNode.MethodDetails.ReturnType.FullName}";
|
||||
if (otherNodeReturnType == typeof(object))
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IDynamicContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据");
|
||||
}
|
||||
else if (pd.DataType.IsAssignableFrom(otherNodeReturnType))
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {GetNodeMethodName(otherNode)}({flowContext}); // 获取指定节点的数据");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 获取的数据无法转换为目标方法入参类型
|
||||
throw new Exception("获取的数据无法转换为目标方法入参类型");
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// 指定了Guid,但项目中不存在对应的节点,需要抛出异常
|
||||
throw new Exception("指定了Guid,但项目中不存在对应的节点");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (methodInfo.ReturnType == typeof(void))
|
||||
{
|
||||
if (methodInfo.IsStatic)
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"global::{instanceType}.{methodInfo.Name}(", false);
|
||||
for (int index = 0; index < pds.Length; index++)
|
||||
{
|
||||
sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}");
|
||||
}
|
||||
sb_invoke_login.AppendCode(0, $"); // 调用方法 {md.MethodAnotherName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"{instanceName}.{methodInfo.Name}(", false);
|
||||
for (int index = 0; index < pds.Length; index++)
|
||||
{
|
||||
sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}");
|
||||
}
|
||||
sb_invoke_login.AppendCode(0, $"); // 调用方法 {md.MethodAnotherName}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (methodInfo.IsStatic)
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"var result = global::{instanceType}.{methodInfo.Name}(", false);
|
||||
for (int index = 0; index < pds.Length; index++)
|
||||
{
|
||||
sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}");
|
||||
}
|
||||
sb_invoke_login.AppendCode(0, $"); // 调用方法 {md.MethodAnotherName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb_invoke_login.AppendCode(3, $"var result = {instanceName}.{methodInfo.Name}(", false);
|
||||
for (int index = 0; index < pds.Length; index++)
|
||||
{
|
||||
sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}");
|
||||
}
|
||||
sb_invoke_login.AppendCode(0, $"); // 调用方法 {md.MethodAnotherName}");
|
||||
}
|
||||
|
||||
sb_invoke_login.AppendCode(3, $"{flowContext}.{nameof(IDynamicContext.AddOrUpdate)}(\"{flowNode.Guid}\", result);", false);
|
||||
//sb_invoke_login.AppendCode(3, $"return result;", false);
|
||||
}
|
||||
#endregion
|
||||
|
||||
// global::{returnTypeFullName}
|
||||
|
||||
sb_main.AppendCode(2, $"[Description(\"{instanceTypeFullName}.{methodInfo.Name}\")]");
|
||||
sb_main.AppendCode(2, $"public void {GetNodeMethodName(flowNode)}(global::{dynamicContextTypeName} {flowContext})");
|
||||
sb_main.AppendCode(2, $"{{");
|
||||
sb_main.AppendCode(0, sb_invoke_login.ToString());
|
||||
sb_main.AppendCode(2, $"}}"); // 方法结束
|
||||
sb_main.AppendLine(); // 方法结束
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.Flipflop)
|
||||
{
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.Script)
|
||||
{
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.UI)
|
||||
{
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.ExpCondition)
|
||||
{
|
||||
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.ExpOp)
|
||||
{
|
||||
}
|
||||
|
||||
return;
|
||||
throw new Exception("无法为该节点生成调用逻辑");
|
||||
}
|
||||
|
||||
private void GenerateInitMethod(StringBuilder sb)
|
||||
{
|
||||
sb.AppendCode(2, $"public void Init()");
|
||||
sb.AppendCode(2, $"{{");
|
||||
sb.AppendCode(3, $"{nameof(GenerateCallTree)}(); // 初始化调用树"); // 初始化调用树
|
||||
sb.AppendCode(2, $"}}");
|
||||
}
|
||||
|
||||
private void GenerateCallTree(StringBuilder sb, IFlowNode[] flowNodes)
|
||||
{
|
||||
// Get("0fa6985b-4b63-4499-80b2-76401669292d").AddChildNodeSucceed(Get("acdbe7ea-eb27-4a3e-9cc9-c48f642ee4f5"));
|
||||
|
||||
sb.AppendCode(2, $"private void {nameof(GenerateCallTree)}()");
|
||||
sb.AppendCode(2, $"{{");
|
||||
|
||||
foreach (var node in flowNodes)
|
||||
{
|
||||
var nodeMethod = GetNodeMethodName(node); // 节点对应的方法名称
|
||||
sb.AppendCode(3, $"Get(\"{node.Guid}\").SetAction({nodeMethod});");
|
||||
}
|
||||
|
||||
foreach (var node in flowNodes)
|
||||
{
|
||||
var nodeMethod = GetNodeMethodName(node); // 节点对应的方法名称
|
||||
var cts = NodeStaticConfig.ConnectionTypes;
|
||||
foreach (var ct in cts)
|
||||
{
|
||||
var childNodes = node.SuccessorNodes[ct];
|
||||
var AddChildNodeMethodName = ct switch
|
||||
{
|
||||
ConnectionInvokeType.IsSucceed => nameof(CallNode.AddChildNodeSucceed),
|
||||
ConnectionInvokeType.IsFail => nameof(CallNode.AddChildNodeFail),
|
||||
ConnectionInvokeType.IsError => nameof(CallNode.AddChildNodeError),
|
||||
ConnectionInvokeType.Upstream => nameof(CallNode.AddChildNodeUpstream),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ct), ct, null)
|
||||
};
|
||||
foreach (var childNode in childNodes)
|
||||
{
|
||||
sb.AppendCode(3, $"Get(\"{node.Guid}\").{AddChildNodeMethodName}(Get(\"{childNode.Guid}\"));");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
sb.AppendCode(2, $"}}");
|
||||
sb.AppendLine();
|
||||
|
||||
/*string? dynamicContextTypeName = typeof(IDynamicContext).FullName;
|
||||
string? flowContext = nameof(flowContext);
|
||||
var callTreeType = typeof(IFlowCallTree);
|
||||
var callTreeName = GetCamelCase(callTreeType);
|
||||
//var canvasGuid = flowCanvas.Guid;
|
||||
//var startNodeGuid = flowCanvas.StartNode.Guid;
|
||||
|
||||
sb.AppendCode(2, $"private void {nameof(GenerateCallTree)}()");
|
||||
sb.AppendCode(2, $"{{");
|
||||
//sb.AppendCode(3, $"global::{callTreeType.FullName} {callTreeName} = new global::{callTreeType.FullName}()\";");
|
||||
|
||||
// 注册节点
|
||||
*//* foreach (var node in flowCanvas.Nodes)
|
||||
{
|
||||
var nodeMethod = GetNodeMethodName(node);
|
||||
var call = $"{flowContext} => {nodeMethod}({flowContext})";
|
||||
sb.AppendCode(3, $"{callTreeName}.{nameof(FlowCallTree.AddCallNode)}(\"{node.Guid}\", {call});");
|
||||
}*//*
|
||||
|
||||
sb.AppendLine();
|
||||
foreach (var node in flowNodes)
|
||||
{
|
||||
var nodeMethod = GetNodeMethodName(node);
|
||||
var cts = NodeStaticConfig.ConnectionTypes;
|
||||
foreach (var ct in cts)
|
||||
{
|
||||
var childNodes = node.SuccessorNodes[ct];
|
||||
var addType = ct switch
|
||||
{
|
||||
ConnectionInvokeType.IsSucceed => nameof(CallNode.AddChildNodeSucceed),
|
||||
ConnectionInvokeType.IsFail => nameof(CallNode.AddChildNodeFail),
|
||||
ConnectionInvokeType.IsError => nameof(CallNode.AddChildNodeError),
|
||||
ConnectionInvokeType.Upstream => nameof(CallNode.AddChildNodeUpstream),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ct), ct, null)
|
||||
};
|
||||
foreach (var childNode in childNodes)
|
||||
{
|
||||
sb.AppendCode(3, $"{callTreeName}[\"{node.Guid}\"].{addType}(\"{childNode.Guid}\");");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
sb.AppendCode(2, $"}}");
|
||||
sb.AppendLine();*/
|
||||
}
|
||||
|
||||
private void GenerateNodeIndexLookup(StringBuilder sb, string className, IFlowNode[] flowNodes)
|
||||
{
|
||||
// 初始化Id
|
||||
nodeIdMap.Clear();
|
||||
for (int index = 0; index < flowNodes.Length; index++)
|
||||
{
|
||||
var flowNode = flowNodes[index];
|
||||
GetNodeId(flowNode);
|
||||
}
|
||||
|
||||
var valueArrayName = "_values";
|
||||
|
||||
// 生成 _values
|
||||
sb.AppendCode(2, $"private readonly static global::Serein.Library.CallNode[] {valueArrayName} = new global::Serein.Library.CallNode[{flowNodes.Length}];");
|
||||
|
||||
/*sb.AppendCode(2, $"private readonly static global::System.String[] _keys = new global::System.String[]");
|
||||
sb.AppendCode(2, $"{{");
|
||||
for (int index = 0; index < flowNodes.Length; index++)
|
||||
{
|
||||
var flowNode = flowNodes[index];
|
||||
sb.AppendCode(3, $"\"{flowNode.Guid}\", // {index} : {flowNode.MethodDetails.MethodName}");
|
||||
}
|
||||
sb.AppendCode(2, $"}};");*/
|
||||
|
||||
// 生成静态构造函数
|
||||
sb.AppendCode(2, $"static {className}()");
|
||||
sb.AppendCode(2, $"{{");
|
||||
for (int index = 0; index < flowNodes.Length; index++)
|
||||
{
|
||||
var flowNode = flowNodes[index];
|
||||
sb.AppendCode(3, $"{valueArrayName}[{index}] = new global::Serein.Library.CallNode(\"{flowNode.Guid}\"); // {index} : {flowNode.MethodDetails.MethodName}");
|
||||
}
|
||||
sb.AppendCode(2, $"}}");
|
||||
|
||||
// 初始化 Get 函数
|
||||
var nodeIndexName = "node_index";
|
||||
sb.AppendCode(2, $" [MethodImpl(MethodImplOptions.AggressiveInlining)]"); // 内联优化
|
||||
sb.AppendCode(2, $"public global::Serein.Library.CallNode {nameof(IFlowCallTree.Get)}( global::System.String key)");
|
||||
sb.AppendCode(2, $"{{");
|
||||
sb.AppendCode(3, $"global::System.Int32 {nodeIndexName};");
|
||||
sb.AppendCode(3, $"switch (key)");
|
||||
sb.AppendCode(3, $"{{");
|
||||
|
||||
for (int index = 0; index < flowNodes.Length; index++)
|
||||
{
|
||||
var flowNode = flowNodes[index];
|
||||
sb.AppendCode(4, $"case \"{flowNode.Guid}\":");
|
||||
sb.AppendCode(5, $"{nodeIndexName} = {index};");
|
||||
sb.AppendCode(5, $"break;");
|
||||
}
|
||||
sb.AppendCode(4, $"default:");
|
||||
sb.AppendCode(4, $"{nodeIndexName} = -1;");
|
||||
sb.AppendCode(5, $"break;");
|
||||
sb.AppendCode(3, $"}}");
|
||||
sb.AppendCode(3, $"return {valueArrayName}[{nodeIndexName}];");
|
||||
sb.AppendCode(2, $"}}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成方法名称
|
||||
/// </summary>
|
||||
/// <param name="flowNode"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private string GetNodeMethodName(IFlowNode flowNode)
|
||||
{
|
||||
/*if (!flowLibraryService.TryGetMethodInfo(flowNode.MethodDetails.AssemblyName,
|
||||
flowNode.MethodDetails.MethodName,
|
||||
out var methodInfo))
|
||||
{
|
||||
throw new Exception();
|
||||
}*/
|
||||
var guid = flowNode.Guid;
|
||||
var tmp = guid.Replace("-", "");
|
||||
var methodName = $"FlowMethod_{tmp}";
|
||||
return methodName;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsStaticClass(Type type)
|
||||
{
|
||||
return type.IsAbstract && type.IsSealed && type.IsClass;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private string GetCamelCase(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
// 获取类型名称(不包括命名空间)
|
||||
string typeName = type.Name;
|
||||
|
||||
if (string.IsNullOrEmpty(typeName))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// 处理泛型类型(去掉后面的`N)
|
||||
int indexOfBacktick = typeName.IndexOf('`');
|
||||
if (indexOfBacktick > 0)
|
||||
{
|
||||
typeName = typeName.Substring(0, indexOfBacktick);
|
||||
}
|
||||
|
||||
// 如果是接口且以"I"开头,去掉第一个字母
|
||||
if (type.IsInterface && typeName.Length > 1 && typeName[0] == 'I' && char.IsUpper(typeName[1]))
|
||||
{
|
||||
typeName = typeName.Substring(1);
|
||||
}
|
||||
|
||||
// 转换为驼峰命名法:首字母小写,其余不变
|
||||
if (typeName.Length > 0)
|
||||
{
|
||||
return char.ToLowerInvariant(typeName[0]) + typeName.Substring(1);
|
||||
}
|
||||
|
||||
return typeName;
|
||||
}
|
||||
|
||||
private Dictionary<IFlowNode, int> nodeIdMap = new Dictionary<IFlowNode, int>();
|
||||
private int GetNodeId(IFlowNode flowNode)
|
||||
{
|
||||
if (nodeIdMap.ContainsKey(flowNode))
|
||||
{
|
||||
return nodeIdMap[flowNode];
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (nodeIdMap)
|
||||
{
|
||||
int id = nodeIdMap.Count + 1; // 从1开始计数
|
||||
nodeIdMap[flowNode] = id;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* /// <summary>
|
||||
/// 生成方法名称
|
||||
/// </summary>
|
||||
/// <param name="flowNode"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private string GetNodeMethodName(IFlowNode flowNode)
|
||||
{
|
||||
return $"FlowMethod_{flowNode.Guid.Remove('-')}";
|
||||
|
||||
if (flowNode.ControlType == NodeControlType.Action)
|
||||
{
|
||||
if (!flowLibraryService.TryGetMethodInfo(flowNode.MethodDetails.AssemblyName,
|
||||
flowNode.MethodDetails.MethodName,
|
||||
out var methodInfo))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
return $"FlowMethod_{nameof(NodeControlType.Action)}_{methodInfo.Name}";
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.Flipflop)
|
||||
{
|
||||
if (!flowLibraryService.TryGetMethodInfo(flowNode.MethodDetails.AssemblyName,
|
||||
flowNode.MethodDetails.MethodName,
|
||||
out var methodInfo))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
return $"FlowMethod_{nameof(NodeControlType.Flipflop)}_{methodInfo.Name}";
|
||||
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.Script)
|
||||
{
|
||||
return $"FlowMethod_{flowNode.Guid.Remove('-')}";
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.UI)
|
||||
{
|
||||
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.ExpCondition)
|
||||
{
|
||||
|
||||
}
|
||||
else if (flowNode.ControlType == NodeControlType.ExpOp)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("无法为该节点生成方法名称");
|
||||
}
|
||||
}*/
|
||||
@@ -268,14 +268,16 @@ namespace Serein.NodeFlow.Services
|
||||
/// </summary>
|
||||
/// <param name="startNode"></param>
|
||||
/// <returns></returns>
|
||||
public async Task StartFlowInSelectNodeAsync(IFlowNode startNode)
|
||||
public async Task<FlowResult> StartFlowInSelectNodeAsync(IFlowNode startNode)
|
||||
{
|
||||
var pool = WorkOptions.FlowContextPool;
|
||||
var context = pool.Allocate();
|
||||
var token = WorkOptions.CancellationTokenSource.Token;
|
||||
var result = await startNode.StartFlowAsync(context, token); // 开始运行时从选定节点开始运行
|
||||
|
||||
context.Reset();
|
||||
pool.Free(context);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -342,7 +344,7 @@ namespace Serein.NodeFlow.Services
|
||||
{
|
||||
var context = pool.Allocate(); // 启动全局触发器时新建上下文
|
||||
var newFlowData = await singleFlipFlopNode.ExecutingAsync(context, singleToken); // 获取触发器等待Task
|
||||
context.AddOrUpdate(singleFlipFlopNode.Guid, newFlowData);
|
||||
context.AddOrUpdateFlowData(singleFlipFlopNode.Guid, newFlowData);
|
||||
if (context.NextOrientation == ConnectionInvokeType.None)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -14,10 +14,18 @@ using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Serein.NodeFlow
|
||||
{
|
||||
public class LibraryMdDd (MethodDetails methodDetails,DelegateDetails delegateDetails)
|
||||
public class LibraryMdDd
|
||||
{
|
||||
public MethodDetails MethodDetails { get; } = methodDetails;
|
||||
public DelegateDetails DelegateDetails { get; } = delegateDetails;
|
||||
public MethodDetails MethodDetails { get; }
|
||||
public MethodInfo MethodInfo { get; }
|
||||
public DelegateDetails DelegateDetails { get; }
|
||||
|
||||
public LibraryMdDd(MethodInfo methodInfo, MethodDetails methodDetails, DelegateDetails delegateDetails)
|
||||
{
|
||||
MethodDetails = methodDetails;
|
||||
MethodInfo = methodInfo;
|
||||
DelegateDetails = delegateDetails;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +36,6 @@ namespace Serein.NodeFlow
|
||||
{
|
||||
public Assembly Assembly { get; private set; }
|
||||
|
||||
|
||||
|
||||
//private readonly Action actionOfUnloadAssmbly;
|
||||
/*, Action actionOfUnloadAssmbly*/
|
||||
//this.actionOfUnloadAssmbly = actionOfUnloadAssmbly;
|
||||
@@ -60,6 +66,7 @@ namespace Serein.NodeFlow
|
||||
/// Value :方法详情
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<string, MethodDetails> MethodDetailss { get; } = new ConcurrentDictionary<string, MethodDetails>();
|
||||
public ConcurrentDictionary<string, MethodInfo> MethodInfos { get; } = new ConcurrentDictionary<string, MethodInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// 管理通过Emit动态构建的委托
|
||||
@@ -112,6 +119,7 @@ namespace Serein.NodeFlow
|
||||
public bool LoadAssembly()
|
||||
{
|
||||
Assembly assembly = this.Assembly;
|
||||
|
||||
#region 检查入参
|
||||
|
||||
// 加载DLL,创建 MethodDetails、实例作用对象、委托方法
|
||||
@@ -142,7 +150,7 @@ namespace Serein.NodeFlow
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 获取 DynamicFlow 特性的流程控制器,如果没有退出
|
||||
// Type : 具有 DynamicFlowAttribute 标记的类型
|
||||
// string : 类型元数据 DynamicFlowAttribute 特性中的 Name 属性 (用于生成方法描述时,添加在方法别名中提高可读性)
|
||||
@@ -183,13 +191,13 @@ namespace Serein.NodeFlow
|
||||
{
|
||||
// 尝试创建
|
||||
if (!NodeMethodDetailsHelper.TryCreateDetails(type, methodInfo, assemblyName,
|
||||
out var md, out var dd)) // 返回的描述
|
||||
out var mi, out var md, out var dd)) // 返回的描述
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.ERROR, $"无法加载方法信息:{assemblyName}-{type}-{methodInfo}");
|
||||
continue;
|
||||
}
|
||||
md.MethodAnotherName = flowName + md.MethodAnotherName; // 方法别名
|
||||
detailss.Add(new LibraryMdDd(md, dd));
|
||||
detailss.Add(new LibraryMdDd(mi, md, dd));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +227,7 @@ namespace Serein.NodeFlow
|
||||
{
|
||||
var key = item.MethodDetails.MethodName;
|
||||
MethodDetailss.TryAdd(key, item.MethodDetails);
|
||||
MethodInfos.TryAdd(key, item.MethodInfo);
|
||||
DelegateDetailss.TryAdd(key, item.DelegateDetails);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ public static class NodeMethodDetailsHelper
|
||||
public static bool TryCreateDetails(Type type,
|
||||
MethodInfo methodInfo,
|
||||
string assemblyName,
|
||||
[MaybeNullWhen(false)] out MethodInfo outMethodInfo,
|
||||
[MaybeNullWhen(false)] out MethodDetails methodDetails,
|
||||
[MaybeNullWhen(false)] out DelegateDetails delegateDetails)
|
||||
{
|
||||
@@ -43,6 +44,7 @@ public static class NodeMethodDetailsHelper
|
||||
var attribute = methodInfo.GetCustomAttribute<NodeActionAttribute>();
|
||||
if(attribute is null || attribute.Scan == false)
|
||||
{
|
||||
outMethodInfo = null;
|
||||
methodDetails = null;
|
||||
delegateDetails = null;
|
||||
return false;
|
||||
@@ -62,17 +64,18 @@ public static class NodeMethodDetailsHelper
|
||||
|
||||
|
||||
Type? returnType;
|
||||
bool isTask = IsGenericTask(methodInfo.ReturnType, out var taskResult);
|
||||
bool isAsync = IsGenericTask(methodInfo.ReturnType, out var taskResult);
|
||||
|
||||
|
||||
if (attribute.MethodDynamicType == Library.NodeType.UI)
|
||||
{
|
||||
if (isTask)
|
||||
if (isAsync)
|
||||
{
|
||||
var innerType = methodInfo.ReturnType.GetGenericArguments()[0];
|
||||
if (innerType.IsGenericType && innerType != typeof(IEmbeddedContent))
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, $"[{methodName}]跳过创建,因为UI方法的返回值并非IEmbeddedContent,流程工作台将无法正确显示自定义控件界面以及传递数据。");
|
||||
outMethodInfo = null;
|
||||
methodDetails = null;
|
||||
delegateDetails = null;
|
||||
return false;
|
||||
@@ -83,6 +86,7 @@ public static class NodeMethodDetailsHelper
|
||||
if (methodInfo.ReturnType != typeof(IEmbeddedContent))
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, $"[{methodName}]跳过创建,因为UI方法的返回值并非IEmbeddedContent,流程工作台将无法正确显示自定义控件界面以及传递数据。");
|
||||
outMethodInfo = null;
|
||||
methodDetails = null;
|
||||
delegateDetails = null;
|
||||
return false;
|
||||
@@ -106,6 +110,7 @@ public static class NodeMethodDetailsHelper
|
||||
else
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, $"[{methodName}]跳过创建,返回类型非预期的Task<IFlipflopContext<TResult>>。");
|
||||
outMethodInfo = null;
|
||||
methodDetails = null;
|
||||
delegateDetails = null;
|
||||
return false;
|
||||
@@ -114,13 +119,14 @@ public static class NodeMethodDetailsHelper
|
||||
else
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, $"[{methodName}]跳过创建,因为触发器方法的返回值并非Task<>,将无法等待。");
|
||||
outMethodInfo = null;
|
||||
methodDetails = null;
|
||||
delegateDetails = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
else if(isTask)
|
||||
else if(isAsync)
|
||||
{
|
||||
returnType = taskResult is null ? typeof(Task) : taskResult;
|
||||
}
|
||||
@@ -134,7 +140,7 @@ public static class NodeMethodDetailsHelper
|
||||
}
|
||||
|
||||
var asyncPrefix = "[异步]"; // IsGenericTask(returnType) ? "[async]" : ;
|
||||
var methodMethodAnotherName = isTask ? asyncPrefix + attribute.AnotherName : attribute.AnotherName;
|
||||
var methodMethodAnotherName = isAsync ? asyncPrefix + attribute.AnotherName : attribute.AnotherName;
|
||||
|
||||
bool hasParamsArg = false;
|
||||
if (explicitDataOfParameters.Length > 0)
|
||||
@@ -155,13 +161,14 @@ public static class NodeMethodDetailsHelper
|
||||
ReturnType = returnType,
|
||||
// 如果存在可变参数,取最后一个元素的下标,否则为-1;
|
||||
ParamsArgIndex = hasParamsArg ? explicitDataOfParameters.Length - 1 : -1,
|
||||
IsAsync = isAsync,
|
||||
};
|
||||
|
||||
//var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var methodDelegate);// 返回值
|
||||
|
||||
|
||||
var dd = new DelegateDetails(methodInfo) ; // 构造委托
|
||||
|
||||
outMethodInfo = methodInfo;
|
||||
methodDetails = md;
|
||||
delegateDetails = dd;
|
||||
return true;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.NodeFlow.Env;
|
||||
using Serein.NodeFlow.Services;
|
||||
using Serein.Workbench.Api;
|
||||
using Serein.Workbench.Services;
|
||||
using Serein.Workbench.ViewModels;
|
||||
@@ -54,6 +55,7 @@ namespace Serein.Workbench
|
||||
// 这里是测试代码,可以删除
|
||||
private async Task LoadLocalProjectAsync()
|
||||
{
|
||||
|
||||
if (1 == 11)
|
||||
{
|
||||
var projectService = App.GetService<FlowProjectService>();
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ namespace Serein.Workbench
|
||||
collection.AddSingleton<IFlowEnvironmentEvent>(flowEnvironment.Event); // 注册运行环境事件
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Serein.Workbench.Themes
|
||||
{
|
||||
try
|
||||
{
|
||||
await flowEnvironment.FlowControl.StartFlowFromSelectNodeAsync(tmpNodeTreeModel.RootNode.Guid);
|
||||
await flowEnvironment.FlowControl.StartFlowAsync<FlowResult>(tmpNodeTreeModel.RootNode.Guid);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Serein.Library.Api;
|
||||
using Serein.NodeFlow.Services;
|
||||
using Serein.Workbench.Services;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Serein.Workbench.ViewModels
|
||||
@@ -130,6 +132,20 @@ namespace Serein.Workbench.ViewModels
|
||||
|
||||
private void OpenRemoteServer()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
var env = App.GetService<IFlowEnvironment>();
|
||||
var flowModelService = env.IOC.Get<FlowModelService>();
|
||||
var text = flowModelService.ToCsharpCoreFile(); ;
|
||||
Debug.WriteLine(text);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
flowEnvironment.StartRemoteServerAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -551,7 +551,6 @@ namespace Serein.Workbench.Views
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (key == Key.F5)
|
||||
{
|
||||
if (keyEventService.GetKeyState(Key.LeftCtrl) || keyEventService.GetKeyState(Key.RightCtrl))
|
||||
@@ -564,7 +563,7 @@ namespace Serein.Workbench.Views
|
||||
// F5 调试当前选定节点
|
||||
var nodeModel = selectNodeControls[0].ViewModel.NodeModel;
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"调试运行当前节点:{nodeModel.Guid}");
|
||||
_ = flowEnvironment.FlowControl.StartFlowFromSelectNodeAsync(nodeModel.Guid);
|
||||
_ = flowEnvironment.FlowControl.StartFlowAsync<FlowResult>(nodeModel.Guid);
|
||||
//_ = nodeModel.StartFlowAsync(new DynamicContext(flowEnvironment), new CancellationToken());
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</MenuItem>
|
||||
<MenuItem Header="拓展">
|
||||
<!--<MenuItem Header="动态编译" ></MenuItem>-->
|
||||
<MenuItem Header="启动远程服务"></MenuItem>
|
||||
<MenuItem Header="启动远程服务" Command="{Binding OpenRemoteServerCommand}"></MenuItem>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user