准备添加流程接口调用

This commit is contained in:
fengjiayi
2025-07-04 21:31:07 +08:00
parent 340ff7770f
commit 162dc7bcf8
23 changed files with 1401 additions and 1698 deletions

View File

@@ -10,7 +10,7 @@ namespace Serein.Library.Api
/// <summary> /// <summary>
/// 流程中的控件 /// 流程中的控件
/// </summary> /// </summary>
public interface IFlowControl public interface IFlowUIControl
{ {
/// <summary> /// <summary>
/// 节点执行事件 /// 节点执行事件
@@ -33,7 +33,7 @@ namespace Serein.Library.Api
/// 获取窗体控件 /// 获取窗体控件
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
IFlowControl GetFlowControl(); IFlowUIControl GetFlowControl();
} }

View File

@@ -0,0 +1,68 @@
using System.Threading.Tasks;
namespace Serein.Library.Api
{
/// <summary>
/// 流程运行接口
/// </summary>
public interface IFlowControl
{
/// <summary>
/// <para>需要你提供一个由你实现的ISereinIOC接口实现类</para>
/// <para>当你将流程运行环境集成在你的项目时,并希望流程运行时使用你提供的对象,而非自动创建</para>
/// <para>就需要你调用这个方法用来替换运行环境的IOC容器</para>
/// <para>注意,是流程运行时,而非运行环境</para>
/// </summary>
/// <param name="ioc"></param>
void UseExternalIOC(ISereinIOC ioc);
/// <summary>
/// 开始运行流程
/// </summary>
/// <param name="canvasGuids">需要运行的流程Guid</param>
/// <returns></returns>
Task<bool> StartFlowAsync(string[] canvasGuids);
/// <summary>
/// 从选定的节点开始运行
/// </summary>
/// <param name="startNodeGuid"></param>
/// <returns></returns>
Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid);
/// <summary>
/// 结束运行
/// </summary>
Task<bool> ExitFlowAsync();
/// <summary>
/// 激活未启动的全局触发器
/// </summary>
/// <param name="nodeGuid"></param>
void ActivateFlipflopNode(string nodeGuid);
/// <summary>
/// 终结一个全局触发器,在它触发后将不会再次监听消息(表现为已经启动的触发器至少会再次处理一次消息,后面版本再修正这个非预期行为)
/// </summary>
/// <param name="nodeGuid"></param>
void TerminateFlipflopNode(string nodeGuid);
/// <summary>
/// 流程启动器调用,监视数据更新通知
/// </summary>
/// <param name="nodeGuid">更新了数据的节点Guid</param>
/// <param name="monitorData">更新的数据</param>
/// <param name="sourceType">更新的数据</param>
void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType);
/// <summary>
/// 流程启动器调用,节点触发了中断
/// </summary>
/// <param name="nodeGuid">被中断的节点Guid</param>
/// <param name="expression">被触发的表达式</param>
/// <param name="type">中断类型。0主动监视1表达式</param>
void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type);
}
}

169
Library/Api/IFlowEdit.cs Normal file
View File

@@ -0,0 +1,169 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Serein.Library.Api
{
/// <summary>
/// 流程编辑
/// </summary>
public interface IFlowEdit
{
/// <summary>
/// 节点视图模型管理类
/// </summary>
NodeMVVMService NodeMVVMManagement { get; }
/// <summary>
/// 从节点信息集合批量加载节点控件
/// </summary>
/// <param name="nodeInfos">节点集合信息</param>
/// <returns></returns>
Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos);
#region
/// <summary>
/// 增加画布
/// </summary>
/// <param name="canvasName">画布名称</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <returns></returns>
void CreateCanvas(string canvasName, int width, int height);
/// <summary>
/// 删除画布
/// </summary>
/// <param name="canvasGuid">画布Guid</param>
/// <returns></returns>
void RemoveCanvas(string canvasGuid);
/// <summary>
/// 在两个节点之间创建连接关系
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="fromNodeGuid">起始节点Guid</param>
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="fromNodeJunctionType">起始节点控制点</param>
/// <param name="toNodeJunctionType">目标节点控制点</param>
/// <param name="invokeType">决定了方法执行后的后继行为</param>
void ConnectInvokeNode(string canvasGuid,
string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionInvokeType invokeType);
/// <summary>
/// 在两个节点之间创建连接关系
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="fromNodeGuid">起始节点Guid</param>
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="fromNodeJunctionType">起始节点控制点</param>
/// <param name="toNodeJunctionType">目标节点控制点</param>
/// <param name="argSourceType">决定了方法参数来源</param>
/// <param name="argIndex">设置第几个参数</param>
void ConnectArgSourceNode(string canvasGuid,
string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionArgSourceType argSourceType,
int argIndex);
/// <summary>
/// 移除两个节点之间的方法调用关系
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接类型</param>
void RemoveInvokeConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType);
/// <summary>
/// 移除连接节点之间参数传递的关系
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="fromNodeGuid">起始节点Guid</param>
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="argIndex">连接到第几个参数</param>
void RemoveArgSourceConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex);
/// <summary>
/// 创建节点
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeType">控件类型</param>
/// <param name="position">节点在画布上的位置(</param>
/// <param name="methodDetailsInfo">节点绑定的方法说明</param>
void CreateNode(string canvasGuid, NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null);
/// <summary>
/// 移除节点
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeGuid">待移除的节点Guid</param>
void RemoveNode(string canvasGuid, string nodeGuid);
/// <summary>
/// 将节点放置在容器中
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeGuid">需要放置的节点Guid</param>
/// <param name="containerNodeGuid">存放节点的容器Guid</param>
/// <returns></returns>
void PlaceNodeToContainer(string canvasGuid, string nodeGuid, string containerNodeGuid);
/// <summary>
/// 将节点放置在容器中
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeGuid">需要取出的节点Guid</param>
void TakeOutNodeToContainer(string canvasGuid, string nodeGuid);
/// <summary>
/// 设置流程起点节点
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeGuid">尝试设置为起始节点的节点Guid</param>
/// <returns>被设置为起始节点的Guid</returns>
void SetStartNode(string canvasGuid, string nodeGuid);
/// <summary>
/// 设置两个节点某个类型的方法调用关系为优先调用
/// </summary>
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接关系</param>
/// <returns></returns>
void SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType);
/// <summary>
/// 改变可选参数的数目
/// </summary>
/// <param name="nodeGuid">对应的节点Guid</param>
/// <param name="isAdd">true增加参数false减少参数</param>
/// <param name="paramIndex">以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数)</param>
/// <returns></returns>
void ChangeParameter(string nodeGuid, bool isAdd, int paramIndex);
#endregion
#region UI视觉
/// <summary>
/// 节点定位
/// </summary>
/// <param name="nodeGuid"></param>
void NodeLocate(string nodeGuid);
#endregion
}
}

View File

