尝试将节点流导出为c#代码文件

This commit is contained in:
fengjiayi
2025-07-06 14:34:49 +08:00
parent 162dc7bcf8
commit b25fd9c83c
45 changed files with 1625 additions and 361 deletions

View File

@@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser;
namespace Serein.NodeFlow.Env
{
@@ -25,11 +24,11 @@ namespace Serein.NodeFlow.Env
private readonly UIContextOperation UIContextOperation;
public FlowControl(IFlowEnvironment flowEnvironment,
IFlowEnvironmentEvent flowEnvironmentEvent,
FlowLibraryService flowLibraryService,
FlowOperationService flowOperationService,
FlowModelService flowModelService,
UIContextOperation UIContextOperation)
IFlowEnvironmentEvent flowEnvironmentEvent,
FlowLibraryService flowLibraryService,
FlowOperationService flowOperationService,
FlowModelService flowModelService,
UIContextOperation UIContextOperation)
{
this.flowEnvironment = flowEnvironment;
this.flowEnvironmentEvent = flowEnvironmentEvent;
@@ -285,38 +284,24 @@ namespace Serein.NodeFlow.Env
/// 调用流程接口,将返回 FlowResult.Value。如果需要 FlowResult 对象,请使用该方法的泛型版本。
/// </summary>
/// <param name="apiGuid">流程接口节点Guid</param>
/// <param name="param">调用时入参参数</param>
/// <param name="dict">调用时入参参数</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public async Task<object> ApiInvokeAsync(string apiGuid, object[] param)
public async Task<object> InvokeAsync(string apiGuid, Dictionary<string, object> dict)
{
if (sereinIOC is null)
{
sereinIOC = flowEnvironment.IOC;
}
if (!flowModelService.TryGetNodeModel(apiGuid, out var nodeModel))
{
throw new ArgumentNullException($"不存在流程接口:{apiGuid}");
}
if (nodeModel is not SingleFlowCallNode flowCallNode)
{
throw new ArgumentNullException($"目标节点并非流程接口:{apiGuid}");
}
var context = contexts.Allocate();
CancellationTokenSource cts = new CancellationTokenSource();
var flowResult = await flowCallNode.StartFlowAsync(context, cts.Token);
return flowResult.Value;
var result = await InvokeAsync<object>(apiGuid, dict);
return result;
}
/// <summary>
/// 调用流程接口,泛型类型为 FlowResult 时,将返回 FlowResult 对象。
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="apiGuid">流程接口节点Guid</param>
/// <param name="param">调用时入参参数</param>
/// <param name="dict">调用时入参参数</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public async Task<TResult> ApiInvokeAsync<TResult>(string apiGuid, object[] param)
public async Task<TResult> InvokeAsync<TResult>(string apiGuid, Dictionary<string, object> dict)
{
if (sereinIOC is null)
{
@@ -330,10 +315,25 @@ namespace Serein.NodeFlow.Env
{
throw new ArgumentNullException($"目标节点并非流程接口:{apiGuid}");
}
var context = contexts.Allocate();
var pds = flowCallNode.MethodDetails.ParameterDetailss;
if (dict.Keys.Count != pds.Length)
{
throw new ArgumentNullException($"参数数量不一致。传入参数数量:{dict.Keys.Count}。接口入参数量:{pds.Length}。");
}
IDynamicContext context = contexts.Allocate();
for (int index = 0; index < pds.Length; index++)
{
ParameterDetails pd = pds[index];
if (dict.TryGetValue(pd.Name, out var value))
{
context.SetParamsTempData(flowCallNode.Guid, index, value); // 设置入参参数
}
}
CancellationTokenSource cts = new CancellationTokenSource();
var flowResult = await flowCallNode.StartFlowAsync(context, cts.Token);
cts?.Cancel();
cts?.Dispose();
if (flowResult.Value is TResult result)
{
return result;

View File

@@ -1,11 +1,15 @@
using Serein.Library;
using Microsoft.CodeAnalysis;
using Serein.Library;
using Serein.Library.Api;
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;
using IOperation = Serein.NodeFlow.Model.Operation.IOperation;
namespace Serein.NodeFlow.Env
{
@@ -107,11 +111,13 @@ namespace Serein.NodeFlow.Env
/// 从节点信息创建节点,并返回状态指示是否创建成功
/// </summary>
/// <param name="nodeInfo"></param>
/// <param name="nodeModel"></param>
/// <returns></returns>
private bool CreateNodeFromNodeInfo(NodeInfo nodeInfo)
private bool CreateNodeFromNodeInfo(NodeInfo nodeInfo, out IFlowNode? nodeModel)
{
if (!EnumHelper.TryConvertEnum<NodeControlType>(nodeInfo.Type, out var controlType))
{
nodeModel = null;
return false;
}
@@ -146,37 +152,22 @@ namespace Serein.NodeFlow.Env
}
else
{
if (string.IsNullOrEmpty(nodeInfo.MethodName)) return false;
if (string.IsNullOrEmpty(nodeInfo.MethodName))
{
nodeModel = null;
return false;
}
// 加载方法节点
flowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息
}
#endregion
var nodeModel = FlowNodeExtension.CreateNode(flowEnvironment, controlType, methodDetails); // 加载项目时创建节点
nodeModel = FlowNodeExtension.CreateNode(flowEnvironment, controlType, methodDetails); // 加载项目时创建节点
if (nodeModel is null)
{
nodeInfo.Guid = string.Empty;
return false;
}
if (TryGetCanvasModel(nodeInfo.CanvasGuid, out var canvasModel))
{
// 节点与画布互相绑定
// 需要在UI线程上进行添加否则会报 “不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改”异常
nodeModel.CanvasDetails = canvasModel;
UIContextOperation?.Invoke(() => canvasModel.Nodes.Add(nodeModel));
nodeModel.LoadInfo(nodeInfo); // 创建节点model
TryAddNode(nodeModel); // 加载项目时将节点加载到环境中
}
else
{
SereinEnv.WriteLine(InfoType.ERROR, $"加载节点[{nodeInfo.Guid}]时发生异常,画布[{nodeInfo.CanvasGuid}]不存在");
return false;
}
UIContextOperation?.Invoke(() =>
flowEnvironmentEvent.OnNodeCreated(new NodeCreateEventArgs(nodeInfo.CanvasGuid, nodeModel, nodeInfo.Position))); // 添加到UI上
return true;
}
@@ -355,7 +346,10 @@ namespace Serein.NodeFlow.Env
}*/
canvasModel.StartNode = newStartNodeModel;
//newStartNode.IsStart = true;
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnStartNodeChanged(new StartNodeChangeEventArgs(canvasGuid, oldNodeGuid, newStartNodeModel.Guid)));
_ = TriggerEvent(() =>
flowEnvironmentEvent.OnStartNodeChanged(
new StartNodeChangeEventArgs(canvasGuid, oldNodeGuid, newStartNodeModel.Guid)
));
return;
}
@@ -395,6 +389,46 @@ namespace Serein.NodeFlow.Env
#region NodeInfo创建NodeModel
// 流程接口节点最后才创建
async Task AddNodeAsync(NodeInfo nodeInfo, IFlowNode nodeModel)
{
if (!TryGetCanvasModel(nodeInfo.CanvasGuid, out var canvasModel))
{
SereinEnv.WriteLine(InfoType.ERROR, $"加载节点[{nodeInfo.Guid}]时发生异常,画布[{nodeInfo.CanvasGuid}]不存在");
}
else
{
// 节点与画布互相绑定
// 需要在UI线程上进行添加否则会报 “不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改”异常
nodeModel.CanvasDetails = canvasModel;
await TriggerEvent(() =>
{
try
{
var nodes = canvasModel.Nodes.ToList();
nodes.Add(nodeModel);
canvasModel.Nodes = nodes;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}); // 添加到画布节点集合中
nodeModel.LoadInfo(nodeInfo); // 创建节点model
nodeModel.Guid ??= Guid.NewGuid().ToString();
flowModelService.AddNodeModel(nodeModel);
await TriggerEvent(() =>
flowEnvironmentEvent.OnNodeCreated(
new NodeCreateEventArgs(nodeInfo.CanvasGuid, nodeModel, nodeInfo.Position)
)
); // 创建节点事件
}
}
List<NodeInfo> flowCallNodeInfos = [];
foreach (NodeInfo? nodeInfo in nodeInfos)
{
@@ -404,10 +438,13 @@ namespace Serein.NodeFlow.Env
}
else
{
if (!CreateNodeFromNodeInfo(nodeInfo))
if (CreateNodeFromNodeInfo(nodeInfo, out var nodeModel) && nodeModel is not null)
{
await AddNodeAsync(nodeInfo, nodeModel);
}
else
{
SereinEnv.WriteLine(InfoType.WARN, $"节点创建失败。{Environment.NewLine}{nodeInfo}");
continue;
}
}
}
@@ -415,10 +452,13 @@ namespace Serein.NodeFlow.Env
// 创建流程接口节点
foreach (NodeInfo? nodeInfo in flowCallNodeInfos)
{
if (!CreateNodeFromNodeInfo(nodeInfo))
if (CreateNodeFromNodeInfo(nodeInfo, out var nodeModel) && nodeModel is not null)
{
await AddNodeAsync(nodeInfo, nodeModel);
}
else
{
SereinEnv.WriteLine(InfoType.WARN, $"节点创建失败。{Environment.NewLine}{nodeInfo}");
continue;
}
}
#endregion
@@ -444,8 +484,10 @@ namespace Serein.NodeFlow.Env
var result = nodeContainer.PlaceNode(nodeModel);
if (result)
{
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnNodePlace(
new NodePlaceEventArgs(nodeInfo.CanvasGuid, nodeModel.Guid, containerNode.Guid)));
await TriggerEvent(() =>
flowEnvironmentEvent.OnNodePlace(
new NodePlaceEventArgs(nodeInfo.CanvasGuid, nodeModel.Guid, containerNode.Guid)
));
}
@@ -484,30 +526,9 @@ namespace Serein.NodeFlow.Env
ConnectInvokeNode(canvasGuid, fromNodeModel.Guid, toNodeModel.Guid, JunctionType.NextStep, JunctionType.Execute, item.connectionType);
//var isSuccessful = ConnectInvokeOfNode(canvasGuid, fromNodeModel, toNodeModel, item.connectionType); // 加载时确定节点间的连接关系
}
}
//List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes),
// (ConnectionInvokeType.IsFail, nodeInfo.FalseNodes),
// (ConnectionInvokeType.IsError, nodeInfo.ErrorNodes),
// (ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)];
//List<(ConnectionInvokeType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0)
// .Select(info => (info.connectionType,
// info.guids.Where(guid => NodeModels.ContainsKey(guid)).Select(guid => NodeModels[guid])
// .ToArray()))
// .ToList();
// 遍历每种类型的节点分支(四种)
//foreach ((ConnectionInvokeType connectionType, NodeModelBase[] toNodes) item in nodeInfo)
//{
// // 遍历当前类型分支的节点(确认连接关系)
// foreach (var toNode in item.toNodes)
// {
// _ = ConnectInvokeOfNode(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系
// }
//}
}
#endregion
@@ -526,20 +547,13 @@ namespace Serein.NodeFlow.Env
if (!string.IsNullOrEmpty(pd.ArgDataSourceNodeGuid)
&& TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var fromNode))
{
ConnectArgSourceNode(canvasGuid, fromNode.Guid, toNode.Guid, JunctionType.ReturnData, JunctionType.ArgData, pd.ArgDataSourceType, pd.Index);
}
}
}
#endregion
UIContextOperation?.Invoke(() =>
{
flowEnvironmentEvent.OnProjectLoaded(new ProjectLoadedEventArgs());
});
return;
}
#endregion
@@ -561,8 +575,27 @@ namespace Serein.NodeFlow.Env
#endregion
private async Task TriggerEvent(Action action)
{
if(UIContextOperation is null)
{
action?.Invoke();
}
else
{
await UIContextOperation.InvokeAsync(() =>
{
action?.Invoke();
});
}
}
}
}