@@ -748,7 +748,7 @@ namespace Serein.Library.Api
public void OnEnvOutput(InfoType type, string value); public void OnEnvOutput(InfoType type, string value);
} }
/// <summary> /// <summary>
/// 运行环境 /// 运行环境
@@ -764,6 +764,15 @@ namespace Serein.Library.Api
/// </summary> /// </summary>
ISereinIOC IOC { get; } ISereinIOC IOC { get; }
/// <summary>
/// 流程编辑接口
/// </summary>
IFlowEdit FlowEdit { get; }
/// <summary>
/// 流程控制接口
/// </summary>
IFlowControl FlowControl { get; }
/// <summary> /// <summary>
/// 流程事件接口 /// 流程事件接口
@@ -811,10 +820,6 @@ namespace Serein.Library.Api
/// </summary> /// </summary>
UIContextOperation UIContextOperation { get; } UIContextOperation UIContextOperation { get; }
/// <summary>
/// 节点视图模型管理类
/// </summary>
NodeMVVMService NodeMVVMManagement { get; }
#endregion #endregion
#region #region
@@ -826,6 +831,17 @@ namespace Serein.Library.Api
/// <param name="type">输出类型</param> /// <param name="type">输出类型</param>
/// <param name="class">输出级别</param> /// <param name="class">输出级别</param>
void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial); void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial);
/// <summary>
/// <para>提供设置UI上下文的能力</para>
/// <para>提供设置UI上下文的能力在WinForm/WPF项目中在UI线程外对UI元素的修改将会导致异常</para>
/// <para>需要你提供</para>
/// </summary>
/// <param name="uiContextOperation"></param>
void SetUIContextOperation(UIContextOperation uiContextOperation);
#endregion
#region
/// <summary> /// <summary>
/// 加载项目文件 /// 加载项目文件
@@ -845,15 +861,69 @@ namespace Serein.Library.Api
/// <returns></returns> /// <returns></returns>
Task<SereinProjectData> GetProjectInfoAsync(); Task<SereinProjectData> GetProjectInfoAsync();
#endregion
#region Emit委托
/// <summary> /// <summary>
/// 节点信息集合批量加载节点控件 /// 获取节点信息
/// </summary> /// </summary>
/// <param name="nodeInfos">节点集合信息</param> /// <param name="nodeGuid"></param>
/// <param name="nodeModel"></param>
/// <returns></returns> /// <returns></returns>
Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos); bool TryGetNodeModel(string nodeGuid, out IFlowNode nodeModel);
/// <summary>
/// 获取方法描述信息
/// </summary>
/// <param name="assemblyName">程序集名称</param>
/// <param name="methodName">方法描述</param>
/// <param name="mdInfo">方法信息</param>
/// <returns></returns>
bool TryGetMethodDetailsInfo(string assemblyName, string methodName, out MethodDetailsInfo mdInfo);
/// <summary>
/// 获取指定方法的Emit委托
/// </summary>
/// <param name="assemblyName">程序集名称</param>
/// <param name="methodName"></param>
/// <param name="del"></param>
/// <returns></returns>
bool TryGetDelegateDetails(string assemblyName, string methodName, out DelegateDetails del);
#endregion
#region
/// <summary>
/// 从文件中加载Dll
/// </summary>
/// <param name="dllPath"></param>
void LoadLibrary(string dllPath);
/// <summary>
/// 移除DLL
/// </summary>
/// <param name="assemblyFullName">程序集的名称</param>
bool TryUnloadLibrary(string assemblyFullName);
/// <summary>
/// 运行时加载
/// </summary>
/// <param name="file">文件名</param>
/// <returns></returns>
bool LoadNativeLibraryOfRuning(string file);
/// <summary>
/// 运行时加载指定目录下的类库
/// </summary>
/// <param name="path">目录</param>
/// <param name="isRecurrence">是否递归加载</param>
void LoadAllNativeLibraryOfRuning(string path, bool isRecurrence = true);
#endregion #endregion
#region #region
/// <summary> /// <summary>
/// 启动远程服务 /// 启动远程服务
@@ -895,142 +965,9 @@ namespace Serein.Library.Api
#endregion #endregion
#region
/// <summary>
/// 增加画布
/// </summary>
/// <param name="canvasName">画布名称</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <returns></returns>
void CreateCanvas(string canvasName, int width , int height);
/// <summary>
/// 删除画布
/// </summary>
/// <param name="canvasGuid">画布Guid</param>
/// <returns></returns>
void RemoveCanvas(string canvasGuid);
#region
/// <summary>
/// 在两个节点之间创建连接关系
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="fromNodeGuid">起始节点Guid</param>
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="fromNodeJunctionType">起始节点控制点</param>
/// <param name="toNodeJunctionType">目标节点控制点</param>
/// <param name="invokeType">决定了方法执行后的后继行为</param>
void ConnectInvokeNode(string canvasGuid,
string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionInvokeType invokeType);
/// <summary>
/// 在两个节点之间创建连接关系
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="fromNodeGuid">起始节点Guid</param>
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="fromNodeJunctionType">起始节点控制点</param>
/// <param name="toNodeJunctionType">目标节点控制点</param>
/// <param name="argSourceType">决定了方法参数来源</param>
/// <param name="argIndex">设置第几个参数</param>
void ConnectArgSourceNode(string canvasGuid,
string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionArgSourceType argSourceType,
int argIndex);
/// <summary>
/// 移除两个节点之间的方法调用关系
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接类型</param>
void RemoveInvokeConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType);
/// <summary>
/// 移除连接节点之间参数传递的关系
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="fromNodeGuid">起始节点Guid</param>
/// <param name="toNodeGuid">目标节点Guid</param>
/// <param name="argIndex">连接到第几个参数</param>
void RemoveArgSourceConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex);
/// <summary>
/// 创建节点
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeType">控件类型</param>
/// <param name="position">节点在画布上的位置(</param>
/// <param name="methodDetailsInfo">节点绑定的方法说明</param>
void CreateNode(string canvasGuid, NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null);
/// <summary>
/// 移除节点
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeGuid">待移除的节点Guid</param>
void RemoveNode(string canvasGuid, string nodeGuid);
/// <summary>
/// 将节点放置在容器中
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeGuid">需要放置的节点Guid</param>
/// <param name="containerNodeGuid">存放节点的容器Guid</param>
/// <returns></returns>
void PlaceNodeToContainer(string canvasGuid, string nodeGuid, string containerNodeGuid);
/// <summary>
/// 将节点放置在容器中
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeGuid">需要取出的节点Guid</param>
void TakeOutNodeToContainer(string canvasGuid, string nodeGuid);
/// <summary>
/// 设置流程起点节点
/// </summary>
/// <param name="canvasGuid">所在画布</param>
/// <param name="nodeGuid">尝试设置为起始节点的节点Guid</param>
/// <returns>被设置为起始节点的Guid</returns>
void SetStartNode(string canvasGuid, string nodeGuid);
/// <summary>
/// 设置两个节点某个类型的方法调用关系为优先调用
/// </summary>
/// <param name="fromNodeGuid">起始节点</param>
/// <param name="toNodeGuid">目标节点</param>
/// <param name="connectionType">连接关系</param>
/// <returns></returns>
void SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType);
/// <summary>
/// 改变可选参数的数目
/// </summary>
/// <param name="nodeGuid">对应的节点Guid</param>
/// <param name="isAdd">true增加参数false减少参数</param>
/// <param name="paramIndex">以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数)</param>
/// <returns></returns>
void ChangeParameter(string nodeGuid, bool isAdd, int paramIndex);
#endregion
#region
#if false #if false
/// <summary> /// <summary>
@@ -1074,137 +1011,8 @@ namespace Serein.Library.Api
#endif #endif
#endregion #endregion
#region
/// <summary>
/// 获取节点信息
/// </summary>
/// <param name="nodeGuid"></param>
/// <param name="nodeModel"></param>
/// <returns></returns>
bool TryGetNodeModel(string nodeGuid, out IFlowNode nodeModel);
/// <summary>
/// 获取方法描述信息
/// </summary>
/// <param name="assemblyName">程序集名称</param>
/// <param name="methodName">方法描述</param>
/// <param name="mdInfo">方法信息</param>
/// <returns></returns>
bool TryGetMethodDetailsInfo(string assemblyName, string methodName, out MethodDetailsInfo mdInfo);
/// <summary>
/// 获取指定方法的Emit委托
/// </summary>
/// <param name="assemblyName">程序集名称</param>
/// <param name="methodName"></param>
/// <param name="del"></param>
/// <returns></returns>
bool TryGetDelegateDetails(string assemblyName, string methodName, out DelegateDetails del);
/// <summary>
/// <para>提供设置UI上下文的能力</para>
/// <para>提供设置UI上下文的能力在WinForm/WPF项目中在UI线程外对UI元素的修改将会导致异常</para>
/// <para>需要你提供</para>
/// </summary>
/// <param name="uiContextOperation"></param>
void SetUIContextOperation(UIContextOperation uiContextOperation);
/// <summary>
/// <para>需要你提供一个由你实现的ISereinIOC接口实现类</para>
/// <para>当你将流程运行环境集成在你的项目时,并希望流程运行时使用你提供的对象,而非自动创建</para>
/// <para>就需要你调用这个方法用来替换运行环境的IOC容器</para>
/// <para>注意,是流程运行时,而非运行环境</para>
/// </summary>
/// <param name="ioc"></param>
void UseExternalIOC(ISereinIOC ioc);
/// <summary>
/// 开始运行流程
/// </summary>
/// <param name="canvasGuids">需要运行的流程Guid</param>
/// <returns></returns>
Task<bool> StartFlowAsync(string[] canvasGuids);
/// <summary>
/// 从选定的节点开始运行
/// </summary>
/// <param name="startNodeGuid"></param>
/// <returns></returns>
Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid);
/// <summary>
/// 结束运行
/// </summary>
Task<bool> ExitFlowAsync();
/// <summary>
/// 激活未启动的全局触发器
/// </summary>
/// <param name="nodeGuid"></param>
void ActivateFlipflopNode(string nodeGuid);
/// <summary>
/// 终结一个全局触发器,在它触发后将不会再次监听消息(表现为已经启动的触发器至少会再次处理一次消息,后面版本再修正这个非预期行为)
/// </summary>
/// <param name="nodeGuid"></param>
void TerminateFlipflopNode(string nodeGuid);
/// <summary>
/// 流程启动器调用,监视数据更新通知
/// </summary>
/// <param name="nodeGuid">更新了数据的节点Guid</param>
/// <param name="monitorData">更新的数据</param>
/// <param name="sourceType">更新的数据</param>
void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType);
/// <summary>
/// 流程启动器调用,节点触发了中断
/// </summary>
/// <param name="nodeGuid">被中断的节点Guid</param>
/// <param name="expression">被触发的表达式</param>
/// <param name="type">中断类型。0主动监视1表达式</param>
void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type);
#endregion
#region
/// <summary>
/// 从文件中加载Dll
/// </summary>
/// <param name="dllPath"></param>
void LoadLibrary(string dllPath);
/// <summary>
/// 移除DLL
/// </summary>
/// <param name="assemblyFullName">程序集的名称</param>
bool TryUnloadLibrary(string assemblyFullName);
/// <summary>
/// 运行时加载
/// </summary>
/// <param name="file">文件名</param>
/// <returns></returns>
bool LoadNativeLibraryOfRuning(string file);
/// <summary>
/// 运行时加载指定目录下的类库
/// </summary>
/// <param name="path">目录</param>
/// <param name="isRecurrence">是否递归加载</param>
void LoadAllNativeLibraryOfRuning(string path, bool isRecurrence = true);
#endregion
#region UI视觉
/// <summary>
/// 节点定位
/// </summary>
/// <param name="nodeGuid"></param>
void NodeLocate(string nodeGuid);
#endregion
} }
} }

View File

@@ -200,25 +200,24 @@ namespace Serein.Library
/// <param name="context"></param> /// <param name="context"></param>
/// <param name="token">流程运行</param> /// <param name="token">流程运行</param>
/// <returns></returns> /// <returns></returns>
public static async Task StartFlowAsync(this IFlowNode nodeModel, IDynamicContext context, CancellationToken token) public static async Task<FlowResult> StartFlowAsync(this IFlowNode nodeModel, IDynamicContext context, CancellationToken token)
{ {
Stack<IFlowNode> stack = new Stack<IFlowNode>(); Stack<IFlowNode> stack = new Stack<IFlowNode>();
HashSet<IFlowNode> processedNodes = new HashSet<IFlowNode>(); // 用于记录已处理上游节点的节点 HashSet<IFlowNode> processedNodes = new HashSet<IFlowNode>(); // 用于记录已处理上游节点的节点
stack.Push(nodeModel); stack.Push(nodeModel);
while (context.RunState != RunState.Completion // 没有完成
&& token.IsCancellationRequested == false // 没有取消 while (true)
&& stack.Count > 0) // 循环中直到栈为空才会退出循环
{ {
#if DEBUG if (token.IsCancellationRequested)
await Task.Delay(1); {
#endif throw new Exception($"流程执行被取消,未能获取到流程结果。");
}
#region #region
// 从栈中弹出一个节点作为当前节点进行处理 // 从栈中弹出一个节点作为当前节点进行处理
var currentNode = stack.Pop(); var currentNode = stack.Pop();
context.NextOrientation = ConnectionInvokeType.None; // 重置上下文状态 context.NextOrientation = ConnectionInvokeType.None; // 重置上下文状态
FlowResult flowResult = null;
FlowResult flowResult;
try try
{ {
flowResult = await currentNode.ExecutingAsync(context, token); flowResult = await currentNode.ExecutingAsync(context, token);
@@ -230,18 +229,15 @@ namespace Serein.Library
} }
catch (Exception ex) catch (Exception ex)
{ {
flowResult = new FlowResult(currentNode,context); flowResult = new FlowResult(currentNode, context);
context.Env.WriteLine(InfoType.ERROR, $"节点[{currentNode.Guid}]异常:" + ex); context.Env.WriteLine(InfoType.ERROR, $"节点[{currentNode.Guid}]异常:" + ex);
context.NextOrientation = ConnectionInvokeType.IsError; context.NextOrientation = ConnectionInvokeType.IsError;
context.ExceptionOfRuning = ex; context.ExceptionOfRuning = ex;
} }
#endregion #endregion
#region #region
//var ignodeState = context.GetIgnodeFlowStateUpload(currentNode); context.AddOrUpdate(currentNode, flowResult); // 上下文中更新数据
// 更新数据
//if(!ignodeState)
context.AddOrUpdate(currentNode, flowResult); // 上下文中更新数据
// 首先将指定类别后继分支的所有节点逆序推入栈中 // 首先将指定类别后继分支的所有节点逆序推入栈中
var nextNodes = currentNode.SuccessorNodes[context.NextOrientation]; var nextNodes = currentNode.SuccessorNodes[context.NextOrientation];
@@ -255,6 +251,7 @@ namespace Serein.Library
stack.Push(nextNodes[index]); stack.Push(nextNodes[index]);
} }
} }
// 然后将指上游分支的所有节点逆序推入栈中 // 然后将指上游分支的所有节点逆序推入栈中
var upstreamNodes = currentNode.SuccessorNodes[ConnectionInvokeType.Upstream]; var upstreamNodes = currentNode.SuccessorNodes[ConnectionInvokeType.Upstream];
for (int index = upstreamNodes.Count - 1; index >= 0; index--) for (int index = upstreamNodes.Count - 1; index >= 0; index--)
@@ -262,17 +259,40 @@ namespace Serein.Library
// 筛选出启用的节点的节点 // 筛选出启用的节点的节点
if (upstreamNodes[index].DebugSetting.IsEnable) if (upstreamNodes[index].DebugSetting.IsEnable)
{ {
//if (!ignodeState) context.SetPreviousNode(upstreamNodes[index], currentNode);
context.SetPreviousNode(upstreamNodes[index], currentNode);
stack.Push(upstreamNodes[index]); stack.Push(upstreamNodes[index]);
} }
} }
//context.RecoverIgnodeFlowStateUpload(currentNode);
#endregion #endregion
#region
if (stack.Count == 0)
{
return flowResult; // 说明流程到了终点
}
if (context.RunState == RunState.Completion)
{
currentNode.Env.WriteLine(InfoType.INFO, $"流程执行到节点[{currentNode.Guid}]时提前结束,将返回当前执行结果。");
return flowResult; // 流程执行完成,返回结果
}
if (token.IsCancellationRequested)
{
throw new Exception($"流程执行到节点[{currentNode.Guid}]时被取消,未能获取到流程结果。");
}
#endregion
#if DEBUG
await Task.Delay(1);
#endif
} }
} }
/// <summary> /// <summary>
/// 获取对应的参数数组 /// 获取对应的参数数组
/// </summary> /// </summary>
@@ -478,5 +498,12 @@ namespace Serein.Library
} }
#endif #endif
} }
} }

View File

@@ -32,10 +32,11 @@ namespace Serein.Library
/// <param name="context"></param> /// <param name="context"></param>
public FlowResult(IFlowNode nodeModel, IDynamicContext context, object value) public FlowResult(IFlowNode nodeModel, IDynamicContext context, object value)
{ {
this.NodeGuid = nodeModel.Guid; this.Source = nodeModel;
this.ContextGuid = context.Guid; this.ContextGuid = context.Guid;
this.Value = value; this.Value = value;
} }
/// <summary> /// <summary>
/// 空返回值 /// 空返回值
/// </summary> /// </summary>
@@ -43,10 +44,11 @@ namespace Serein.Library
/// <param name="context"></param> /// <param name="context"></param>
public FlowResult(IFlowNode nodeModel, IDynamicContext context) public FlowResult(IFlowNode nodeModel, IDynamicContext context)
{ {
this.NodeGuid = nodeModel.Guid; this.Source = nodeModel;
this.ContextGuid = context.Guid; this.ContextGuid = context.Guid;
this.Value = Unit.Default; this.Value = Unit.Default;
} }
/// <summary> /// <summary>
/// 尝试获取值 /// 尝试获取值
/// </summary> /// </summary>
@@ -74,7 +76,7 @@ namespace Serein.Library
/// <summary> /// <summary>
/// 来源节点Guid /// 来源节点Guid
/// </summary> /// </summary>
public string NodeGuid { get; } public IFlowNode Source{ get; }
/// <summary> /// <summary>
/// 来源上下文Guid /// 来源上下文Guid
/// </summary> /// </summary>
@@ -83,6 +85,7 @@ namespace Serein.Library
/// 数据值 /// 数据值
/// </summary> /// </summary>
public object Value { get; private set; } public object Value { get; private set; }
/// <summary> /// <summary>
/// 生成时间 /// 生成时间
/// </summary> /// </summary>

375
NodeFlow/Env/FlowControl.cs Normal file
View File

@@ -0,0 +1,375 @@
using Serein.Library;
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;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser;
namespace Serein.NodeFlow.Env
{
internal class FlowControl : IFlowControl
{
private readonly IFlowEnvironment flowEnvironment;
private readonly IFlowEnvironmentEvent flowEnvironmentEvent;
private readonly FlowLibraryService flowLibraryService;
private readonly FlowOperationService flowOperationService;
private readonly FlowModelService flowModelService;
private readonly UIContextOperation UIContextOperation;
public FlowControl(IFlowEnvironment flowEnvironment,
IFlowEnvironmentEvent flowEnvironmentEvent,
FlowLibraryService flowLibraryService,
FlowOperationService flowOperationService,
FlowModelService flowModelService,
UIContextOperation UIContextOperation)
{
this.flowEnvironment = flowEnvironment;
this.flowEnvironmentEvent = flowEnvironmentEvent;
this.flowLibraryService = flowLibraryService;
this.flowOperationService = flowOperationService;
this.flowModelService = flowModelService;
this.UIContextOperation = UIContextOperation;
contexts = new ObjectPool<IDynamicContext>(() => new DynamicContext(flowEnvironment));
}
private ObjectPool<IDynamicContext> contexts;
private FlowWorkManagement flowWorkManagement;
private ISereinIOC sereinIOC;
/// <summary>
/// 如果全局触发器还在运行,则为 Running 。
/// </summary>
private RunState FlipFlopState = RunState.NoStart;
/// <summary>
/// 异步运行
/// </summary>
/// <returns></returns>
public async Task<bool> StartFlowAsync(string[] canvasGuids)
{
#region
HashSet<string> guids = new HashSet<string>();
bool isBreak = false;
foreach (var canvasGuid in canvasGuids)
{
if (guids.Contains(canvasGuid))
{
flowEnvironment.WriteLine(InfoType.WARN, $"画布重复,停止运行。{canvasGuid}");
isBreak = true;
}
else if (!flowModelService.ContainsCanvasModel(canvasGuid))
{
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在,停止运行。{canvasGuid}");
isBreak = true;
}
else if (!flowModelService.IsExsitNodeOnCanvas(canvasGuid))
{
SereinEnv.WriteLine(InfoType.WARN, $"画布没有节点,停止运行。{canvasGuid}");
isBreak = true;
}
else
{
guids.Add(canvasGuid);
}
}
if (isBreak)
{
guids.Clear();
return false;
}
#endregion
#region
Dictionary<string, FlowTask> flowTasks = [];
foreach (var guid in guids)
{
if (!flowModelService.TryGetCanvasModel(guid, out var canvasModel))
{
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在,停止运行。{guid}");
return false;
}
var ft = new FlowTask();
ft.GetNodes = () => flowModelService.GetAllNodeModel(guid);
if (canvasModel.StartNode?.Guid is null)
{
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在起始节点,将停止运行。{guid}");
return false;
}
ft.GetStartNode = () => canvasModel.StartNode;
flowTasks.Add(guid, ft);
}
#endregion
sereinIOC.Reset();
sereinIOC.Register<IFlowEnvironment>(() => flowEnvironment);
sereinIOC.Register<IScriptFlowApi, ScriptFlowApi>(); // 注册脚本接口
var flowTaskOptions = new FlowWorkOptions
{
Environment = flowEnvironment, // 流程
Flows = flowTasks,
FlowContextPool = contexts, // 上下文对象池
AutoRegisterTypes = flowLibraryService.GetaAutoRegisterType(), // 需要自动实例化的类型
InitMds = flowLibraryService.GetMdsOnFlowStart(NodeType.Init),
LoadMds = flowLibraryService.GetMdsOnFlowStart(NodeType.Loading),
ExitMds = flowLibraryService.GetMdsOnFlowStart(NodeType.Exit),
};
flowWorkManagement = new FlowWorkManagement(flowTaskOptions);
var cts = new CancellationTokenSource();
try
{
var t = await flowWorkManagement.RunAsync(cts.Token);
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
}
finally
{
SereinEnv.WriteLine(InfoType.INFO, $"流程运行完毕{Environment.NewLine}"); ;
}
flowTaskOptions = null;
return true;
}
/// <summary>
/// 从选定节点开始运行
/// </summary>
/// <param name="startNodeGuid"></param>
/// <returns></returns>
public async Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid)
{
var flowTaskOptions = new FlowWorkOptions
{
Environment = flowEnvironment, // 流程
FlowContextPool = contexts, // 上下文对象池
};
var flowTaskManagement = new FlowWorkManagement(flowTaskOptions);
if (true || flowEnvironment.FlowState == RunState.Running || FlipFlopState == RunState.Running)
{
if (!flowModelService.TryGetNodeModel(startNodeGuid, out var nodeModel) || nodeModel is SingleFlipflopNode)
{
return false;
}
await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel);
return true;
}
else
{
return false;
}
}
/*/// <summary>
/// 单独运行一个节点
/// </summary>
/// <param name="nodeGuid"></param>
/// <returns></returns>
public async Task<object> InvokeNodeAsync(IDynamicContext context, string nodeGuid)
{
object result = Unit.Default;
if (this.NodeModels.TryGetValue(nodeGuid, out var model))
{
CancellationTokenSource cts = new CancellationTokenSource();
result = await model.ExecutingAsync(context, cts.Token);
cts?.Cancel();
}
return result;
}*/
/// <summary>
/// 结束流程
/// </summary>
public Task<bool> ExitFlowAsync()
{
flowWorkManagement?.Exit();
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnFlowRunComplete(new FlowEventArgs()));
sereinIOC.Reset();
flowWorkManagement = null;
GC.Collect();
return Task.FromResult(true);
}
/// <summary>
/// 激活全局触发器
/// </summary>
/// <param name="nodeGuid"></param>
public void ActivateFlipflopNode(string nodeGuid)
{
/*if (!TryGetNodeModel(nodeGuid, out var nodeModel))
{
return;
}
if (nodeModel is null) return;
if (flowTaskManagement is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器
{
if (FlowState != RunState.Completion
&& flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点
{
_ = flowTaskManagement.RunGlobalFlipflopAsync(this, flipflopNode);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中
}
}*/
}
/// <summary>
/// 关闭全局触发器
/// </summary>
/// <param name="nodeGuid"></param>
public void TerminateFlipflopNode(string nodeGuid)
{
/* if (!TryGetNodeModel(nodeGuid, out var nodeModel))
{
return;
}
if (nodeModel is null) return;
if (flowTaskManagement is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器
{
flowTaskManagement.TerminateGlobalFlipflopRuning(flipflopNode);
}*/
}
/// <inheritdoc/>
public void UseExternalIOC(ISereinIOC ioc)
{
this.sereinIOC = ioc; // 设置IOC容器
}
/// <summary>
/// 启动器调用,运行到某个节点时触发了监视对象的更新(对象预览视图将会自动更新)
/// </summary>
/// <param name="nodeGuid"></param>
/// <param name="monitorData"></param>
/// <param name="sourceType"></param>
public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType)
{
flowEnvironmentEvent.OnMonitorObjectChanged(new MonitorObjectEventArgs(nodeGuid, monitorData, sourceType));
}
/// <summary>
/// 启动器调用,节点触发了中断。
/// </summary>
/// <param name="nodeGuid">节点</param>
/// <param name="expression">表达式</param>
/// <param name="type">类型0用户主动的中断1表达式中断</param>
public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type)
{
flowEnvironmentEvent.OnInterruptTriggered(new InterruptTriggerEventArgs(nodeGuid, expression, type));
}
#region
/// <summary>
/// 调用流程接口,将返回 FlowResult.Value。如果需要 FlowResult 对象,请使用该方法的泛型版本。
/// </summary>
/// <param name="apiGuid">流程接口节点Guid</param>
/// <param name="param">调用时入参参数</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public async Task<object> ApiInvokeAsync(string apiGuid, object[] param)
{
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;
}
/// <summary>
/// 调用流程接口,泛型类型为 FlowResult 时,将返回 FlowResult 对象。
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="apiGuid">流程接口节点Guid</param>
/// <param name="param">调用时入参参数</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public async Task<TResult> ApiInvokeAsync<TResult>(string apiGuid, object[] param)
{
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);
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}]。");
}
}
private bool IsHasSuccessorNodes(IFlowNode nodeModel)
{
var nextTypes = new[]
{
ConnectionInvokeType.Upstream,
ConnectionInvokeType.IsSucceed,
ConnectionInvokeType.IsFail,
ConnectionInvokeType.IsError
};
foreach (var type in nextTypes)
{
if (nodeModel.SuccessorNodes.TryGetValue(type, out var nextNodes))
{
if(nextNodes.Count > 0)
{
return true;
}
}
}
return false;
}
#endregion
}
}