View File

@@ -51,8 +51,7 @@ namespace Serein.NodeFlow.Env
public FlowEnvironment()
{
ISereinIOC ioc = new SereinIOC();
ioc.Reset()
.Register<ISereinIOC>(()=> ioc) // 注册IOC
ioc.Register<ISereinIOC>(()=> ioc) // 注册IOC
.Register<IFlowEnvironment>(() => this)
.Register<IFlowEnvironmentEvent, FlowEnvironmentEvent>()
.Register<IFlowEdit, FlowEdit>()
@@ -205,11 +204,20 @@ namespace Serein.NodeFlow.Env
}
/// <inheritdoc/>
public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath)
public void LoadProject(string filePath)
{
if (flowEnvInfo is null) return;
//if (flowEnvInfo is null) return;
SetProjectLoadingFlag(false);
currentFlowEnvironment.LoadProject(flowEnvInfo, filePath);
currentFlowEnvironment.LoadProject(filePath);
SetProjectLoadingFlag(true);
}
/// <inheritdoc/>
public async Task LoadProjetAsync(string filePath)
{
//if (flowEnvInfo is null) return;
SetProjectLoadingFlag(false);
await currentFlowEnvironment.LoadProjetAsync(filePath);
SetProjectLoadingFlag(true);
}
@@ -307,11 +315,7 @@ namespace Serein.NodeFlow.Env
/// <inheritdoc/>
public void SetUIContextOperation(UIContextOperation uiContextOperation)
{
if(uiContextOperation is null)
{
return;
}
IOC.Register<UIContextOperation>(() => uiContextOperation).Build();
currentFlowEnvironment.SetUIContextOperation(uiContextOperation);
}