568
NodeFlow/Env/FlowEdit.cs Normal file
View File

@@ -0,0 +1,568 @@
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 static Serein.Library.Api.IFlowEnvironment;
namespace Serein.NodeFlow.Env
{
/// <summary>
/// 流程编辑接口实现
/// </summary>
internal class FlowEdit : IFlowEdit
{
public FlowEdit(IFlowEnvironment flowEnvironment,
IFlowEnvironmentEvent flowEnvironmentEvent,
FlowLibraryService flowLibraryManagement,
FlowOperationService flowOperationService,
FlowModelService flowModelService,
UIContextOperation UIContextOperation,
ISereinIOC sereinIOC,
NodeMVVMService nodeMVVMService)
{
this.flowEnvironment = flowEnvironment;
this.flowEnvironmentEvent = flowEnvironmentEvent;
this.flowLibraryManagement = flowLibraryManagement;
this.flowOperationService = flowOperationService;
this.flowModelService = flowModelService;
this.UIContextOperation = UIContextOperation;
NodeMVVMManagement = nodeMVVMService;
InitNodeMVVM(nodeMVVMService);
}
/// <inheritdoc/>
public NodeMVVMService NodeMVVMManagement { get; }
private readonly IFlowEnvironment flowEnvironment;
private readonly IFlowEnvironmentEvent flowEnvironmentEvent;
private readonly FlowLibraryService flowLibraryManagement;
private readonly FlowOperationService flowOperationService;
private readonly FlowModelService flowModelService;
private readonly NodeMVVMService nodeMVVMService;
/// <summary>
/// 注册基本节点类型
/// </summary>
private void InitNodeMVVM(NodeMVVMService nodeMVVMService)
{
nodeMVVMService.RegisterModel(NodeControlType.UI, typeof(SingleUINode)); // 动作节点
nodeMVVMService.RegisterModel(NodeControlType.Action, typeof(SingleActionNode)); // 动作节点
nodeMVVMService.RegisterModel(NodeControlType.Flipflop, typeof(SingleFlipflopNode)); // 触发器节点
nodeMVVMService.RegisterModel(NodeControlType.ExpOp, typeof(SingleExpOpNode)); // 表达式节点
nodeMVVMService.RegisterModel(NodeControlType.ExpCondition, typeof(SingleConditionNode)); // 条件表达式节点
nodeMVVMService.RegisterModel(NodeControlType.GlobalData, typeof(SingleGlobalDataNode)); // 全局数据节点
nodeMVVMService.RegisterModel(NodeControlType.Script, typeof(SingleScriptNode)); // 脚本节点
nodeMVVMService.RegisterModel(NodeControlType.NetScript, typeof(SingleNetScriptNode)); // 脚本节点
nodeMVVMService.RegisterModel(NodeControlType.FlowCall, typeof(SingleFlowCallNode)); // 流程调用节点
}
private UIContextOperation UIContextOperation;
/// <summary>
/// 从Guid获取画布
/// </summary>
/// <param name="nodeGuid">节点Guid</param>
/// <returns>节点Model</returns>
/// <exception cref="ArgumentNullException">无法获取节点、Guid/节点为null时报错</exception>
public bool TryGetCanvasModel(string nodeGuid, out FlowCanvasDetails canvasDetails)
{
if (string.IsNullOrEmpty(nodeGuid))
{
canvasDetails = null;
return false;
}
return flowModelService.TryGetCanvasModel(nodeGuid, out canvasDetails);
}
/// <summary>
/// 从Guid获取节点
/// </summary>
/// <param name="nodeGuid">节点Guid</param>
/// <returns>节点Model</returns>
/// <exception cref="ArgumentNullException">无法获取节点、Guid/节点为null时报错</exception>
public bool TryGetNodeModel(string nodeGuid, out IFlowNode nodeModel)
{
if (string.IsNullOrEmpty(nodeGuid))
{
nodeModel = null;
return false;
}
return flowModelService.TryGetNodeModel(nodeGuid, out nodeModel);
}
#region
/// <summary>
/// 从节点信息创建节点,并返回状态指示是否创建成功
/// </summary>
/// <param name="nodeInfo"></param>
/// <returns></returns>
private bool CreateNodeFromNodeInfo(NodeInfo nodeInfo)
{
if (!EnumHelper.TryConvertEnum<NodeControlType>(nodeInfo.Type, out var controlType))
{
return false;
}
#region
MethodDetails? methodDetails;
if (controlType == NodeControlType.FlowCall)
{
if (string.IsNullOrEmpty(nodeInfo.MethodName))
{
methodDetails = new MethodDetails();
methodDetails.ParamsArgIndex = 0;
methodDetails.ParameterDetailss = new ParameterDetails[nodeInfo.ParameterData.Length];
for (int i = 0; i < methodDetails.ParameterDetailss.Length; i++)
{
var pdInfo = nodeInfo.ParameterData[i];
var t = new ParameterDetailsInfo();
var pd = new ParameterDetails(pdInfo, i);
methodDetails.ParameterDetailss[i] = pd;
}
}
else
{
// 目标节点可能是方法节点
flowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息
}
}
else if (controlType.IsBaseNode())
{
// 加载基础节点
methodDetails = new MethodDetails();
}
else
{
if (string.IsNullOrEmpty(nodeInfo.MethodName)) return false;
// 加载方法节点
flowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息
}
#endregion
var 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;
}
/// <summary>
/// 创建节点
/// </summary>
/// <param name="nodeBase"></param>
private bool TryAddNode(IFlowNode nodeModel)
{
nodeModel.Guid ??= Guid.NewGuid().ToString();
flowModelService.AddNodeModel(nodeModel);
// 如果是触发器,则需要添加到专属集合中
/*if (nodeModel is SingleFlipflopNode flipflopNode)
{
var guid = flipflopNode.Guid;
if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid)))
{
FlipflopNodes.Add(flipflopNode);
}
}*/
return true;
}
#endregion
#region
private int _add_canvas_count = 1;
public void CreateCanvas(string canvasName, int width, int height)
{
IOperation operation = new CreateCanvasOperation
{
CanvasInfo = new FlowCanvasDetailsInfo
{
Name = $"Canvas {_add_canvas_count++}",
Width = width,
Height = height,
Guid = Guid.NewGuid().ToString(),
ScaleX = 1.0f,
ScaleY = 1.0f,
}
};
flowOperationService.Execute(operation);
}
public void RemoveCanvas(string canvasGuid)
{
IOperation operation = new RemoveCanvasOperation
{
CanvasGuid = canvasGuid
};
flowOperationService.Execute(operation);
}
public void ConnectInvokeNode(string canvasGuid, string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionInvokeType invokeType)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = canvasGuid,
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
FromNodeJunctionType = fromNodeJunctionType,
ToNodeJunctionType = toNodeJunctionType,
ConnectionInvokeType = invokeType,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Create,
JunctionOfConnectionType = JunctionOfConnectionType.Invoke,
};
flowOperationService.Execute(operation);
}
public void ConnectArgSourceNode(string canvasGuid, string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionArgSourceType argSourceType, int argIndex)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = canvasGuid,
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
FromNodeJunctionType = fromNodeJunctionType,
ToNodeJunctionType = toNodeJunctionType,
ConnectionArgSourceType = argSourceType,
ArgIndex = argIndex,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Create,
JunctionOfConnectionType = JunctionOfConnectionType.Arg,
};
flowOperationService.Execute(operation);
}
public void RemoveInvokeConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = canvasGuid,
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
ConnectionInvokeType = connectionType,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Remove,
};
flowOperationService.Execute(operation);
}
public void RemoveArgSourceConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = canvasGuid,
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
ArgIndex = argIndex,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Remove
};
flowOperationService.Execute(operation);
}
public void CreateNode(string canvasGuid, NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null)
{
IOperation operation = new CreateNodeOperation
{
CanvasGuid = canvasGuid,
NodeControlType = nodeType,
Position = position,
MethodDetailsInfo = methodDetailsInfo
};
flowOperationService.Execute(operation);
}
public void RemoveNode(string canvasGuid, string nodeGuid)
{
IOperation operation = new RemoveNodeOperation
{
CanvasGuid = canvasGuid,
NodeGuid = nodeGuid
};
flowOperationService.Execute(operation);
}
public void PlaceNodeToContainer(string canvasGuid, string nodeGuid, string containerNodeGuid)
{
IOperation operation = new ContainerPlaceNodeOperation
{
CanvasGuid = canvasGuid,
NodeGuid = nodeGuid,
ContainerNodeGuid = containerNodeGuid
};
flowOperationService.Execute(operation);
}
public void TakeOutNodeToContainer(string canvasGuid, string nodeGuid)
{
IOperation operation = new ContainerTakeOutNodeOperation
{
CanvasGuid = canvasGuid,
NodeGuid = nodeGuid,
};
flowOperationService.Execute(operation);
}
public void SetStartNode(string canvasGuid, string nodeGuid)
{
if (!TryGetCanvasModel(canvasGuid, out var canvasModel) || !TryGetNodeModel(nodeGuid, out var newStartNodeModel))
{
return;
}
var oldNodeGuid = canvasModel.StartNode?.Guid;
/*if(TryGetNodeModel(oldNodeGuid, out var newStartNodeModel))
{
newStartNode.IsStart = false;
}*/
canvasModel.StartNode = newStartNodeModel;
//newStartNode.IsStart = true;
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnStartNodeChanged(new StartNodeChangeEventArgs(canvasGuid, oldNodeGuid, newStartNodeModel.Guid)));
return;
}
public void SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = string.Empty, // 连接优先级不需要画布
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
ConnectionInvokeType = connectionType,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Create
};
flowOperationService.Execute(operation);
}
public void ChangeParameter(string nodeGuid, bool isAdd, int paramIndex)
{
IOperation operation = new ChangeParameterOperation
{
NodeGuid = nodeGuid,
IsAdd = isAdd,
ParamIndex = paramIndex
};
flowOperationService.Execute(operation);
}
/// <summary>
/// 从节点信息集合批量加载节点控件
/// </summary>
/// <param name="List<NodeInfo>">节点信息</param>
/// <returns></returns>
///
public async Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos)
{
#region NodeInfo创建NodeModel
// 流程接口节点最后才创建
List<NodeInfo> flowCallNodeInfos = [];
foreach (NodeInfo? nodeInfo in nodeInfos)
{
if (nodeInfo.Type == nameof(NodeControlType.FlowCall))
{
flowCallNodeInfos.Add(nodeInfo);
}
else
{
if (!CreateNodeFromNodeInfo(nodeInfo))
{
SereinEnv.WriteLine(InfoType.WARN, $"节点创建失败。{Environment.NewLine}{nodeInfo}");
continue;
}
}
}
// 创建流程接口节点
foreach (NodeInfo? nodeInfo in flowCallNodeInfos)
{
if (!CreateNodeFromNodeInfo(nodeInfo))
{
SereinEnv.WriteLine(InfoType.WARN, $"节点创建失败。{Environment.NewLine}{nodeInfo}");
continue;
}
}
#endregion
#region
List<NodeInfo> needPlaceNodeInfos = [];
foreach (NodeInfo? nodeInfo in nodeInfos)
{
if (!string.IsNullOrEmpty(nodeInfo.ParentNodeGuid) &&
TryGetNodeModel(nodeInfo.ParentNodeGuid, out var parentNode))
{
needPlaceNodeInfos.Add(nodeInfo); // 需要重新放置的节点
}
}
foreach (NodeInfo nodeInfo in needPlaceNodeInfos)
{
if (TryGetNodeModel(nodeInfo.Guid, out var nodeModel) &&
TryGetNodeModel(nodeInfo.ParentNodeGuid, out var containerNode)
&& containerNode is INodeContainer nodeContainer)
{
var result = nodeContainer.PlaceNode(nodeModel);
if (result)
{
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnNodePlace(
new NodePlaceEventArgs(nodeInfo.CanvasGuid, nodeModel.Guid, containerNode.Guid)));
}
}
}
#endregion
await Task.Delay(100);
#region
foreach (var nodeInfo in nodeInfos)
{
var canvasGuid = nodeInfo.CanvasGuid;
if (!TryGetNodeModel(nodeInfo.Guid, out var fromNodeModel))
{
return;
}
if (fromNodeModel is null) continue;
List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes),
(ConnectionInvokeType.IsFail, nodeInfo.FalseNodes),
(ConnectionInvokeType.IsError, nodeInfo.ErrorNodes),
(ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)];
foreach ((ConnectionInvokeType connectionType, string[] toNodeGuids) item in allToNodes)
{
// 遍历当前类型分支的节点(确认连接关系)
foreach (var toNodeGuid in item.toNodeGuids)
{
if (!TryGetNodeModel(toNodeGuid, out var toNodeModel))
{
return;
}
if (toNodeModel is null)
{
// 防御性代码,加载正常保存的项目文件不会进入这里
continue;
}
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
#region
var nodeModels = flowModelService.GetAllNodeModel();
foreach (var toNode in nodeModels)
{
var canvasGuid = toNode.CanvasDetails.Guid;
if (toNode.MethodDetails.ParameterDetailss == null)
{
continue;
}
for (var i = 0; i < toNode.MethodDetails.ParameterDetailss.Length; i++)
{
var pd = toNode.MethodDetails.ParameterDetailss[i];
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
#region
/// <summary>
/// 定位节点
/// </summary>
/// <param name="nodeGuid"></param>
public void NodeLocate(string nodeGuid)
{
if (OperatingSystem.IsWindows())
{
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnNodeLocated(new NodeLocatedEventArgs(nodeGuid)));
}
}
#endregion
}
}

View File

@@ -45,6 +45,9 @@ namespace Serein.NodeFlow.Env
/// </summary> /// </summary>
public class FlowEnvironment : IFlowEnvironment public class FlowEnvironment : IFlowEnvironment
{ {
/// <summary>
/// 流程运行环境构造函数
/// </summary>
public FlowEnvironment() public FlowEnvironment()
{ {
ISereinIOC ioc = new SereinIOC(); ISereinIOC ioc = new SereinIOC();
@@ -52,6 +55,8 @@ namespace Serein.NodeFlow.Env
.Register<ISereinIOC>(()=> ioc) // 注册IOC .Register<ISereinIOC>(()=> ioc) // 注册IOC
.Register<IFlowEnvironment>(() => this) .Register<IFlowEnvironment>(() => this)
.Register<IFlowEnvironmentEvent, FlowEnvironmentEvent>() .Register<IFlowEnvironmentEvent, FlowEnvironmentEvent>()
.Register<IFlowEdit, FlowEdit>()
.Register<IFlowControl, FlowControl>()
.Register<LocalFlowEnvironment>() .Register<LocalFlowEnvironment>()
.Register<FlowModelService>() .Register<FlowModelService>()
.Register<FlowOperationService>() .Register<FlowOperationService>()
@@ -63,7 +68,7 @@ namespace Serein.NodeFlow.Env
currentFlowEnvironmentEvent = ioc.Get<IFlowEnvironmentEvent>(); currentFlowEnvironmentEvent = ioc.Get<IFlowEnvironmentEvent>();
SereinEnv.SetEnv(currentFlowEnvironment); SereinEnv.SetEnv(currentFlowEnvironment);
} }
/*
/// <summary> /// <summary>
/// 本地环境事件 /// 本地环境事件
/// </summary> /// </summary>
@@ -73,7 +78,7 @@ namespace Serein.NodeFlow.Env
/// 远程环境事件 /// 远程环境事件
/// </summary> /// </summary>
private IFlowEnvironmentEvent remoteFlowEnvironmentEvent; private IFlowEnvironmentEvent remoteFlowEnvironmentEvent;
*/
/// <summary> /// <summary>
/// 管理当前环境 /// 管理当前环境
@@ -86,9 +91,9 @@ namespace Serein.NodeFlow.Env
/// </summary> /// </summary>
private IFlowEnvironmentEvent currentFlowEnvironmentEvent; private IFlowEnvironmentEvent currentFlowEnvironmentEvent;
private int _loadingProjectFlag = 0; // 使用原子自增代替锁 private int _loadingProjectFlag = 0; // 使用原子自增代替锁
/// <summary> /// <summary>
/// 传入false时将停止数据通知。传入true时 /// 传入false时将停止数据通知。传入true时
/// </summary> /// </summary>
@@ -110,8 +115,12 @@ namespace Serein.NodeFlow.Env
public IFlowEnvironment CurrentEnv => currentFlowEnvironment; public IFlowEnvironment CurrentEnv => currentFlowEnvironment;
/// <inheritdoc/> /// <inheritdoc/>
public UIContextOperation UIContextOperation => currentFlowEnvironment.UIContextOperation; public UIContextOperation UIContextOperation => currentFlowEnvironment.UIContextOperation;
/// <inheritdoc/> /// <inheritdoc/>
public NodeMVVMService NodeMVVMManagement => currentFlowEnvironment.NodeMVVMManagement; public IFlowEdit FlowEdit => currentFlowEnvironment.FlowEdit;
/// <inheritdoc/>
public IFlowControl FlowControl => currentFlowEnvironment.FlowControl;
/// <inheritdoc/> /// <inheritdoc/>
public ISereinIOC IOC => currentFlowEnvironment.IOC; public ISereinIOC IOC => currentFlowEnvironment.IOC;
@@ -138,172 +147,10 @@ namespace Serein.NodeFlow.Env
/// <inheritdoc/> /// <inheritdoc/>
public RunState FlowState { get => currentFlowEnvironment.FlowState; set => currentFlowEnvironment.FlowState = value; } public RunState FlowState { get => currentFlowEnvironment.FlowState; set => currentFlowEnvironment.FlowState = value; }
/// <inheritdoc/>
public event LoadDllHandler DllLoad {
add { currentFlowEnvironmentEvent.DllLoad += value; }
remove { currentFlowEnvironmentEvent.DllLoad -= value; }
}
/// <inheritdoc/>
public event ProjectLoadedHandler ProjectLoaded
{
add { currentFlowEnvironmentEvent.ProjectLoaded += value; }
remove { currentFlowEnvironmentEvent.ProjectLoaded -= value; }
}
/// <inheritdoc/>
public event ProjectSavingHandler? ProjectSaving
{
add { currentFlowEnvironmentEvent.ProjectSaving += value; }
remove { currentFlowEnvironmentEvent.ProjectSaving -= value; }
}
/// <inheritdoc/>
public event NodeConnectChangeHandler NodeConnectChanged
{
add { currentFlowEnvironmentEvent.NodeConnectChanged += value; }
remove { currentFlowEnvironmentEvent.NodeConnectChanged -= value; }
}
/// <inheritdoc/>
public event CanvasCreateHandler CanvasCreated
{
add { currentFlowEnvironmentEvent.CanvasCreated += value; }
remove { currentFlowEnvironmentEvent.CanvasCreated -= value; }
}
/// <inheritdoc/>
public event CanvasRemoveHandler CanvasRemoved
{
add { currentFlowEnvironmentEvent.CanvasRemoved += value; }
remove { currentFlowEnvironmentEvent.CanvasRemoved -= value; }
}
/// <inheritdoc/>
public event NodeCreateHandler NodeCreated
{
add { currentFlowEnvironmentEvent.NodeCreated += value; }
remove { currentFlowEnvironmentEvent.NodeCreated -= value; }
}
/// <inheritdoc/>
public event NodeRemoveHandler NodeRemoved
{
add { currentFlowEnvironmentEvent.NodeRemoved += value; }
remove { currentFlowEnvironmentEvent.NodeRemoved -= value; }
}
/// <inheritdoc/>
public event NodePlaceHandler NodePlace
{
add { currentFlowEnvironmentEvent.NodePlace += value; }
remove { currentFlowEnvironmentEvent.NodePlace -= value; }
}
/// <inheritdoc/>
public event NodeTakeOutHandler NodeTakeOut
{
add { currentFlowEnvironmentEvent.NodeTakeOut += value; }
remove { currentFlowEnvironmentEvent.NodeTakeOut -= value; }
}
/// <inheritdoc/>
public event StartNodeChangeHandler StartNodeChanged
{
add { currentFlowEnvironmentEvent.StartNodeChanged += value; }
remove { currentFlowEnvironmentEvent.StartNodeChanged -= value; }
}
/// <inheritdoc/>
public event FlowRunCompleteHandler FlowRunComplete
{
add { currentFlowEnvironmentEvent.FlowRunComplete += value; }
remove { currentFlowEnvironmentEvent.FlowRunComplete -= value; }
}
/// <inheritdoc/>
public event MonitorObjectChangeHandler MonitorObjectChanged
{
add { currentFlowEnvironmentEvent.MonitorObjectChanged += value; }
remove { currentFlowEnvironmentEvent.MonitorObjectChanged -= value; }
}
/// <inheritdoc/>
public event NodeInterruptStateChangeHandler NodeInterruptStateChanged
{
add { currentFlowEnvironmentEvent.NodeInterruptStateChanged += value; }
remove { currentFlowEnvironmentEvent.NodeInterruptStateChanged -= value; }
}
/// <inheritdoc/>
public event ExpInterruptTriggerHandler InterruptTriggered
{
add { currentFlowEnvironmentEvent.InterruptTriggered += value; }
remove { currentFlowEnvironmentEvent.InterruptTriggered -= value; }
}
/// <inheritdoc/>
public event IOCMembersChangedHandler IOCMembersChanged
{
add { currentFlowEnvironmentEvent.IOCMembersChanged += value; }
remove { currentFlowEnvironmentEvent.IOCMembersChanged -= value; }
}
/// <inheritdoc/>
public event NodeLocatedHandler NodeLocated
{
add { currentFlowEnvironmentEvent.NodeLocated += value; }
remove { currentFlowEnvironmentEvent.NodeLocated -= value; }
}
/// <inheritdoc/>
public event EnvOutHandler EnvOutput
{
add { currentFlowEnvironmentEvent.EnvOutput += value; }
remove { currentFlowEnvironmentEvent.EnvOutput -= value; }
}
/// <inheritdoc/> /// <inheritdoc/>
public void ActivateFlipflopNode(string nodeGuid) public void ActivateFlipflopNode(string nodeGuid)
{ {
currentFlowEnvironment.ActivateFlipflopNode(nodeGuid); currentFlowEnvironment.FlowControl.ActivateFlipflopNode(nodeGuid);
}
/// <inheritdoc/>
public void CreateCanvas(string canvasName, int width, int height)
{
currentFlowEnvironment.CreateCanvas(canvasName, width, height);
}
/// <inheritdoc/>
public void RemoveCanvas(string canvasGuid)
{
currentFlowEnvironment.RemoveCanvas(canvasGuid);
}
/// <inheritdoc/>
public void ConnectInvokeNode(string canvasGuid,
string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionInvokeType invokeType)
{
currentFlowEnvironment.ConnectInvokeNode(canvasGuid, fromNodeGuid, toNodeGuid, fromNodeJunctionType, toNodeJunctionType, invokeType);
}
/// <inheritdoc/>
public void ConnectArgSourceNode(string canvasGuid,
string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
ConnectionArgSourceType argSourceType,
int argIndex)
{
currentFlowEnvironment.ConnectArgSourceNode(canvasGuid, fromNodeGuid, toNodeGuid, fromNodeJunctionType, toNodeJunctionType, argSourceType, argIndex);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -320,42 +167,10 @@ namespace Serein.NodeFlow.Env
return (isConnect, remoteMsgUtil); return (isConnect, remoteMsgUtil);
} }
/// <inheritdoc/>
public async Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos)
{
SetProjectLoadingFlag(false);
await currentFlowEnvironment.LoadNodeInfosAsync(nodeInfos); // 装饰器调用
SetProjectLoadingFlag(true);
}
/// <inheritdoc/>
public void CreateNode(string canvasGuid, NodeControlType nodeBase, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null)
{
SetProjectLoadingFlag(false);
currentFlowEnvironment.CreateNode(canvasGuid, nodeBase, position, methodDetailsInfo); // 装饰器调用
SetProjectLoadingFlag(true);
}
/// <inheritdoc/>
public void PlaceNodeToContainer(string canvasGuid, string nodeGuid, string containerNodeGuid)
{
SetProjectLoadingFlag(false);
currentFlowEnvironment.PlaceNodeToContainer(canvasGuid, nodeGuid, containerNodeGuid); // 装饰器调用
SetProjectLoadingFlag(true);
}
/// <inheritdoc/>
public void TakeOutNodeToContainer(string canvasGuid, string nodeGuid)
{
SetProjectLoadingFlag(false);
currentFlowEnvironment.TakeOutNodeToContainer(canvasGuid,nodeGuid); // 装饰器调用
SetProjectLoadingFlag(true);
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> ExitFlowAsync() public async Task<bool> ExitFlowAsync()
{ {
return await currentFlowEnvironment.ExitFlowAsync(); return await currentFlowEnvironment.FlowControl.ExitFlowAsync();
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -401,19 +216,7 @@ namespace Serein.NodeFlow.Env
/// <inheritdoc/> /// <inheritdoc/>
public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType) public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType)
{ {
currentFlowEnvironment.MonitorObjectNotification(nodeGuid, monitorData, sourceType); currentFlowEnvironment.FlowControl.MonitorObjectNotification(nodeGuid, monitorData, sourceType);
}
/*/// <inheritdoc/>
public void MoveNode(string canvasGuid, string nodeGuid, double x, double y)
{
currentFlowEnvironment.MoveNode(canvasGuid, nodeGuid, x, y);
}
*/
/// <inheritdoc/>
public void NodeLocate(string nodeGuid)
{
currentFlowEnvironment.NodeLocate(nodeGuid);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -422,31 +225,6 @@ namespace Serein.NodeFlow.Env
return currentFlowEnvironment.TryUnloadLibrary(assemblyName); return currentFlowEnvironment.TryUnloadLibrary(assemblyName);
} }
/// <inheritdoc/>
public void SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
currentFlowEnvironment.SetConnectPriorityInvoke(fromNodeGuid, toNodeGuid, connectionType);
}
/// <inheritdoc/>
public void RemoveInvokeConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
currentFlowEnvironment.RemoveInvokeConnect(canvasGuid, fromNodeGuid, toNodeGuid, connectionType);
}
/// <inheritdoc/>
public void RemoveArgSourceConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex)
{
currentFlowEnvironment.RemoveArgSourceConnect(canvasGuid, fromNodeGuid, toNodeGuid, argIndex);
}
/// <inheritdoc/>
public void RemoveNode(string canvasGuid, string nodeGuid)
{
currentFlowEnvironment.RemoveNode(canvasGuid, nodeGuid);
}
/// <summary> /// <summary>
/// 输出信息 /// 输出信息
/// </summary> /// </summary>
@@ -489,22 +267,17 @@ namespace Serein.NodeFlow.Env
#endregion #endregion
/// <inheritdoc/>
public void SetStartNode(string canvasGuid, string nodeGuid)
{
currentFlowEnvironment.SetStartNode(canvasGuid, nodeGuid);
}
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> StartFlowAsync(string[] canvasGuids) public async Task<bool> StartFlowAsync(string[] canvasGuids)
{ {
return await currentFlowEnvironment.StartFlowAsync(canvasGuids); return await currentFlowEnvironment.FlowControl.StartFlowAsync(canvasGuids);
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid) public async Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid)
{ {
return await currentFlowEnvironment.StartFlowFromSelectNodeAsync(startNodeGuid); return await currentFlowEnvironment.FlowControl.StartFlowFromSelectNodeAsync(startNodeGuid);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -522,13 +295,13 @@ namespace Serein.NodeFlow.Env
/// <inheritdoc/> /// <inheritdoc/>
public void TerminateFlipflopNode(string nodeGuid) public void TerminateFlipflopNode(string nodeGuid)
{ {
currentFlowEnvironment.TerminateFlipflopNode(nodeGuid); currentFlowEnvironment.FlowControl.TerminateFlipflopNode(nodeGuid);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type) public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type)
{ {
currentFlowEnvironment.TriggerInterrupt(nodeGuid, expression, type); currentFlowEnvironment.FlowControl.TriggerInterrupt(nodeGuid, expression, type);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -545,7 +318,7 @@ namespace Serein.NodeFlow.Env
/// <inheritdoc/> /// <inheritdoc/>
public void UseExternalIOC(ISereinIOC ioc) public void UseExternalIOC(ISereinIOC ioc)
{ {
currentFlowEnvironment.UseExternalIOC(ioc); currentFlowEnvironment.FlowControl.UseExternalIOC(ioc);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -579,11 +352,6 @@ namespace Serein.NodeFlow.Env
} }
} }
/// <inheritdoc/>
public void ChangeParameter(string nodeGuid, bool isAdd, int paramIndex)
{
currentFlowEnvironment.ChangeParameter(nodeGuid, isAdd, paramIndex);
}
#region #region

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,7 @@ namespace Serein.NodeFlow
// 尝试获取需要创建的节点类型 // 尝试获取需要创建的节点类型
if (!env.NodeMVVMManagement.TryGetType(nodeControlType, out var nodeMVVM) || nodeMVVM.ModelType == null) if (!env.FlowEdit.NodeMVVMManagement.TryGetType(nodeControlType, out var nodeMVVM) || nodeMVVM.ModelType == null)
{ {
throw new Exception($"无法创建{nodeControlType}节点,节点类型尚未注册。"); throw new Exception($"无法创建{nodeControlType}节点,节点类型尚未注册。");
} }

View File

@@ -115,7 +115,7 @@ namespace Serein.NodeFlow.Model
// 执行触发检查是否需要中断 // 执行触发检查是否需要中断
if (DebugSetting.IsInterrupt) if (DebugSetting.IsInterrupt)
{ {
context.Env.TriggerInterrupt(Guid, "", InterruptTriggerEventArgs.InterruptTriggerType.Monitor); // 通知运行环境该节点中断了 context.Env.FlowControl.TriggerInterrupt(Guid, "", InterruptTriggerEventArgs.InterruptTriggerType.Monitor); // 通知运行环境该节点中断了
await DebugSetting.GetInterruptTask.Invoke(); await DebugSetting.GetInterruptTask.Invoke();
SereinEnv.WriteLine(InfoType.INFO, $"[{this.MethodDetails?.MethodName}]中断已取消,开始执行后继分支"); SereinEnv.WriteLine(InfoType.INFO, $"[{this.MethodDetails?.MethodName}]中断已取消,开始执行后继分支");
if (token.IsCancellationRequested) { return null; } if (token.IsCancellationRequested) { return null; }

View File

@@ -38,17 +38,15 @@ namespace Serein.NodeFlow.Model
public partial class SingleFlowCallNode : NodeModelBase public partial class SingleFlowCallNode : NodeModelBase
{ {
/// <summary> /// <summary>
/// 接口节点 /// 被调用的节点
/// </summary> /// </summary>
private IFlowNode targetNode; private IFlowNode targetNode;
/// <summary> /// <summary>
/// 缓存的方法信息 /// 缓存的方法信息
/// </summary> /// </summary>
public MethodDetails CacheMethodDetails { get; private set; } public MethodDetails CacheMethodDetails { get; private set; }
/// <summary>
/// 接口节点Guid
/// </summary>
//public string? TargetNodeGuid => targetNode?.Guid;
public SingleFlowCallNode(IFlowEnvironment environment) : base(environment) public SingleFlowCallNode(IFlowEnvironment environment) : base(environment)

View File

@@ -96,7 +96,7 @@ namespace Serein.NodeFlow.Model
{ {
foreach (var nodeModel in ChildrenNode) foreach (var nodeModel in ChildrenNode)
{ {
nodeModel.Env.TakeOutNodeToContainer(nodeModel.CanvasDetails.Guid, nodeModel.Guid); nodeModel.Env.FlowEdit.TakeOutNodeToContainer(nodeModel.CanvasDetails.Guid, nodeModel.Guid);
} }
DataNode = null; DataNode = null;
} }

View File

@@ -112,7 +112,7 @@ namespace Serein.NodeFlow.Model.Operation
if(flowNode is null) return false; // 没有创建过节点 if(flowNode is null) return false; // 没有创建过节点
var canvasGuid = flowCanvasDetails.Guid; var canvasGuid = flowCanvasDetails.Guid;
var nodeGuid = flowNode.Guid; var nodeGuid = flowNode.Guid;
flowEnvironment.RemoveNode(canvasGuid, nodeGuid); flowEnvironment.FlowEdit.RemoveNode(canvasGuid, nodeGuid);
return true; return true;
} }

View File

@@ -5,7 +5,6 @@ using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using static Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser;
namespace Serein.NodeFlow.Services namespace Serein.NodeFlow.Services
{ {
@@ -31,32 +30,6 @@ namespace Serein.NodeFlow.Services
/* object result = flowApiService.Invoke("", params);
TResult result = flowApiService.Invoke<TResult>("", params);
object result = await flowApiService.InvokeAsync("", params);
TResult result = await flowApiService.InvokeAsync<TResult>("", params);*/
public object Invoke(string apiName, object[] param)
{
return null;
}
public TResult Invoke<TResult>(string apiName, object[] param)
{
return default(TResult);
}
public async Task<object> InvokeAsync(string apiName, object[] param)
{
return null;
}
public async Task<TResult> InvokeAsync<TResult>(string apiName, object[] param)
{
return default(TResult);
}
} }

View File

@@ -159,11 +159,11 @@ namespace Serein.Workbench.Node.View
private void AddParamAsync() private void AddParamAsync()
{ {
this.MyNode.Env.ChangeParameter(MyNode.Guid, true, ArgIndex); this.MyNode.Env.FlowEdit.ChangeParameter(MyNode.Guid, true, ArgIndex);
} }
private void RemoveParamAsync() private void RemoveParamAsync()
{ {
this.MyNode.Env.ChangeParameter(MyNode.Guid, false, ArgIndex); this.MyNode.Env.FlowEdit.ChangeParameter(MyNode.Guid, false, ArgIndex);
} }
} }

View File

@@ -243,11 +243,11 @@ namespace Serein.Workbench.Node.View
var jctEnd = End.JunctionType.ToConnectyionType(); var jctEnd = End.JunctionType.ToConnectyionType();
if (jct == JunctionOfConnectionType.Invoke) if (jct == JunctionOfConnectionType.Invoke)
{ {
env.RemoveInvokeConnect(canvasGuid, Start.MyNode.Guid, End.MyNode.Guid, InvokeType); env.FlowEdit.RemoveInvokeConnect(canvasGuid, Start.MyNode.Guid, End.MyNode.Guid, InvokeType);
} }
else if (jct == JunctionOfConnectionType.Arg) else if (jct == JunctionOfConnectionType.Arg)
{ {
env.RemoveArgSourceConnect(canvasGuid,Start.MyNode.Guid, End.MyNode.Guid, ArgIndex) ; env.FlowEdit.RemoveArgSourceConnect(canvasGuid,Start.MyNode.Guid, End.MyNode.Guid, ArgIndex) ;
} }
} }
@@ -259,7 +259,7 @@ namespace Serein.Workbench.Node.View
var env = Start.MyNode.Env; var env = Start.MyNode.Env;
if (Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke) if (Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke)
{ {
env.SetConnectPriorityInvoke(Start.MyNode.Guid, End.MyNode.Guid, InvokeType); env.FlowEdit.SetConnectPriorityInvoke(Start.MyNode.Guid, End.MyNode.Guid, InvokeType);
} }
} }

View File

@@ -174,15 +174,15 @@ namespace Serein.Workbench.Services
/// </summary> /// </summary>
private void InitNodeType() private void InitNodeType()
{ {
flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.UI, typeof(UINodeControl), typeof(UINodeControlViewModel)); flowEnvironment.FlowEdit.NodeMVVMManagement.RegisterUI(NodeControlType.UI, typeof(UINodeControl), typeof(UINodeControlViewModel));
flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeControl), typeof(ActionNodeControlViewModel)); flowEnvironment.FlowEdit.NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeControl), typeof(ActionNodeControlViewModel));
flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.Flipflop, typeof(FlipflopNodeControl), typeof(FlipflopNodeControlViewModel)); flowEnvironment.FlowEdit.NodeMVVMManagement.RegisterUI(NodeControlType.Flipflop, typeof(FlipflopNodeControl), typeof(FlipflopNodeControlViewModel));
flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.ExpOp, typeof(ExpOpNodeControl), typeof(ExpOpNodeControlViewModel)); flowEnvironment.FlowEdit.NodeMVVMManagement.RegisterUI(NodeControlType.ExpOp, typeof(ExpOpNodeControl), typeof(ExpOpNodeControlViewModel));
flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.ExpCondition, typeof(ConditionNodeControl), typeof(ConditionNodeControlViewModel)); flowEnvironment.FlowEdit.NodeMVVMManagement.RegisterUI(NodeControlType.ExpCondition, typeof(ConditionNodeControl), typeof(ConditionNodeControlViewModel));
flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.GlobalData, typeof(GlobalDataControl), typeof(GlobalDataNodeControlViewModel)); flowEnvironment.FlowEdit.NodeMVVMManagement.RegisterUI(NodeControlType.GlobalData, typeof(GlobalDataControl), typeof(GlobalDataNodeControlViewModel));
flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.Script, typeof(ScriptNodeControl), typeof(ScriptNodeControlViewModel)); flowEnvironment.FlowEdit.NodeMVVMManagement.RegisterUI(NodeControlType.Script, typeof(ScriptNodeControl), typeof(ScriptNodeControlViewModel));
flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.NetScript, typeof(NetScriptNodeControl), typeof(NetScriptNodeControlViewModel)); flowEnvironment.FlowEdit.NodeMVVMManagement.RegisterUI(NodeControlType.NetScript, typeof(NetScriptNodeControl), typeof(NetScriptNodeControlViewModel));
flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.FlowCall, typeof(FlowCallNodeControl), typeof(FlowCallNodeControlViewModel)); flowEnvironment.FlowEdit.NodeMVVMManagement.RegisterUI(NodeControlType.FlowCall, typeof(FlowCallNodeControl), typeof(FlowCallNodeControlViewModel));
} }
/// <summary> /// <summary>
@@ -319,7 +319,7 @@ namespace Serein.Workbench.Services
return; return;
} }
if (!flowEnvironment.NodeMVVMManagement.TryGetType(nodeModel.ControlType, out var nodeMVVM)) if (!flowEnvironment.FlowEdit.NodeMVVMManagement.TryGetType(nodeModel.ControlType, out var nodeMVVM))
{ {
SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,节点类型尚未注册。"); SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,节点类型尚未注册。");
return; return;
@@ -635,7 +635,7 @@ namespace Serein.Workbench.Services
node.Position = new PositionOfUI(positionOfUI.X + offsetX, positionOfUI.Y + offsetY); node.Position = new PositionOfUI(positionOfUI.X + offsetX, positionOfUI.Y + offsetY);
} }
_ = flowEnvironment.LoadNodeInfosAsync(nodes); _ = flowEnvironment.FlowEdit.LoadNodeInfosAsync(nodes);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -656,7 +656,7 @@ namespace Serein.Workbench.Services
{ {
int width = 1200; int width = 1200;
int height = 780; int height = 780;
flowEnvironment.CreateCanvas("", width, height); flowEnvironment.FlowEdit.CreateCanvas("", width, height);
} }
/// <summary> /// <summary>
@@ -669,7 +669,7 @@ namespace Serein.Workbench.Services
return; return;
} }
var model = ((FlowCanvasViewModel)CurrentSelectCanvas.DataContext).Model; var model = ((FlowCanvasViewModel)CurrentSelectCanvas.DataContext).Model;
flowEnvironment.RemoveCanvas(model.Guid); flowEnvironment.FlowEdit.RemoveCanvas(model.Guid);
} }
/// <summary> /// <summary>
@@ -688,7 +688,7 @@ namespace Serein.Workbench.Services
{ {
return; return;
} }
flowEnvironment.CreateNode(canvasGuid, nodeType, position, methodDetailsInfo); flowEnvironment.FlowEdit.CreateNode(canvasGuid, nodeType, position, methodDetailsInfo);
} }
/// <summary> /// <summary>
@@ -707,7 +707,7 @@ namespace Serein.Workbench.Services
return; return;
} }
flowEnvironment.RemoveNode(model.CanvasDetails.Guid, model.Guid); flowEnvironment.FlowEdit.RemoveNode(model.CanvasDetails.Guid, model.Guid);
} }
#endregion #endregion