View File

@@ -1,4 +1,6 @@
using Serein.Library;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Newtonsoft.Json;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.FlowNode;
using Serein.Library.Utils;
@@ -15,6 +17,8 @@ using System.Reactive;
using System.Reflection;
using System.Security.AccessControl;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using static Serein.Library.Api.IFlowEnvironment;
namespace Serein.NodeFlow.Env
@@ -288,12 +292,75 @@ namespace Serein.NodeFlow.Env
/// <summary>
/// 加载项目文件
/// </summary>
/// <param name="flowEnvInfo">环境信息</param>
/// <param name="filePath"></param>
public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath)
public void LoadProject(string filePath)
{
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
var FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
var FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;//
this.ProjectFileLocation = filePath;
var projectData = flowEnvInfo.Project;
var projectData = FlowProjectData;
// 加载项目配置文件
var dllPaths = projectData.Librarys.Select(it => it.FilePath).ToList();
List<MethodDetails> methodDetailss = [];
// 遍历依赖项中的特性注解,生成方法详情
foreach (var dllPath in dllPaths)
{
string cleanedRelativePath = dllPath.TrimStart('.', '\\');
var tmpPath = Path.Combine(FileDataPath, cleanedRelativePath);
var dllFilePath = Path.GetFullPath(tmpPath);
LoadLibrary(dllFilePath); // 加载项目文件时加载对应的程序集
}
_ = Task.Run(async () =>
{
// 加载画布
try
{
foreach (var canvasInfo in projectData.Canvass)
{
await LoadCanvasAsync(canvasInfo);
}
var nodeInfos = projectData.Nodes.ToList();
await FlowEdit.LoadNodeInfosAsync(nodeInfos); // 加载节点信息
// 加载画布
foreach (var canvasInfo in projectData.Canvass)
{
FlowEdit.SetStartNode(canvasInfo.Guid, canvasInfo.StartNode); // 设置起始节点
}
Event.OnProjectLoaded(new ProjectLoadedEventArgs());
}
catch (Exception ex)
{
throw;
}
//
//await SetStartNodeAsync("", projectData.StartNode); // 设置起始节点
});
}
public async Task LoadProjetAsync(string filePath)
{
string content = await System.IO.File.ReadAllTextAsync(filePath); // 读取整个文件内容
var FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
var FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;//
if(FlowProjectData is null)
{
return;
}
this.ProjectFileLocation = filePath;
var projectData = FlowProjectData;
// 加载项目配置文件
var dllPaths = projectData.Librarys.Select(it => it.FilePath).ToList();
List<MethodDetails> methodDetailss = [];
@@ -309,23 +376,18 @@ namespace Serein.NodeFlow.Env
_ = Task.Run(async () =>
// 加载画布
foreach (var canvasInfo in projectData.Canvass)
{
// 加载画布
foreach (var canvasInfo in projectData.Canvass)
{
LoadCanvas(canvasInfo);
}
await FlowEdit.LoadNodeInfosAsync(projectData.Nodes.ToList()); // 加载节点信息
// 加载画布
foreach (var canvasInfo in projectData.Canvass)
{
FlowEdit.SetStartNode(canvasInfo.Guid, canvasInfo.StartNode); // 设置起始节点
}
//await SetStartNodeAsync("", projectData.StartNode); // 设置起始节点
});
await LoadCanvasAsync(canvasInfo);
}
await FlowEdit.LoadNodeInfosAsync(projectData.Nodes.ToList()); // 加载节点信息
// 加载画布
foreach (var canvasInfo in projectData.Canvass)
{
FlowEdit.SetStartNode(canvasInfo.Guid, canvasInfo.StartNode); // 设置起始节点
}
Event.OnProjectLoaded(new ProjectLoadedEventArgs());
}
/// <summary>
@@ -532,15 +594,24 @@ namespace Serein.NodeFlow.Env
private int _addCanvasCount = 0;
private FlowCanvasDetails LoadCanvas(FlowCanvasDetailsInfo info)
private async Task<FlowCanvasDetails> LoadCanvasAsync(FlowCanvasDetailsInfo info)
{
var model = new FlowCanvasDetails(this);
model.LoadInfo(info);
flowModelService.AddCanvasModel(model);
UIContextOperation?.Invoke(() =>
if(UIContextOperation is null)
{
Event.OnCanvasCreated(new CanvasCreateEventArgs(model));
});
}
else
{
await UIContextOperation.InvokeAsync(() =>
{
Event.OnCanvasCreated(new CanvasCreateEventArgs(model));
});
}
return model;
}
@@ -585,12 +656,12 @@ namespace Serein.NodeFlow.Env
/// <param name="uiContextOperation"></param>
public void SetUIContextOperation(UIContextOperation uiContextOperation)
{
if (uiContextOperation is not null)
if (uiContextOperation is null)
{
this.UIContextOperation = uiContextOperation;
//PersistennceInstance[typeof(UIContextOperation)] = uiContextOperation; // 缓存封装好的UI线程上下文
return;
}
this.UIContextOperation = uiContextOperation;
IOC.Register<UIContextOperation>(() => uiContextOperation).Build();
}