View File

@@ -142,7 +142,7 @@ namespace Serein.Workbench.Themes
{ {
try try
{ {
await flowEnvironment.StartFlowFromSelectNodeAsync(tmpNodeTreeModel.RootNode.Guid); await flowEnvironment.FlowControl.StartFlowFromSelectNodeAsync(tmpNodeTreeModel.RootNode.Guid);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -150,7 +150,7 @@ namespace Serein.Workbench.Themes
return; return;
} }
})); }));
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("定位", (s, e) => flowEnvironment.NodeLocate(tmpNodeTreeModel.RootNode.Guid))); contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("定位", (s, e) => flowEnvironment.FlowEdit.NodeLocate(tmpNodeTreeModel.RootNode.Guid)));
treeViewItem.ContextMenu = contextMenu; treeViewItem.ContextMenu = contextMenu;
treeViewItem.Margin = new Thickness(-20, 0, 0, 0); treeViewItem.Margin = new Thickness(-20, 0, 0, 0);

View File

@@ -114,7 +114,7 @@ namespace Serein.Workbench.ViewModels
if(canvass.Length > 0) if(canvass.Length > 0)
{ {
string[] guids = [..canvass.Select(c => c.Guid)]; string[] guids = [..canvass.Select(c => c.Guid)];
flowEnvironment.StartFlowAsync(guids); flowEnvironment.FlowControl.StartFlowAsync(guids);
} }
} }
@@ -122,7 +122,7 @@ namespace Serein.Workbench.ViewModels
{ {
var canvas = flowNodeService.CurrentSelectCanvas; var canvas = flowNodeService.CurrentSelectCanvas;
if (canvas is null) return; if (canvas is null) return;
flowEnvironment.StartFlowAsync([canvas.Guid]); flowEnvironment.FlowControl.StartFlowAsync([canvas.Guid]);
} }
private void StopCurrentCanvasFlow() { } private void StopCurrentCanvasFlow() { }
private void OpenDynamicCompiler() { } private void OpenDynamicCompiler() { }

View File

@@ -370,7 +370,7 @@ namespace Serein.Workbench.Views
if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器 if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器
{ {
// 通知运行环境调用加载节点子项的方法 // 通知运行环境调用加载节点子项的方法
flowEnvironment.PlaceNodeToContainer(Guid, flowEnvironment.FlowEdit.PlaceNodeToContainer(Guid,
nodeControl.ViewModel.NodeModel.Guid, // 待移动的节点 nodeControl.ViewModel.NodeModel.Guid, // 待移动的节点
regionControl.ViewModel.NodeModel.Guid); // 目标的容器节点 regionControl.ViewModel.NodeModel.Guid); // 目标的容器节点
return; return;
@@ -557,14 +557,14 @@ namespace Serein.Workbench.Views
if (keyEventService.GetKeyState(Key.LeftCtrl) || keyEventService.GetKeyState(Key.RightCtrl)) if (keyEventService.GetKeyState(Key.LeftCtrl) || keyEventService.GetKeyState(Key.RightCtrl))
{ {
// Ctrl + F5 调试当前流程 // Ctrl + F5 调试当前流程
_ = flowEnvironment.StartFlowAsync([flowNodeService.CurrentSelectCanvas.Guid]); _ = flowEnvironment.FlowControl.StartFlowAsync([flowNodeService.CurrentSelectCanvas.Guid]);
} }
else if (selectNodeControls.Count == 1 ) else if (selectNodeControls.Count == 1 )
{ {
// F5 调试当前选定节点 // F5 调试当前选定节点
var nodeModel = selectNodeControls[0].ViewModel.NodeModel; var nodeModel = selectNodeControls[0].ViewModel.NodeModel;
SereinEnv.WriteLine(InfoType.INFO, $"调试运行当前节点:{nodeModel.Guid}"); SereinEnv.WriteLine(InfoType.INFO, $"调试运行当前节点:{nodeModel.Guid}");
_ = flowEnvironment.StartFlowFromSelectNodeAsync(nodeModel.Guid); _ = flowEnvironment.FlowControl.StartFlowFromSelectNodeAsync(nodeModel.Guid);
//_ = nodeModel.StartFlowAsync(new DynamicContext(flowEnvironment), new CancellationToken()); //_ = nodeModel.StartFlowAsync(new DynamicContext(flowEnvironment), new CancellationToken());
} }
@@ -897,7 +897,7 @@ namespace Serein.Workbench.Views
{ {
var canvasGuid = this.Guid; var canvasGuid = this.Guid;
flowEnvironment.ConnectInvokeNode( flowEnvironment.FlowEdit.ConnectInvokeNode(
canvasGuid, canvasGuid,
cd.StartJunction.MyNode.Guid, cd.StartJunction.MyNode.Guid,
cd.CurrentJunction.MyNode.Guid, cd.CurrentJunction.MyNode.Guid,
@@ -921,7 +921,7 @@ namespace Serein.Workbench.Views
} }
var canvasGuid = this.Guid; var canvasGuid = this.Guid;
flowEnvironment.ConnectArgSourceNode( flowEnvironment.FlowEdit.ConnectArgSourceNode(
canvasGuid, canvasGuid,
cd.StartJunction.MyNode.Guid, cd.StartJunction.MyNode.Guid,
cd.CurrentJunction.MyNode.Guid, cd.CurrentJunction.MyNode.Guid,
@@ -1268,7 +1268,7 @@ namespace Serein.Workbench.Views
if (!string.IsNullOrEmpty(guid)) if (!string.IsNullOrEmpty(guid))
{ {
var canvasGuid = this.Guid; var canvasGuid = this.Guid;
flowEnvironment.RemoveNode(canvasGuid, guid); flowEnvironment.FlowEdit.RemoveNode(canvasGuid, guid);
} }
} }
} }
@@ -1464,13 +1464,13 @@ namespace Serein.Workbench.Views
{ {
if (menuItem.Header.ToString() == "启动触发器") if (menuItem.Header.ToString() == "启动触发器")
{ {
flowEnvironment.ActivateFlipflopNode(nodeGuid); flowEnvironment.FlowControl.ActivateFlipflopNode(nodeGuid);
menuItem.Header = "终结触发器"; menuItem.Header = "终结触发器";
} }
else else
{ {
flowEnvironment.TerminateFlipflopNode(nodeGuid); flowEnvironment.FlowControl.TerminateFlipflopNode(nodeGuid);
menuItem.Header = "启动触发器"; menuItem.Header = "启动触发器";
} }
@@ -1490,10 +1490,10 @@ namespace Serein.Workbench.Views
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("设为起点", (s, e) => flowEnvironment.SetStartNode(canvasGuid, nodeGuid))); contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("设为起点", (s, e) => flowEnvironment.FlowEdit.SetStartNode(canvasGuid, nodeGuid)));
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除", async (s, e) => contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除", async (s, e) =>
{ {
flowEnvironment.RemoveNode(canvasGuid, nodeGuid); flowEnvironment.FlowEdit.RemoveNode(canvasGuid, nodeGuid);
})); }));
#region - #region -

View File

@@ -39,7 +39,7 @@ namespace Serein.Workbench.Views
if (sender is Grid grid && grid.DataContext is IFlowNode nodeModel) if (sender is Grid grid && grid.DataContext is IFlowNode nodeModel)
{ {
NodeInfoViewModel.ViewNodeModel = nodeModel; NodeInfoViewModel.ViewNodeModel = nodeModel;
App.GetService<IFlowEnvironment>().NodeLocate(nodeModel.Guid); App.GetService<IFlowEnvironment>().FlowEdit.NodeLocate(nodeModel.Guid);
} }
// 定位节点 // 定位节点