From 97df2a04b2e88d4e7dc6c65f746f58c825bed5a0 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Sun, 22 Jun 2025 21:53:37 +0800 Subject: [PATCH] =?UTF-8?q?LocalFlowEnvironment=E6=96=87=E4=BB=B6=E4=B8=A2?= =?UTF-8?q?=E5=A4=B1=EF=BC=8C=E9=9C=80=E8=A6=81=E9=87=8D=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Api/IFlowEnvironment.cs | 92 +- Library/Api/IFlowNode.cs | 13 +- Library/Api/ISereinIoc.cs | 22 +- Library/FlowNode/MethodDetails.cs | 108 +- ...deMVVMManagement.cs => NodeMVVMService.cs} | 2 +- Library/FlowNode/ParameterDetails.cs | 2 +- Library/Network/Http/Router.cs | 2 +- Library/NodeAttribute.cs | 2 +- Library/NodeStaticConfig.cs | 11 +- Library/Utils/SereinEnv.cs | 4 +- Library/Utils/SereinIoc.cs | 179 +- Net462DllTest/View/FromWorkBenchView.cs | 2 +- NodeFlow/Env/FlowEnvironment.cs | 93 +- NodeFlow/Env/FlowEnvironmentEvent.cs | 36 +- NodeFlow/Env/LocalFlowEnvironment.cs | 3 + NodeFlow/Env/LocalFlowEnvironment_T.cs | 2170 +++++++++++++++++ NodeFlow/Env/RemoteFlowEnvironment.cs | 9 +- NodeFlow/FlowNodeExtension.cs | 2 +- .../Model/{ => Node}/NodeModelBaseData.cs | 13 + .../Model/{ => Node}/NodeModelBaseFunc.cs | 4 +- NodeFlow/Model/{ => Node}/SingleActionNode.cs | 2 +- .../Model/{ => Node}/SingleConditionNode.cs | 0 NodeFlow/Model/{ => Node}/SingleExpOpNode.cs | 0 .../Model/{ => Node}/SingleFlipflopNode.cs | 10 +- .../Model/{ => Node}/SingleFlowCallNode.cs | 7 +- .../Model/{ => Node}/SingleGlobalDataNode.cs | 6 +- .../Model/{ => Node}/SingleNetScriptNode.cs | 0 NodeFlow/Model/{ => Node}/SingleScriptNode.cs | 2 +- NodeFlow/Model/{ => Node}/SingleUINode.cs | 4 +- .../ChangeNodeConnectionOperation.cs | 451 ++++ .../Operation/ChangeParameterOperation.cs | 90 + .../Operation/ContainerPlaceNodeOperation.cs | 98 + .../ContainerTakeOutNodeOperation.cs | 89 + .../Model/Operation/CreateCanvasOperation.cs | 51 + .../Model/Operation/CreateNodeOperation.cs | 145 ++ NodeFlow/Model/Operation/OperationBase.cs | 126 +- .../Model/Operation/RemoveCanvasOperation.cs | 59 + .../Model/Operation/RemoveNodeOperation.cs | 216 ++ .../SetConnectPriorityInvokeOperation.cs | 91 + .../Model/Operation/SetStartNodeOperation.cs | 78 + NodeFlow/Serein.NodeFlow.csproj | 6 + NodeFlow/Services/FlowModelService.cs | 99 + NodeFlow/Services/FlowOperationService.cs | 81 + NodeFlow/{ => Services}/FlowWorkManagement.cs | 8 +- NodeFlow/Services/NodeService.cs | 12 - .../Customs/FlowMethodInfoListBox.xaml.cs | 2 +- .../Node/Junction/JunctionControlBase.cs | 20 +- Workbench/Node/View/ConnectionControl.cs | 4 +- .../ViewModel/ActionNodeControlViewModel.cs | 2 +- .../ViewModel/FlipflopNodeControlViewModel.cs | 2 +- .../Node/ViewModel/UINodeControlViewModel.cs | 2 +- Workbench/Services/FlowEEForwardingService.cs | 11 +- Workbench/Services/FlowNodeService.cs | 12 +- Workbench/Services/FlowProjectService.cs | 5 + Workbench/Themes/MethodDetailsControl.xaml.cs | 2 +- Workbench/ViewModels/MainMenuBarViewModel.cs | 48 +- Workbench/Views/FlowCanvasView.xaml.cs | 15 +- Workbench/Views/MainMenuBarView.xaml | 14 +- 58 files changed, 4285 insertions(+), 354 deletions(-) rename Library/FlowNode/{NodeMVVMManagement.cs => NodeMVVMService.cs} (98%) create mode 100644 NodeFlow/Env/LocalFlowEnvironment_T.cs rename NodeFlow/Model/{ => Node}/NodeModelBaseData.cs (89%) rename NodeFlow/Model/{ => Node}/NodeModelBaseFunc.cs (99%) rename NodeFlow/Model/{ => Node}/SingleActionNode.cs (90%) rename NodeFlow/Model/{ => Node}/SingleConditionNode.cs (100%) rename NodeFlow/Model/{ => Node}/SingleExpOpNode.cs (100%) rename NodeFlow/Model/{ => Node}/SingleFlipflopNode.cs (85%) rename NodeFlow/Model/{ => Node}/SingleFlowCallNode.cs (98%) rename NodeFlow/Model/{ => Node}/SingleGlobalDataNode.cs (97%) rename NodeFlow/Model/{ => Node}/SingleNetScriptNode.cs (100%) rename NodeFlow/Model/{ => Node}/SingleScriptNode.cs (99%) rename NodeFlow/Model/{ => Node}/SingleUINode.cs (94%) create mode 100644 NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs create mode 100644 NodeFlow/Model/Operation/ChangeParameterOperation.cs create mode 100644 NodeFlow/Model/Operation/ContainerPlaceNodeOperation.cs create mode 100644 NodeFlow/Model/Operation/ContainerTakeOutNodeOperation.cs create mode 100644 NodeFlow/Model/Operation/CreateCanvasOperation.cs create mode 100644 NodeFlow/Model/Operation/CreateNodeOperation.cs create mode 100644 NodeFlow/Model/Operation/RemoveCanvasOperation.cs create mode 100644 NodeFlow/Model/Operation/RemoveNodeOperation.cs create mode 100644 NodeFlow/Model/Operation/SetConnectPriorityInvokeOperation.cs create mode 100644 NodeFlow/Model/Operation/SetStartNodeOperation.cs create mode 100644 NodeFlow/Services/FlowModelService.cs create mode 100644 NodeFlow/Services/FlowOperationService.cs rename NodeFlow/{ => Services}/FlowWorkManagement.cs (98%) delete mode 100644 NodeFlow/Services/NodeService.cs diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 4e5cd93..40c485d 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -260,8 +260,8 @@ namespace Serein.Library.Api public NodeConnectChangeEventArgs(string canvasGuid, string fromNodeGuid, string toNodeGuid, - JunctionOfConnectionType junctionOfConnectionType, // 指示需要创建什么类型的连接线 int argIndex, + JunctionOfConnectionType junctionOfConnectionType, // 指示需要创建什么类型的连接线 ConnectionArgSourceType connectionArgSourceType, // 节点对应的方法入参所需参数来源 ConnectChangeType changeType) // 需要创建连接线还是删除连接线 { @@ -296,15 +296,15 @@ namespace Serein.Library.Api /// /// 指示需要创建什么类型的连接线 /// - public JunctionOfConnectionType JunctionOfConnectionType { get;} + public JunctionOfConnectionType JunctionOfConnectionType { get; } = JunctionOfConnectionType.None; /// /// 节点对应的方法入参所需参数来源 /// - public ConnectionArgSourceType ConnectionArgSourceType { get;} + public ConnectionArgSourceType ConnectionArgSourceType { get;} /// /// 第几个参数 /// - public int ArgIndex { get;} + public int ArgIndex { get; } = -1; } @@ -814,7 +814,7 @@ namespace Serein.Library.Api /// /// 节点视图模型管理类 /// - NodeMVVMManagement NodeMVVMManagement { get; } + NodeMVVMService NodeMVVMManagement { get; } #endregion #region 基本接口 @@ -904,23 +904,17 @@ namespace Serein.Library.Api /// 宽度 /// 高度 /// - Task CreateCanvasAsync(string canvasName, int width , int height); + void CreateCanvas(string canvasName, int width , int height); /// /// 删除画布 /// /// 画布Guid /// - Task RemoveCanvasAsync(string canvasGuid); + void RemoveCanvas(string canvasGuid); + - /// - /// 设置流程起点节点 - /// - /// 所在画布 - /// 尝试设置为起始节点的节点Guid - /// 被设置为起始节点的Guid - Task SetStartNodeAsync(string canvasGuid, string nodeGuid); /// /// 在两个节点之间创建连接关系 @@ -931,7 +925,7 @@ namespace Serein.Library.Api /// 起始节点控制点 /// 目标节点控制点 /// 决定了方法执行后的后继行为 - Task ConnectInvokeNodeAsync(string canvasGuid, + void ConnectInvokeNode(string canvasGuid, string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, @@ -948,7 +942,7 @@ namespace Serein.Library.Api /// 目标节点控制点 /// 决定了方法参数来源 /// 设置第几个参数 - Task ConnectArgSourceNodeAsync(string canvasGuid, + void ConnectArgSourceNode(string canvasGuid, string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, @@ -956,6 +950,23 @@ namespace Serein.Library.Api ConnectionArgSourceType argSourceType, int argIndex); + /// + /// 移除两个节点之间的方法调用关系 + /// + /// 所在画布 + /// 起始节点 + /// 目标节点 + /// 连接类型 + void RemoveInvokeConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType); + + /// + /// 移除连接节点之间参数传递的关系 + /// + /// 所在画布 + /// 起始节点Guid + /// 目标节点Guid + /// 连接到第几个参数 + void RemoveArgSourceConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex); /// @@ -965,8 +976,14 @@ namespace Serein.Library.Api /// 控件类型 /// 节点在画布上的位置( /// 节点绑定的方法说明 - Task CreateNodeAsync(string canvasGuid, NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null); + void CreateNode(string canvasGuid, NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null); + /// + /// 移除节点 + /// + /// 所在画布 + /// 待移除的节点Guid + void RemoveNode(string canvasGuid, string nodeGuid); /// /// 将节点放置在容器中 @@ -975,15 +992,22 @@ namespace Serein.Library.Api /// 需要放置的节点Guid /// 存放节点的容器Guid /// - Task PlaceNodeToContainerAsync(string canvasGuid, string nodeGuid, string containerNodeGuid); + void PlaceNodeToContainer(string canvasGuid, string nodeGuid, string containerNodeGuid); /// /// 将节点放置在容器中 /// /// 所在画布 /// 需要取出的节点Guid - Task TakeOutNodeToContainerAsync(string canvasGuid, string nodeGuid); + void TakeOutNodeToContainer(string canvasGuid, string nodeGuid); + /// + /// 设置流程起点节点 + /// + /// 所在画布 + /// 尝试设置为起始节点的节点Guid + /// 被设置为起始节点的Guid + void SetStartNode(string canvasGuid, string nodeGuid); /// /// 设置两个节点某个类型的方法调用关系为优先调用 @@ -992,32 +1016,8 @@ namespace Serein.Library.Api /// 目标节点 /// 连接关系 /// - Task SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType); + void SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType); - /// - /// 移除两个节点之间的方法调用关系 - /// - /// 所在画布 - /// 起始节点 - /// 目标节点 - /// 连接类型 - Task RemoveConnectInvokeAsync(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType); - - /// - /// 移除连接节点之间参数传递的关系 - /// - /// 所在画布 - /// 起始节点Guid - /// 目标节点Guid - /// 连接到第几个参数 - Task RemoveConnectArgSourceAsync(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex); - - /// - /// 移除节点/区域/基础控件 - /// - /// 所在画布 - /// 待移除的节点Guid - Task RemoveNodeAsync(string canvasGuid, string nodeGuid); /// /// 改变可选参数的数目 @@ -1026,8 +1026,7 @@ namespace Serein.Library.Api /// true,增加参数;false,减少参数 /// 以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数) /// - Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex); - + void ChangeParameter(string nodeGuid, bool isAdd, int paramIndex); #endregion @@ -1114,6 +1113,7 @@ namespace Serein.Library.Api /// 需要你提供一个由你实现的ISereinIOC接口实现类 /// 当你将流程运行环境集成在你的项目时,并希望流程运行时使用你提供的对象,而非自动创建 /// 就需要你调用这个方法,用来替换运行环境的IOC容器 + /// 注意,是流程运行时,而非运行环境 /// /// void UseExternalIOC(ISereinIOC ioc); diff --git a/Library/Api/IFlowNode.cs b/Library/Api/IFlowNode.cs index 0d4f829..be7127b 100644 --- a/Library/Api/IFlowNode.cs +++ b/Library/Api/IFlowNode.cs @@ -72,14 +72,19 @@ namespace Serein.Library.Api MethodDetails MethodDetails { get; set; } /// - /// 父节点 + /// 父节点集合 /// Dictionary> PreviousNodes { get;} /// - /// 子节点 + /// 子节点集合 /// Dictionary> SuccessorNodes { get; set; } + /// + /// 需要该节点返回结果作为入参参数的节点集合 + /// + Dictionary> NeedResultNodes { get; } + /// /// 当该节点放置在某个具有容器行为的节点时,该值指示其容器节点 /// @@ -94,10 +99,10 @@ namespace Serein.Library.Api /// 节点创建时的行为 /// void OnCreating(); - /// + /*/// /// 节点移除时的行为 /// - void Remove(); + void Remove();*/ /// /// 节点保存时如若需要保存自定义数据,可通过该方法进行控制保存逻辑 diff --git a/Library/Api/ISereinIoc.cs b/Library/Api/ISereinIoc.cs index 401c0f4..2aade37 100644 --- a/Library/Api/ISereinIoc.cs +++ b/Library/Api/ISereinIoc.cs @@ -18,14 +18,14 @@ namespace Serein.Library.Api ISereinIOC Reset(); /// - /// 通过指定类型的方式注册实例 + /// 通过指定类型的方式注册实例,该类型的实例由你提供 /// /// 实例类型 /// ISereinIOC Register(Type type); /// - /// 通过指定类型的方式注册实例 + /// 通过指定类型的方式注册实例,该类型将由IOC容器自动创建 /// /// 实例类型 /// 获取实例的回调函数 @@ -33,14 +33,14 @@ namespace Serein.Library.Api ISereinIOC Register(Type type, Func getInstance); /// - /// 通过泛型的方式注册实例 + /// 通过泛型的方式注册类型,该类型将由IOC容器自动创建 /// /// 实例类型 /// ISereinIOC Register(); /// - /// 通过泛型的方式注册实例 + /// 通过泛型的方式注册类型,该类型的实例由你提供 /// /// 实例类型 /// 获取实例的回调函数 @@ -48,7 +48,7 @@ namespace Serein.Library.Api ISereinIOC Register(Func getInstance); /// - /// 注册接口的实例 + /// 注册接口的实例,该接口类型的实现类实例由你提供 /// /// 接口类型 /// 实例类型 @@ -78,13 +78,21 @@ namespace Serein.Library.Api /// 给定一个类型,由IOC容器负责创建实例,如果存在多个构造函数,将由参数最多的构造函数开始尝试创建。 /// /// - object CreateTempObject(Type type); + object CreateObject(Type type); /// /// 给定一个类型,由IOC容器负责创建实例,如果存在多个构造函数,将由参数最多的构造函数开始尝试创建。 /// /// - T CreateTempObject(); + T CreateObject(); + + /// + /// 给定一个实例,尽可能地在该实例中具有[AutoInjection]特性的属性上,设置为IOC容器中已有的对应类型的对象。 + /// + /// 对应的类型 + /// 传入的实例 + /// + T InjectDependenciesProperty(T instance); /// /// 搜寻已注册的类型生成依赖关系,依次实例化并注入依赖项,缓存在由IOC容器维护的Map中,直到手动调用Reset()方法。 diff --git a/Library/FlowNode/MethodDetails.cs b/Library/FlowNode/MethodDetails.cs index 8aefd42..1b380c2 100644 --- a/Library/FlowNode/MethodDetails.cs +++ b/Library/FlowNode/MethodDetails.cs @@ -103,51 +103,101 @@ namespace Serein.Library /// 新增可变参数 /// /// - public bool AddParamsArg(int index = 0) + public bool AddParamsArg(int index) { - if (ParamsArgIndex >= 0 // 方法是否包含可变参数 - && index >= 0 // 如果包含,则判断从哪个参数赋值 - && index >= ParamsArgIndex // 需要判断是否为可选参数的部分 - && index < ParameterDetailss.Length) // 防止下标越界 - { - var newPd = ParameterDetailss[index].CloneOfModel(this.NodeModel); // 复制出属于本身节点的参数描述 - newPd.Index = ParameterDetailss.Length; // 更新索引 - newPd.IsParams = true; - ParameterDetailss = ArrayHelper.AddToArray(ParameterDetailss, newPd); // 新增 - return true; - } - else + if (ParamsArgIndex < 0 // 方法是否包含可变参数 + || index < 0 // 如果包含,则判断从哪个参数赋值 + || index < ParamsArgIndex // 需要判断是否为可选参数的部分 + || index >= ParameterDetailss.Length) // 防止下标越界 { return false; } + + var newPd = ParameterDetailss[index].CloneOfModel(this.NodeModel); // 复制出属于本身节点的参数描述 + newPd.Index = ParameterDetailss.Length; // 更新索引 + newPd.IsParams = true; + ParameterDetailss = ArrayHelper.AddToArray(ParameterDetailss, newPd); // 新增 + return true; } + + + public bool AddParamsArg(ParameterDetails parameterDetails) + { + if (ParamsArgIndex < 0) // 方法是否包含可变参数 + { + return false; + } + if (parameterDetails is null) + { + return false; + } + parameterDetails.Index = ParameterDetailss.Length; // 更新索引 + parameterDetails.IsParams = true; + ParameterDetailss = ArrayHelper.AddToArray(ParameterDetailss, parameterDetails); // 新增 + return true; + + } + + /// /// 移除可变参数 /// /// - public bool RemoveParamsArg(int index = 0) + public bool RemoveParamsArg(int index) { - if (ParamsArgIndex >= 0 // 方法是否包含可变参数 - && index >= 0 // 如果包含,则判断从哪个参数赋值 - && index > ParamsArgIndex // 需要判断是否为可选参数的部分,并且不能删除原始的可变参数描述 - && index < ParameterDetailss.Length) // 防止下标越界 - { - ParameterDetailss[index] = null; // 释放对象引用 - - - var tmp = ArrayHelper.RemoteToArray(ParameterDetailss, index); // 新增; - UpdateParamIndex(ref tmp); - ParameterDetailss = tmp; // 新增 - return true; - } - else + if (ParamsArgIndex < 0 // 方法是否包含可变参数 + || index < 0 // 如果包含,则判断从哪个参数赋值 + || index <= ParamsArgIndex // 需要判断是否为可选参数的部分,并且不能删除原始的可变参数描述 + || index >= ParameterDetailss.Length) // 防止下标越界 { return false; } - + + ParameterDetailss[index] = null; // 释放对象引用 + var tmp = ArrayHelper.RemoteToArray(ParameterDetailss, index); // 新增; + UpdateParamIndex(ref tmp); + ParameterDetailss = tmp; // 新增 + return true; } + /// + /// 移除可变参数 + /// + /// + public bool RemoveParamsArg(ParameterDetails parameterDetails) + { + if (ParamsArgIndex < 0) // 方法是否包含可变参数 + { + return false; + } + if (parameterDetails is null) + { + return false; + } + int index = -1; + for (int i = 0; i < ParameterDetailss.Length; i++) + { + var pd = ParameterDetailss[i]; + if (pd.Equals(parameterDetails)) + { + index = i; + break; + } + } + if (index == -1) + { + return false; + } + + ParameterDetailss[index] = null; // 释放对象引用 + var tmp = ArrayHelper.RemoteToArray(ParameterDetailss, index); // 新增; + UpdateParamIndex(ref tmp); + ParameterDetailss = tmp; // 新增 + return true; + } + + /// /// 更新参数的索引 /// diff --git a/Library/FlowNode/NodeMVVMManagement.cs b/Library/FlowNode/NodeMVVMService.cs similarity index 98% rename from Library/FlowNode/NodeMVVMManagement.cs rename to Library/FlowNode/NodeMVVMService.cs index ee7def3..a40d1f7 100644 --- a/Library/FlowNode/NodeMVVMManagement.cs +++ b/Library/FlowNode/NodeMVVMService.cs @@ -45,7 +45,7 @@ namespace Serein.Library /// /// 节点 数据、视图、VM 管理 /// - public class NodeMVVMManagement + public class NodeMVVMService { /// /// 节点对应的控件类型 diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 097ae0d..b7c732b 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -246,7 +246,7 @@ namespace Serein.Library var type = EnumHelper.GetBoundValue(ExplicitType, resultEnum, attr => attr.Value); if (type is Type enumBindType && !(enumBindType is null)) { - var value = nodeModel.Env.IOC.CreateTempObject(enumBindType); + var value = nodeModel.Env.IOC.CreateObject(enumBindType); return value; } } diff --git a/Library/Network/Http/Router.cs b/Library/Network/Http/Router.cs index 6538203..c7c4ce8 100644 --- a/Library/Network/Http/Router.cs +++ b/Library/Network/Http/Router.cs @@ -145,7 +145,7 @@ namespace Serein.Library.Web return false; // 没有对应的处理配置 } - ControllerBase controllerInstance = (ControllerBase)SereinIOC.CreateTempObject(controllerType); + ControllerBase controllerInstance = (ControllerBase)SereinIOC.CreateObject(controllerType); if (controllerInstance is null) { diff --git a/Library/NodeAttribute.cs b/Library/NodeAttribute.cs index 68da2fd..fcaf0df 100644 --- a/Library/NodeAttribute.cs +++ b/Library/NodeAttribute.cs @@ -9,7 +9,7 @@ namespace Serein.Library /// 这种情况会导致流程启动时,IOC容器无法注入构造函数并创建类型,导致启动失败。 /// 解决方法:从ServiceA类的构造函数中移除ServiceB类型的入参,将该类型更改为公开可见的可写属性成员ServiceB serviceB{get;set;},并在该属性上标记[AutoInjection]特性 /// - [AttributeUsage(AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public sealed class AutoInjectionAttribute : Attribute { } diff --git a/Library/NodeStaticConfig.cs b/Library/NodeStaticConfig.cs index 6931234..c3b8ff2 100644 --- a/Library/NodeStaticConfig.cs +++ b/Library/NodeStaticConfig.cs @@ -21,10 +21,19 @@ namespace Serein.Library /// public static readonly ConnectionInvokeType[] ConnectionTypes = new ConnectionInvokeType[] { - ConnectionInvokeType.Upstream, + ConnectionInvokeType.Upstream, ConnectionInvokeType.IsSucceed, ConnectionInvokeType.IsFail, ConnectionInvokeType.IsError, }; + /// + /// 节点连接关系种类 + /// + public static readonly ConnectionArgSourceType[] ConnectionArgSourceTypes = new ConnectionArgSourceType[] + { + ConnectionArgSourceType.GetPreviousNodeData, + ConnectionArgSourceType.GetOtherNodeData, + ConnectionArgSourceType.GetOtherNodeDataOfInvoke, + }; } } diff --git a/Library/Utils/SereinEnv.cs b/Library/Utils/SereinEnv.cs index 8ecc03c..bcecb5d 100644 --- a/Library/Utils/SereinEnv.cs +++ b/Library/Utils/SereinEnv.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Linq; using System.Text; @@ -122,7 +123,8 @@ namespace Serein.Library /// 级别 public static void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.General) { - SereinEnv.environment.WriteLine(type,message,@class); + Debug.WriteLine($"{type} : {message}"); + SereinEnv.environment?.WriteLine(type,message,@class); } /// diff --git a/Library/Utils/SereinIoc.cs b/Library/Utils/SereinIoc.cs index 3fa7b45..84b062f 100644 --- a/Library/Utils/SereinIoc.cs +++ b/Library/Utils/SereinIoc.cs @@ -137,13 +137,13 @@ namespace Serein.Library.Utils #endregion - #region 示例的获取 + #region 示例的创建 /// /// 用于临时实例的创建,不登记到IOC容器中,依赖项注入失败时也不记录。 /// /// /// - public object CreateTempObject(Type type) + public object CreateObject(Type type) { var ctors = GetConstructor(type); // 获取构造函数 object instance = null; @@ -180,10 +180,55 @@ namespace Serein.Library.Utils /// /// /// - public T CreateTempObject() + public T CreateObject() { - return (T)CreateTempObject(typeof(T)); - } + return (T)CreateObject(typeof(T)); + } + + + /// + /// 给定一个实例,尽可能地在该实例中具有[AutoInjection]特性的属性/字段上,设置为IOC容器中已有的对应类型的对象。 + /// + /// + /// + /// + public T InjectDependenciesProperty(T instance) + { + var type = instance.GetType(); + var properties = type.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToArray() + .Where(p => p.CanWrite // 可写属性 + && p.GetCustomAttribute() != null // 有特性标注需要注入 + && p.GetValue(instance) == null); // 属性为空 + + // 属性注入 + foreach (var property in properties) + { + var propertyType = property.PropertyType; + if (_dependencies.TryGetValue(propertyType.FullName, out var dependencyInstance)) + { + property.SetValue(instance, dependencyInstance); // 尝试写入到目标实例的属性中 + } + } + + + // 字段注入 + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ) + .Where(f => f.GetCustomAttribute() != null + && f.GetValue(instance) == null); + + foreach (var field in fields) + { + var fieldType = field.FieldType; + if (_dependencies.TryGetValue(fieldType.FullName, out var dependencyInstance)) + { + field.SetValue(instance, dependencyInstance); + } + } + + return instance; + } + #endregion #region 通过名称记录或获取一个实例 @@ -257,56 +302,62 @@ namespace Serein.Library.Utils public string Name { get; set; } public Type Type { get; set; } } - private const string FlowBaseClassName = "@LibraryRootNode"; + + + private const string IOC_MAIN = "*Priority Instantiation*"; /// - /// 构建依赖关系树 + /// 遍历所有需要注册的类型,获取到它们所有构造函数,并统计每个构造函数的入参类型,构建依赖关系树 /// - /// + /// “ID-PID”关系的树形结构 private Dictionary> BuildDependencyTree() { var dependencyMap = new Dictionary>(); - dependencyMap[FlowBaseClassName] = new HashSet(); + dependencyMap[IOC_MAIN] = new HashSet(); // 优先实例化 foreach (var typeMapping in _typeMappings) { - var constructors = GetConstructor(typeMapping.Value); // 获取构造函数 + var typeFullName = typeMapping.Key; // 注册的类型 FullName + var type = typeMapping.Value; // 对应的Type。如果是以接口形式注册,typeFullName将是接口类的FullName,而type将是接口实现类。 + var constructors = GetConstructor(type); // 获取构造函数 + foreach (var constructor in constructors) { - if (constructor != null) + if (constructor is null) { - var parameters = constructor.GetParameters() - .Select(p => p.ParameterType) - .ToList(); - if (parameters.Count == 0) // 无参的构造函数 + continue; + } + var parameters = constructor.GetParameters().Select(p => p.ParameterType); + var ctorCount = constructors.Length; + var ctorParamCount = parameters.Count(); + + // 类型仅有一个构造函数,并且无参,将优先实例化 + if (ctorCount == 1 && ctorParamCount == 0) + { + if (!dependencyMap[IOC_MAIN].Contains(type.FullName)) { - var type = typeMapping.Value; - if (!dependencyMap[FlowBaseClassName].Contains(type.FullName)) - { - dependencyMap[FlowBaseClassName].Add(type.FullName); - } + dependencyMap[IOC_MAIN].Add(type.FullName); + } + continue; + } + + // 从类型的有参构造函数中提取类型 + foreach (var param in parameters) + { + if (!dependencyMap.TryGetValue(param.FullName, out var hashSet)) + { + hashSet = new HashSet(); + hashSet.Add(typeMapping.Key); + dependencyMap.Add(param.FullName, hashSet); } else { - // 从类型的有参构造函数中提取类型 - foreach (var param in parameters) + if (!hashSet.Contains(typeMapping.Key)) { - if (!dependencyMap.TryGetValue(param.FullName, out var hashSet)) - { - hashSet = new HashSet(); - hashSet.Add(typeMapping.Key); - dependencyMap.Add(param.FullName, hashSet); - } - else - { - if (!hashSet.Contains(typeMapping.Key)) - { - hashSet.Add(typeMapping.Key); - } - } - + hashSet.Add(typeMapping.Key); } } } + } } @@ -315,7 +366,7 @@ namespace Serein.Library.Utils } /// - /// 获取类型的获取所有构造函数 + /// 获取类型的所有构造函数,根据入参数量,由多到少排列 /// /// /// @@ -325,42 +376,46 @@ namespace Serein.Library.Utils } /// - /// 创建示例的生成顺序 + /// 创建实例的生成顺序 /// - /// + /// 依赖关系树 /// public List GetCreationOrder(Dictionary> dependencyMap) { - var graph = new Dictionary>(); - var indegree = new Dictionary(); + var graph = new Dictionary>(); // 另一种依赖关系树 + var indegree = new Dictionary(); // 表示出现次数 foreach (var entry in dependencyMap) { - var key = entry.Key; - if (!graph.ContainsKey(key)) + + // “rootNode”是注册类的类型FullName属性 + var rootNode = entry.Key; // 根节点 + + if (!graph.ContainsKey(rootNode)) { - graph[key] = new List(); + graph[rootNode] = new List(); } - foreach (var dependent in entry.Value) + // “childNode”是注册类构造函数中出现过的参数的类型FullName属性 + foreach (var childNode in entry.Value) { - if (!graph.ContainsKey(dependent)) + if (!graph.ContainsKey(childNode)) { - graph[dependent] = new List(); + graph[childNode] = new List(); } - graph[key].Add(dependent); + graph[rootNode].Add(childNode); // 更新入度 - if (!indegree.ContainsKey(dependent)) + if (!indegree.ContainsKey(childNode)) { - indegree[dependent] = 0; + indegree[childNode] = 0; } - indegree[dependent]++; + indegree[childNode]++; } - if (!indegree.ContainsKey(key)) + if (!indegree.ContainsKey(rootNode)) { - indegree[key] = 0; + indegree[rootNode] = 0; } } @@ -386,10 +441,10 @@ namespace Serein.Library.Utils if (tmpList.Count > 0) { StringBuilder sb = new StringBuilder(); - sb.Append("以下类型可能产生循环依赖,请避免循环依赖,如果确实需要循环引用,请使用 [AutoInjection] 特性注入属性"); + sb.Append("以下类型存在循环依赖,请避免循环依赖,如果确实需要循环引用,请使用 [AutoInjection] 特性注入属性"); foreach (var kv in tmpList) { - sb.AppendLine($"Class Name : {kv}"); + sb.AppendLine($"类名 : {kv}"); } SereinEnv.WriteLine(InfoType.ERROR, sb.ToString()); } @@ -412,9 +467,9 @@ namespace Serein.Library.Utils { return instance; } - if (_registerCallback.TryGetValue(typeName,out var obj)) + if (_registerCallback.TryGetValue(typeName,out var getInstance)) { - instance = obj; + return getInstance.Invoke(); } // 字符串、值类型,抽象类型,暂时不支持自动创建 @@ -427,7 +482,7 @@ namespace Serein.Library.Utils { // 没有显示指定构造函数入参,选择参数最多的构造函数 //var constructor = GetConstructorWithMostParameters(type); - var constructors = GetConstructor(type); // 获取参数最多的构造函数 + var constructors = GetConstructor(type); // 获取构造函数 foreach(var constructor in constructors) { @@ -480,15 +535,16 @@ namespace Serein.Library.Utils /// public ISereinIOC Build() { - var dependencyTree = BuildDependencyTree(); - var creationOrder = GetCreationOrder(dependencyTree); + var dependencyTree = BuildDependencyTree(); // 生成类型依赖关系 + var creationOrder = GetCreationOrder(dependencyTree); // 生成创建顺序 // 输出创建顺序 - Debug.WriteLine("创建顺序: " + string.Join(" → ", creationOrder)); + Debug.WriteLine("创建顺序: " + string.Join($"{Environment.NewLine}↓ {Environment.NewLine}", creationOrder)); // 创建对象 foreach (var typeName in creationOrder) { + if (_dependencies.ContainsKey(typeName)) { continue; @@ -496,6 +552,7 @@ namespace Serein.Library.Utils var value = CreateInstance(typeName); if(value is null) { + SereinEnv.WriteLine(InfoType.ERROR, $"IOC容器无法创建对象:{typeName}"); continue; } _dependencies[typeName] = value; diff --git a/Net462DllTest/View/FromWorkBenchView.cs b/Net462DllTest/View/FromWorkBenchView.cs index 60a51c5..d5d9c1e 100644 --- a/Net462DllTest/View/FromWorkBenchView.cs +++ b/Net462DllTest/View/FromWorkBenchView.cs @@ -21,7 +21,7 @@ namespace Net462DllTest if (ViewModel is null) { SereinEnv.WriteLine(InfoType.INFO, "创建对象并注入依赖项"); - ViewModel = env.IOC.CreateTempObject(); + ViewModel = env.IOC.CreateObject(); } BindData(); } diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 1f6b34b..5efc858 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -2,6 +2,7 @@ using Serein.Library.Api; using Serein.Library.FlowNode; using Serein.Library.Utils; +using Serein.NodeFlow.Services; using Serein.NodeFlow.Tool; using System.Reflection; @@ -14,15 +15,26 @@ namespace Serein.NodeFlow.Env { public FlowEnvironment() { - flowEnvironmentEvent = new FlowEnvironmentEvent(); - flowEnvironment = new LocalFlowEnvironment(flowEnvironmentEvent); + ISereinIOC sereinIOC = new SereinIOC(); + sereinIOC.Reset(); + sereinIOC.Register(()=> sereinIOC); // 注册IOC + sereinIOC.Register(() => this); + sereinIOC.Register(); + sereinIOC.Register(); + sereinIOC.Register(); + sereinIOC.Register(); + sereinIOC.Register(); + sereinIOC.Register(); + sereinIOC.Build(); + this.IOC = sereinIOC; + // 默认使用本地环境 - currentFlowEnvironment = flowEnvironment; - currentFlowEnvironmentEvent = flowEnvironmentEvent; + currentFlowEnvironment = sereinIOC.Get(); + currentFlowEnvironmentEvent = sereinIOC.Get(); SereinEnv.SetEnv(currentFlowEnvironment); } - /// + /* /// /// 本地环境 /// private readonly LocalFlowEnvironment flowEnvironment; @@ -30,7 +42,7 @@ namespace Serein.NodeFlow.Env /// /// 远程环境 /// - private RemoteFlowEnvironment remoteFlowEnvironment; + private RemoteFlowEnvironment remoteFlowEnvironment;*/ /// /// 本地环境事件 @@ -79,9 +91,10 @@ namespace Serein.NodeFlow.Env /// public UIContextOperation UIContextOperation => currentFlowEnvironment.UIContextOperation; /// - public NodeMVVMManagement NodeMVVMManagement => currentFlowEnvironment.NodeMVVMManagement; + public NodeMVVMService NodeMVVMManagement => currentFlowEnvironment.NodeMVVMManagement; + /// - public ISereinIOC IOC => currentFlowEnvironment.IOC; + public ISereinIOC IOC { get; set; } /// public IFlowEnvironmentEvent Event => currentFlowEnvironment.Event; @@ -239,30 +252,30 @@ namespace Serein.NodeFlow.Env } /// - public async Task CreateCanvasAsync(string canvasName, int width, int height) + public void CreateCanvas(string canvasName, int width, int height) { - return await currentFlowEnvironment.CreateCanvasAsync(canvasName, width, height); + currentFlowEnvironment.CreateCanvas(canvasName, width, height); } /// - public async Task RemoveCanvasAsync(string canvasGuid) + public void RemoveCanvas(string canvasGuid) { - return await currentFlowEnvironment.RemoveCanvasAsync(canvasGuid); + currentFlowEnvironment.RemoveCanvas(canvasGuid); } /// - public async Task ConnectInvokeNodeAsync(string canvasGuid, + public void ConnectInvokeNode(string canvasGuid, string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionInvokeType invokeType) { - return await currentFlowEnvironment.ConnectInvokeNodeAsync(canvasGuid, fromNodeGuid, toNodeGuid, fromNodeJunctionType, toNodeJunctionType, invokeType); + currentFlowEnvironment.ConnectInvokeNode(canvasGuid, fromNodeGuid, toNodeGuid, fromNodeJunctionType, toNodeJunctionType, invokeType); } /// - public async Task ConnectArgSourceNodeAsync(string canvasGuid, + public void ConnectArgSourceNode(string canvasGuid, string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, @@ -270,7 +283,7 @@ namespace Serein.NodeFlow.Env ConnectionArgSourceType argSourceType, int argIndex) { - return await currentFlowEnvironment.ConnectArgSourceNodeAsync(canvasGuid, fromNodeGuid, toNodeGuid, fromNodeJunctionType, toNodeJunctionType, argSourceType, argIndex); + currentFlowEnvironment.ConnectArgSourceNode(canvasGuid, fromNodeGuid, toNodeGuid, fromNodeJunctionType, toNodeJunctionType, argSourceType, argIndex); } /// @@ -281,8 +294,8 @@ namespace Serein.NodeFlow.Env if (isConnect) { - remoteFlowEnvironment ??= new RemoteFlowEnvironment(remoteMsgUtil, this.Event, this.UIContextOperation); - currentFlowEnvironment = remoteFlowEnvironment; + /* remoteFlowEnvironment ??= new RemoteFlowEnvironment(remoteMsgUtil, this.Event, this.UIContextOperation); + currentFlowEnvironment = remoteFlowEnvironment;*/ } return (isConnect, remoteMsgUtil); } @@ -296,30 +309,27 @@ namespace Serein.NodeFlow.Env } /// - public async Task CreateNodeAsync(string canvasGuid, NodeControlType nodeBase, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null) + public void CreateNode(string canvasGuid, NodeControlType nodeBase, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null) { SetProjectLoadingFlag(false); - var result = await currentFlowEnvironment.CreateNodeAsync(canvasGuid, nodeBase, position, methodDetailsInfo); // 装饰器调用 + currentFlowEnvironment.CreateNode(canvasGuid, nodeBase, position, methodDetailsInfo); // 装饰器调用 SetProjectLoadingFlag(true); - return result; } /// - public async Task PlaceNodeToContainerAsync(string canvasGuid, string nodeGuid, string containerNodeGuid) + public void PlaceNodeToContainer(string canvasGuid, string nodeGuid, string containerNodeGuid) { SetProjectLoadingFlag(false); - var result = await currentFlowEnvironment.PlaceNodeToContainerAsync(canvasGuid, nodeGuid, containerNodeGuid); // 装饰器调用 + currentFlowEnvironment.PlaceNodeToContainer(canvasGuid, nodeGuid, containerNodeGuid); // 装饰器调用 SetProjectLoadingFlag(true); - return result; } /// - public async Task TakeOutNodeToContainerAsync(string canvasGuid, string nodeGuid) + public void TakeOutNodeToContainer(string canvasGuid, string nodeGuid) { SetProjectLoadingFlag(false); - var result = await currentFlowEnvironment.TakeOutNodeToContainerAsync(canvasGuid,nodeGuid); // 装饰器调用 + currentFlowEnvironment.TakeOutNodeToContainer(canvasGuid,nodeGuid); // 装饰器调用 SetProjectLoadingFlag(true); - return result; } /// @@ -393,27 +403,27 @@ namespace Serein.NodeFlow.Env } /// - public async Task SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) + public void SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) { - return await currentFlowEnvironment.SetConnectPriorityInvoke(fromNodeGuid, toNodeGuid, connectionType); + currentFlowEnvironment.SetConnectPriorityInvoke(fromNodeGuid, toNodeGuid, connectionType); } /// - public async Task RemoveConnectInvokeAsync(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) + public void RemoveInvokeConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) { - return await currentFlowEnvironment.RemoveConnectInvokeAsync(canvasGuid, fromNodeGuid, toNodeGuid, connectionType); + currentFlowEnvironment.RemoveInvokeConnect(canvasGuid, fromNodeGuid, toNodeGuid, connectionType); } /// - public async Task RemoveConnectArgSourceAsync(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex) + public void RemoveArgSourceConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex) { - return await currentFlowEnvironment.RemoveConnectArgSourceAsync(canvasGuid, fromNodeGuid, toNodeGuid, argIndex); + currentFlowEnvironment.RemoveArgSourceConnect(canvasGuid, fromNodeGuid, toNodeGuid, argIndex); } /// - public async Task RemoveNodeAsync(string canvasGuid, string nodeGuid) + public void RemoveNode(string canvasGuid, string nodeGuid) { - return await currentFlowEnvironment.RemoveNodeAsync(canvasGuid, nodeGuid); + currentFlowEnvironment.RemoveNode(canvasGuid, nodeGuid); } @@ -460,9 +470,9 @@ namespace Serein.NodeFlow.Env #endregion /// - public async Task SetStartNodeAsync(string canvasGuid, string nodeGuid) + public void SetStartNode(string canvasGuid, string nodeGuid) { - return await currentFlowEnvironment.SetStartNodeAsync(canvasGuid, nodeGuid); + currentFlowEnvironment.SetStartNode(canvasGuid, nodeGuid); } /// @@ -504,6 +514,11 @@ namespace Serein.NodeFlow.Env /// public void SetUIContextOperation(UIContextOperation uiContextOperation) { + if(uiContextOperation is null) + { + return; + } + IOC.Register(() => uiContextOperation).Build(); currentFlowEnvironment.SetUIContextOperation(uiContextOperation); } @@ -545,9 +560,9 @@ namespace Serein.NodeFlow.Env } /// - public async Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) + public void ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) { - return await currentFlowEnvironment.ChangeParameter(nodeGuid, isAdd, paramIndex); + currentFlowEnvironment.ChangeParameter(nodeGuid, isAdd, paramIndex); } #region 流程依赖类库的接口 diff --git a/NodeFlow/Env/FlowEnvironmentEvent.cs b/NodeFlow/Env/FlowEnvironmentEvent.cs index ba4f8f2..276150a 100644 --- a/NodeFlow/Env/FlowEnvironmentEvent.cs +++ b/NodeFlow/Env/FlowEnvironmentEvent.cs @@ -26,92 +26,92 @@ namespace Serein.NodeFlow.Env public void OnDllLoad(LoadDllEventArgs eventArgs) { - DllLoad.Invoke(eventArgs); + DllLoad?.Invoke(eventArgs); } public void OnProjectLoaded(ProjectLoadedEventArgs eventArgs) { - ProjectLoaded.Invoke(eventArgs); + ProjectLoaded?.Invoke(eventArgs); } public void OnProjectSaving(ProjectSavingEventArgs eventArgs) { - ProjectSaving.Invoke(eventArgs); + ProjectSaving?.Invoke(eventArgs); } public void OnNodeConnectChanged(NodeConnectChangeEventArgs eventArgs) { - NodeConnectChanged.Invoke(eventArgs); + NodeConnectChanged?.Invoke(eventArgs); } public void OnCanvasCreated(CanvasCreateEventArgs eventArgs) { - CanvasCreated.Invoke(eventArgs); + CanvasCreated?.Invoke(eventArgs); } public void OnCanvasRemoved(CanvasRemoveEventArgs eventArgs) { - CanvasRemoved.Invoke(eventArgs); + CanvasRemoved?.Invoke(eventArgs); } public void OnNodeCreated(NodeCreateEventArgs eventArgs) { - NodeCreated.Invoke(eventArgs); + NodeCreated?.Invoke(eventArgs); } public void OnNodeRemoved(NodeRemoveEventArgs eventArgs) { - NodeRemoved.Invoke(eventArgs); + NodeRemoved?.Invoke(eventArgs); } public void OnNodePlace(NodePlaceEventArgs eventArgs) { - NodePlace.Invoke(eventArgs); + NodePlace?.Invoke(eventArgs); } public void OnNodeTakeOut(NodeTakeOutEventArgs eventArgs) { - NodeTakeOut.Invoke(eventArgs); + NodeTakeOut?.Invoke(eventArgs); } public void OnStartNodeChanged(StartNodeChangeEventArgs eventArgs) { - StartNodeChanged.Invoke(eventArgs); + StartNodeChanged?.Invoke(eventArgs); } public void OnFlowRunComplete(FlowEventArgs eventArgs) { - FlowRunComplete.Invoke(eventArgs); + FlowRunComplete?.Invoke(eventArgs); } public void OnMonitorObjectChanged(MonitorObjectEventArgs eventArgs) { - MonitorObjectChanged.Invoke(eventArgs); + MonitorObjectChanged?.Invoke(eventArgs); } public void OnNodeInterruptStateChanged(NodeInterruptStateChangeEventArgs eventArgs) { - NodeInterruptStateChanged.Invoke(eventArgs); + NodeInterruptStateChanged?.Invoke(eventArgs); } public void OnInterruptTriggered(InterruptTriggerEventArgs eventArgs) { - InterruptTriggered.Invoke(eventArgs); + InterruptTriggered?.Invoke(eventArgs); } public void OnIOCMembersChanged(IOCMembersChangedEventArgs eventArgs) { - IOCMembersChanged.Invoke(eventArgs); + IOCMembersChanged?.Invoke(eventArgs); } public void OnNodeLocated(NodeLocatedEventArgs eventArgs) { - NodeLocated.Invoke(eventArgs); + NodeLocated?.Invoke(eventArgs); } public void OnEnvOutput(InfoType type, string value) { - EnvOutput.Invoke(type, value); + EnvOutput?.Invoke(type, value); } diff --git a/NodeFlow/Env/LocalFlowEnvironment.cs b/NodeFlow/Env/LocalFlowEnvironment.cs index 135bc66..236d219 100644 --- a/NodeFlow/Env/LocalFlowEnvironment.cs +++ b/NodeFlow/Env/LocalFlowEnvironment.cs @@ -89,6 +89,9 @@ namespace Serein.NodeFlow.Env #endregion } + + + #region 远程管理 private MsgControllerOfServer clientMsgManage; diff --git a/NodeFlow/Env/LocalFlowEnvironment_T.cs b/NodeFlow/Env/LocalFlowEnvironment_T.cs new file mode 100644 index 0000000..0c8ff34 --- /dev/null +++ b/NodeFlow/Env/LocalFlowEnvironment_T.cs @@ -0,0 +1,2170 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.FlowNode; +using Serein.Library.Utils; +using Serein.Library.Utils.SereinExpression; +using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Node; +using Serein.NodeFlow.Services; +using Serein.NodeFlow.Tool; +using System; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Net.Http.Headers; +using System.Net.Mime; +using System.Reactive; +using System.Reflection; +using System.Text; + +namespace Serein.NodeFlow.Env +{ + + /// + /// 运行环境 + /// + public class LocalFlowEnvironment : IFlowEnvironment/*, IFlowEnvironmentEvent*/ + { + /// + /// 节点的命名空间 + /// + public const string SpaceName = $"{nameof(Serein)}.{nameof(NodeFlow)}.{nameof(Model)}"; + public const string ThemeKey = "theme"; + public const string DataKey = "data"; + public const string MsgIdKey = "msgid"; + + /// + /// 流程运行环境 + /// + public LocalFlowEnvironment(IFlowEnvironmentEvent flowEnvironmentEvent, + NodeMVVMService nodeMVVMService, + FlowLibraryManagement flowLibraryManagement, + FlowModelService flowModelService, + ISereinIOC sereinIOC) + { + this.FlowModelService = flowModelService; + this.Event = flowEnvironmentEvent; + this.FlowLibraryManagement = flowLibraryManagement; // 实例化类库管理 + this.NodeMVVMManagement = nodeMVVMService; + this.FlowEnvironmentIOC = sereinIOC; + this.IsGlobalInterrupt = false; + + #region 注册基本节点类型 + NodeMVVMManagement.RegisterModel(NodeControlType.UI, typeof(SingleUINode)); // 动作节点 + NodeMVVMManagement.RegisterModel(NodeControlType.Action, typeof(SingleActionNode)); // 动作节点 + NodeMVVMManagement.RegisterModel(NodeControlType.Flipflop, typeof(SingleFlipflopNode)); // 触发器节点 + NodeMVVMManagement.RegisterModel(NodeControlType.ExpOp, typeof(SingleExpOpNode)); // 表达式节点 + NodeMVVMManagement.RegisterModel(NodeControlType.ExpCondition, typeof(SingleConditionNode)); // 条件表达式节点 + NodeMVVMManagement.RegisterModel(NodeControlType.GlobalData, typeof(SingleGlobalDataNode)); // 全局数据节点 + NodeMVVMManagement.RegisterModel(NodeControlType.Script, typeof(SingleScriptNode)); // 脚本节点 + NodeMVVMManagement.RegisterModel(NodeControlType.NetScript, typeof(SingleNetScriptNode)); // 脚本节点 + NodeMVVMManagement.RegisterModel(NodeControlType.FlowCall, typeof(SingleFlowCallNode)); // 流程调用节点 + #endregion + + } + + #region 远程管理 + + private MsgControllerOfServer clientMsgManage; + + /// + /// 表示是否正在控制远程 + /// Local control remote env + /// + public bool IsControlRemoteEnv { get; set; } + + /// + /// 打开远程管理 + /// + /// + public async Task StartRemoteServerAsync(int port = 7525) + { + if (clientMsgManage is null) + { + clientMsgManage = new MsgControllerOfServer(this); + //clientMsgManage = new MsgControllerOfServer(this,"123456"); + } + _ = clientMsgManage.StartRemoteServerAsync(port); + } + + /// + /// 结束远程管理 + /// + public void StopRemoteServer() + { + try + { + clientMsgManage.StopRemoteServer(); + } + catch (Exception ex) + { + SereinEnv.WriteLine(InfoType.ERROR, "结束远程管理异常:" + ex); + } + } + + #endregion + + #region 环境运行事件 + /*/// + /// 加载Dll + /// + public event LoadDllHandler? DllLoad; + + /// + /// 移除DLL + /// + public event RemoteDllHandler? OnDllRemote; + + /// + /// 项目加载完成 + /// + public event ProjectLoadedHandler? ProjectLoaded; + + /// + /// 项目准备保存 + /// + public event ProjectSavingHandler? ProjectSaving; + + /// + /// 节点连接属性改变事件 + /// + public event NodeConnectChangeHandler? NodeConnectChanged; + + /// + /// 节点创建事件 + /// + public event NodeCreateHandler? NodeCreated; + + /// + /// 移除节点事件 + /// + public event NodeRemoveHandler? NodeRemoved; + + /// + /// 节点放置事件 + /// + public event NodePlaceHandler NodePlace; + + /// + /// 节点取出事件 + /// + public event NodeTakeOutHandler NodeTakeOut; + + /// + /// 起始节点变化事件 + /// + public event StartNodeChangeHandler? StartNodeChanged; + + /// + /// 流程运行完成事件 + /// + public event FlowRunCompleteHandler? FlowRunComplete; + + /// + /// 被监视的对象改变事件 + /// + public event MonitorObjectChangeHandler? MonitorObjectChanged; + + /// + /// 节点中断状态改变事件 + /// + public event NodeInterruptStateChangeHandler? NodeInterruptStateChanged; + + /// + /// 节点触发了中断 + /// + public event ExpInterruptTriggerHandler? InterruptTriggered; + + /// + /// 容器改变 + /// + public event IOCMembersChangedHandler? IOCMembersChanged; + + /// + /// 节点需要定位 + /// + public event NodeLocatedHandler? NodeLocated; + + /// + /// 运行环境输出 + /// + public event EnvOutHandler? EnvOutput; + + /// + /// 本地环境添加了画布 + /// + public event CanvasCreateHandler CanvasCreated; + + /// + /// 本地环境移除了画布 + /// + public event CanvasRemoveHandler CanvasRemoved; +*/ + #endregion + + #region 属性 + /// + /// 运行环境的IOC容器 + /// + public ISereinIOC FlowEnvironmentIOC { get; set; } + + /// + /// 当前环境 + /// + public IFlowEnvironment CurrentEnv { get => this; } + + /// + /// 流程事件 + /// + public IFlowEnvironmentEvent Event { get; set; } + + /// + /// UI线程操作类 + /// + public UIContextOperation UIContextOperation { get; private set; } + + /// + /// 节点视图模型管理类 + /// + public NodeMVVMService NodeMVVMManagement { get; set; } + + /// + /// 节点管理服务() + /// + internal FlowModelService FlowModelService { get; set; } + + /// + /// 信息输出等级 + /// + public InfoClass InfoClass { get; set; } = InfoClass.Trivial; + + /// + /// 如果没有全局触发器,且没有循环分支,流程执行完成后自动为 Completion 。 + /// + public RunState FlowState { get; set; } = RunState.NoStart; + /// + /// 如果全局触发器还在运行,则为 Running 。 + /// + public RunState FlipFlopState { get; set; } = RunState.NoStart; + + /// + /// 环境名称 + /// + public string EnvName { get; set; } = SpaceName; + + + /// + /// 本地加载的项目文件路径 + /// + public string ProjectFileLocation { get; set; } = string.Empty; + + /// + /// 是否全局中断 + /// + public bool IsGlobalInterrupt { get; set; } + + ///// + ///// 流程中断器 + ///// + //public ChannelFlowInterrupt ChannelFlowInterrupt { get; set; } + + /// + /// 单例模式IOC容器,内部维护了一个实例字典,默认使用类型的FullName作为Key,如果以“接口-实现类”的方式注册,那么将使用接口类型的FullName作为Key。 + /// 当某个类型注册绑定成功后,将不会因为其它地方尝试注册相同类型的行为导致类型被重新创建。 + /// + public ISereinIOC IOC + { + get + { + if (FlowTaskIOC is null) + { + FlowTaskIOC = new SereinIOC(); + } + return FlowTaskIOC; + } + } + + #endregion + + #region 私有变量 + + /// + /// IOC容器 + /// + private ISereinIOC FlowTaskIOC { get; set; } + + /// + /// 通过程序集名称管理动态加载的程序集,用于节点创建提供方法描述,流程运行时提供Emit委托 + /// + private readonly FlowLibraryManagement FlowLibraryManagement; + + /// + /// IOC对象容器管理 + /// + private readonly SereinIOC sereinIOC; + + /// + /// 本地运行环境缓存的持久化实例 + /// + private Dictionary PersistennceInstance { get; } = new Dictionary(); + + /// + /// 环境加载的节点集合 + /// Node Guid - Node Model + /// + private Dictionary NodeModels { get; } = []; + + /// + /// 运行环境加载的画布集合 + /// + private Dictionary FlowCanvass { get; } = []; + + /// + /// 存放触发器节点(运行时全部调用) + /// + private List FlipflopNodes { get; } = []; + + + /// + /// 流程任务管理 + /// + private FlowWorkManagement? flowTaskManagement; + + #endregion + + #region 环境对外接口 + + /// + /// 输出信息 + /// + /// 日志内容 + /// 日志类别 + /// 日志级别 + public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial) + { + if (@class >= this.InfoClass) + { + Event.OnEnvOutput(type, message); + } + //Console.WriteLine($"{DateTime.UtcNow} [{type}] : {message}{Environment.NewLine}"); + + } + + /// + /// 异步运行 + /// + /// + public async Task StartFlowAsync(string[] canvasGuids) + { + #region 校验参数 + HashSet guids = new HashSet(); + bool isBreak = false; + foreach (var canvasGuid in canvasGuids) + { + if (guids.Contains(canvasGuid)) + { + SereinEnv.WriteLine(InfoType.WARN, $"画布重复,停止运行。{canvasGuid}"); + isBreak = true; + } + if (!FlowCanvass.ContainsKey(canvasGuid)) + { + SereinEnv.WriteLine(InfoType.WARN, $"画布不存在,停止运行。{canvasGuid}"); + isBreak = true; + } + var count = NodeModels.Values.Count(n => n.CanvasDetails.Guid.Equals(canvasGuid)); + if (count == 0) + { + SereinEnv.WriteLine(InfoType.WARN, $"画布没有节点,停止运行。{canvasGuid}"); + isBreak = true; + } + else + { + guids.Add(canvasGuid); + } + } + if (isBreak) + { + guids.Clear(); + return false; + } + #endregion + + + #region 初始化每个画布的数据,转换为流程任务 + Dictionary flowTasks = []; + foreach (var guid in guids) + { + if (!TryGetCanvasModel(guid, out var canvasModel)) + { + SereinEnv.WriteLine(InfoType.WARN, $"画布不存在,停止运行。{guid}"); + return false; + } + var ft = new FlowTask(); + ft.GetNodes = () => NodeModels.Values.Where(node => node.CanvasDetails.Guid.Equals(guid)).ToList(); + if (canvasModel.StartNode?.Guid is null) + { + SereinEnv.WriteLine(InfoType.WARN, $"画布不存在起始节点,将停止运行。{guid}"); + return false; + } + ft.GetStartNode = () => canvasModel.StartNode; + flowTasks.Add(guid, ft); + } + #endregion + + + + IOC.Reset(); + IOC.Register(); // 注册脚本接口 + + var flowTaskOptions = new FlowWorkOptions + { + Environment = this, // 流程 + Flows = flowTasks, + FlowContextPool = new ObjectPool(() => new DynamicContext(this)), // 上下文对象池 + AutoRegisterTypes = this.FlowLibraryManagement.GetaAutoRegisterType(), // 需要自动实例化的类型 + InitMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Init), + LoadMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Loading), + ExitMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Exit), + }; + + + + flowTaskManagement = new FlowWorkManagement(flowTaskOptions); + var cts = new CancellationTokenSource(); + try + { + var t = await flowTaskManagement.RunAsync(cts.Token); + } + catch (Exception ex) + { + SereinEnv.WriteLine(ex); + } + finally + { + + SereinEnv.WriteLine(InfoType.INFO, $"流程运行完毕{Environment.NewLine}"); ; + } + flowTaskOptions = null; + return true; + + + } + + + /// + /// 从选定节点开始运行 + /// + /// + /// + public async Task StartFlowFromSelectNodeAsync(string startNodeGuid) + { + + if (!TryGetNodeModel(startNodeGuid, out var nodeModel) || nodeModel is SingleFlipflopNode) + { + return false; + } + var context = new DynamicContext(this); + var cts = new CancellationTokenSource(); + await nodeModel.StartFlowAsync(context, cts.Token); + cts.Cancel(); + cts.Dispose(); + return true; + + /* if (flowTaskManagement is null) + { + SereinEnv.WriteLine(InfoType.ERROR, "没有启动流程,无法运行单个节点"); + return false; + } + if (true || FlowState == RunState.Running || FlipFlopState == RunState.Running) + { + if (!TryGetNodeModel(startNodeGuid, out var nodeModel) || nodeModel is SingleFlipflopNode) + { + return false; + } + var context = new DynamicContext(this); + var cts = new CancellationTokenSource(); + await nodeModel.StartFlowAsync(context, cts.Token); + cts.Cancel(); + cts.Dispose(); + return true; + } + else + { + return false; + }*/ + } + + /*/// + /// 单独运行一个节点 + /// + /// + /// + /// + public async Task InvokeNodeAsync(string nodeGuid, IDynamicContext? context = null) + { + 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; + }*/ + + /// + /// 结束流程 + /// + public Task ExitFlowAsync() + { + flowTaskManagement?.Exit(); + UIContextOperation?.Invoke(() => Event.OnFlowRunComplete(new FlowEventArgs())); + IOC.Reset(); + flowTaskManagement = null; + GC.Collect(); + return Task.FromResult(true); + } + + /// + /// 激活全局触发器 + /// + /// + 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);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中 + + } + } + } + + /// + /// 关闭全局触发器 + /// + /// + 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); + } + } + + /// + /// 获取当前环境信息(远程连接) + /// + /// + public async Task GetEnvInfoAsync() + { + // 获取所有的程序集对应的方法信息(程序集相关的数据) + var libraryMdss = this.FlowLibraryManagement.GetAllLibraryMds().ToArray(); + // 获取当前项目的信息(节点相关的数据) + var project = await GetProjectInfoAsync(); // 远程连接获取远程环境项目信息 + SereinEnv.WriteLine(InfoType.INFO, "已将当前环境信息发送到远程客户端"); + return new FlowEnvInfo + { + Project = project, // 项目信息 + LibraryMds = libraryMdss, // 环境方法 + }; + } + + + /// + /// 保存项目 + /// + public void SaveProject() + { + var project = GetProjectInfoAsync().GetAwaiter().GetResult(); + Event.OnProjectSaving(new ProjectSavingEventArgs(project)); + } + + /// + /// 加载项目文件 + /// + /// 环境信息 + /// + public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath) + { + this.ProjectFileLocation = filePath; + var projectData = flowEnvInfo.Project; + // 加载项目配置文件 + var dllPaths = projectData.Librarys.Select(it => it.FilePath).ToList(); + List methodDetailss = []; + + // 遍历依赖项中的特性注解,生成方法详情 + foreach (var dllPath in dllPaths) + { + string cleanedRelativePath = dllPath.TrimStart('.', '\\'); + var tmpPath = Path.Combine(filePath, cleanedRelativePath); + var dllFilePath = Path.GetFullPath(tmpPath); + LoadLibrary(dllFilePath); // 加载项目文件时加载对应的程序集 + } + + + + _ = Task.Run(async () => + { + // 加载画布 + foreach (var canvasInfo in projectData.Canvass) + { + LoadCanvas(canvasInfo); + } + await LoadNodeInfosAsync(projectData.Nodes.ToList()); // 加载节点信息 + + // 加载画布 + foreach (var canvasInfo in projectData.Canvass) + { + await SetStartNodeAsync(canvasInfo.Guid, canvasInfo.StartNode); // 设置起始节点 + } + //await SetStartNodeAsync("", projectData.StartNode); // 设置起始节点 + }); + + } + + /// + /// 加载远程环境 + /// + /// 远程环境地址 + /// 远程环境端口 + /// 密码 + public async Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token) + { + if (IsControlRemoteEnv) + { + await Console.Out.WriteLineAsync($"当前已经连接远程环境"); + return (false, null); + } + // 没有连接远程环境,可以重新连接 + + var controlConfiguration = new RemoteMsgUtil.ControlConfiguration + { + Addres = addres, + Port = port, + Token = token, + ThemeJsonKey = LocalFlowEnvironment.ThemeKey, + MsgIdJsonKey = LocalFlowEnvironment.MsgIdKey, + DataJsonKey = LocalFlowEnvironment.DataKey, + }; + var remoteMsgUtil = new RemoteMsgUtil(controlConfiguration); + var result = await remoteMsgUtil.ConnectAsync(); + if (!result) + { + await Console.Out.WriteLineAsync("连接失败,请检查地址与端口是否正确"); + return (false, null); + } + await Console.Out.WriteLineAsync("连接成功,开始验证Token"); + IsControlRemoteEnv = true; + return (true, remoteMsgUtil); + } + + /// + /// 退出远程环境 + /// + public void ExitRemoteEnv() + { + IsControlRemoteEnv = false; + } + + /// + /// 序列化当前项目的依赖信息、节点信息 + /// + /// + public async Task GetProjectInfoAsync() + { + var projectData = new SereinProjectData() + { + Librarys = this.FlowLibraryManagement.GetAllLibraryInfo().ToArray(), + Nodes = NodeModels.Values.Select(node => node.ToInfo()).Where(info => info is not null).ToArray(), + Canvass = FlowCanvass.Values.Select(canvas => canvas.ToInfo()).ToArray(), + //StartNode = NodeModels.Values.FirstOrDefault(it => it.IsStart)?.Guid, + }; + + return projectData; + } + + + /// + /// 从文件路径中加载DLL + /// + /// + /// + public void LoadLibrary(string dllPath) + { + + try + { + #region 检查是否已经加载本地依赖 + var thisAssembly = typeof(IFlowEnvironment).Assembly; + var thisAssemblyName = thisAssembly.GetName().Name; + if (!string.IsNullOrEmpty(thisAssemblyName) && FlowLibraryManagement.GetLibraryMdsOfAssmbly(thisAssemblyName).Count == 0) + { + var tmp = FlowLibraryManagement.LoadLibraryOfPath(thisAssembly.Location); + UIContextOperation?.Invoke(() => Event.OnDllLoad(new LoadDllEventArgs(tmp.Item1, tmp.Item2))); // 通知UI创建dll面板显示 + } + + #endregion + + (var libraryInfo, var mdInfos) = FlowLibraryManagement.LoadLibraryOfPath(dllPath); + if (mdInfos.Count > 0) + { + UIContextOperation?.Invoke(() => Event.OnDllLoad(new LoadDllEventArgs(libraryInfo, mdInfos))); // 通知UI创建dll面板显示 + } + } + catch (Exception ex) + { + SereinEnv.WriteLine(InfoType.ERROR, $"无法加载DLL文件:{ex}"); + } + } + + /// + /// 加载本地程序集 + /// + /// + public void LoadLibrary(FlowLibrary flowLibrary) + { + try + { + (var libraryInfo, var mdInfos) = FlowLibraryManagement.LoadLibraryOfPath(flowLibrary); + if (mdInfos.Count > 0) + { + UIContextOperation?.Invoke(() => Event.OnDllLoad(new LoadDllEventArgs(libraryInfo, mdInfos))); // 通知UI创建dll面板显示 + } + } + catch (Exception ex) + { + SereinEnv.WriteLine(InfoType.ERROR, $"无法加载DLL文件:{ex}"); + } + + } + + + /// + /// 移除DLL + /// + /// + /// + public bool TryUnloadLibrary(string assemblyName) + { + // 获取与此程序集相关的节点 + var groupedNodes = NodeModels.Values.Where(node => !string.IsNullOrWhiteSpace(node.MethodDetails.AssemblyName) && node.MethodDetails.AssemblyName.Equals(assemblyName)).ToArray(); + if (groupedNodes.Length == 0) + { + var isPass = FlowLibraryManagement.UnloadLibrary(assemblyName); + return isPass; + } + else + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(); + for (int i = 0; i < groupedNodes.Length; i++) + { + IFlowNode? node = groupedNodes[i]; + sb.AppendLine($"{i} => {node.Guid}"); + } + SereinEnv.WriteLine(InfoType.ERROR, $"无法卸载[{assemblyName}]程序集,因为这些节点依赖于此程序集:{sb.ToString()}"); + + return false; + } + + //var mds = FlowLibraryManagement.GetLibraryMdsOfAssmbly(assemblyName); + //if(mds.Count > 0) + //{ + + + //} + //else + //{ + // return true; + //} + + //var library = LibraryInfos.Values.FirstOrDefault(nl => assemblyName.Equals(nl.AssemblyName)); + //if (library is null) + //{ + // return false; + //} + //var groupedNodes = NodeModels.Values + // .Where(node => node.MethodDetails is not null) + // .ToArray() + // .GroupBy(node => node.MethodDetails?.MethodName) + // .ToDictionary( + // key => key.Key, + // group => group.Count()); + + + //if (NodeModels.Count == 0) + //{ + // return true; // 当前无节点,可以直接删除 + //} + + //if (MethodDetailsOfLibraryInfos.TryGetValue(library, out var mds)) // 存在方法 + //{ + // foreach (var md in mds) + // { + // if (groupedNodes.TryGetValue(md.MethodName, out int count)) + // { + // if (count > 0) + // { + // return false; // 创建过相关的节点,无法移除 + // } + // } + // } + // // 开始移除相关信息 + // foreach (var md in mds) + // { + // MethodDetailss.TryRemove(md.MethodName, out _); + // } + // MethodDetailsOfLibraryInfos.TryRemove(library, out _); + // return true; + //} + //else + //{ + // return true; + //} + } + + private int _addCanvasCount = 0; + + /// + /// 增加画布 + /// + /// 画布名称 + /// 宽度 + /// 高度 + /// + public async Task CreateCanvasAsync(string canvasName, int width, int height) + { + var info = new FlowCanvasDetailsInfo() + { + Guid = Guid.NewGuid().ToString(), + Height = height, + Width = width, + ViewX = 0, + ViewY = 0, + ScaleY = 1, + ScaleX = 1, + Name = !string.IsNullOrWhiteSpace(canvasName) ? canvasName : $"流程图{_addCanvasCount++}", + }; + var model = LoadCanvas(info); + return info; + } + + private FlowCanvasDetails LoadCanvas(FlowCanvasDetailsInfo info) + { + var model = new FlowCanvasDetails(this); + model.LoadInfo(info); + FlowCanvass.Add(model.Guid, model); + UIContextOperation?.Invoke(() => + { + Event.OnCanvasCreated(new CanvasCreateEventArgs(model)); + }); + return model; + } + + /// + /// 删除画布 + /// + /// 画布Guid + /// + public async Task RemoveCanvasAsync(string canvasGuid) + { + + if (!FlowCanvass.TryGetValue(canvasGuid, out var model)) + { + return false; + } + var count = NodeModels.Values.Count(node => node.CanvasDetails.Guid.Equals(canvasGuid)); + if (count > 0) + { + SereinEnv.WriteLine(InfoType.WARN, "无法删除具有节点的画布"); + return false; + } + if (FlowCanvass.Remove(canvasGuid)) + { + UIContextOperation?.Invoke(() => + { + Event.OnCanvasRemoved(new CanvasRemoveEventArgs(canvasGuid)); + }); + return true; + } + + return false; + } + + + /// + /// 从节点信息集合批量加载节点控件 + /// + /// 节点信息 + /// + /// + public async Task LoadNodeInfosAsync(List nodeInfos) + { + #region 从NodeInfo创建NodeModel + // 流程接口节点最后才创建 + + List 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 needPlaceNodeInfos = []; + foreach (NodeInfo? nodeInfo in nodeInfos) + { + if (!string.IsNullOrEmpty(nodeInfo.ParentNodeGuid) && + NodeModels.TryGetValue(nodeInfo.ParentNodeGuid, out var parentNode)) + { + needPlaceNodeInfos.Add(nodeInfo); // 需要重新放置的节点 + } + } + + foreach (NodeInfo nodeInfo in needPlaceNodeInfos) + { + if (NodeModels.TryGetValue(nodeInfo.Guid, out var nodeModel) && + NodeModels.TryGetValue(nodeInfo.ParentNodeGuid, out var containerNode) + && containerNode is INodeContainer nodeContainer) + { + var result = nodeContainer.PlaceNode(nodeModel); + if (result) + { + UIContextOperation?.Invoke(() => Event.OnNodePlace( + new NodePlaceEventArgs(nodeInfo.CanvasGuid, nodeModel.Guid, containerNode.Guid))); + } + + + } + } + #endregion + + #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; + } + ; + 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 确定节点之间的参数调用关系 + foreach (var toNode in NodeModels.Values) + { + 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) + && NodeModels.TryGetValue(pd.ArgDataSourceNodeGuid, out var fromNode)) + { + + await ConnectArgSourceOfNodeAsync(canvasGuid, fromNode, toNode, pd.ArgDataSourceType, pd.Index); + } + } + } + #endregion + + + UIContextOperation?.Invoke(() => + { + Event.OnProjectLoaded(new ProjectLoadedEventArgs()); + }); + + return; + } + + /// + /// 流程正在运行时创建节点 + /// + /// 所属画布 + /// 所属类型 + /// 所处位置 + /// 如果是表达式节点条件节点,该项为null + public Task CreateNodeAsync(string canvasGuid, + NodeControlType nodeControlType, + PositionOfUI position, + MethodDetailsInfo? methodDetailsInfo = null) + { + if (!TryGetCanvasModel(canvasGuid, out var canvasModel)) + { + return Task.FromResult(null); + } + IFlowNode? nodeModel; + if (methodDetailsInfo is null + || string.IsNullOrEmpty(methodDetailsInfo.AssemblyName) + || string.IsNullOrEmpty(methodDetailsInfo.MethodName)) + { + nodeModel = FlowNodeExtension.CreateNode(this, nodeControlType); // 加载基础节点 + } + else + { + if (FlowLibraryManagement.TryGetMethodDetails(methodDetailsInfo.AssemblyName, // 创建节点 + methodDetailsInfo.MethodName, + out var methodDetails)) + { + nodeModel = FlowNodeExtension.CreateNode(this, nodeControlType, methodDetails); // 一般的加载节点方法 + } + else + { + return Task.FromResult(null); + } + } + nodeModel.CanvasDetails = canvasModel; + canvasModel.Nodes.Add(nodeModel); // 节点与画布互相绑定 + TryAddNode(nodeModel); + nodeModel.Position = position; // 设置位置 + + // 通知UI更改 + UIContextOperation?.Invoke(() => Event.OnNodeCreated(new NodeCreateEventArgs(canvasGuid, nodeModel, position))); + var nodeInfo = nodeModel.ToInfo(); + return Task.FromResult(nodeInfo); + } + + + /// + /// 将节点放置在容器中 + /// + /// + public Task PlaceNodeToContainerAsync(string canvasGuid, + string nodeGuid, string containerNodeGuid) + { + if (!FlowCanvass.ContainsKey(canvasGuid)) + { + return Task.FromResult(false); + } + // 获取目标节点与容器节点 + if (!TryGetNodeModel(nodeGuid, out var nodeModel)) + { + return Task.FromResult(false); + } + if (nodeModel.ContainerNode is INodeContainer tmpContainer) + { + SereinEnv.WriteLine(InfoType.WARN, $"节点放置失败,节点[{nodeGuid}]已经放置于容器节点[{((IFlowNode)tmpContainer).Guid}]"); + return Task.FromResult(false); + } + + if (!TryGetNodeModel(containerNodeGuid, out var containerNode)) + { + return Task.FromResult(false); + } + if (containerNode is not INodeContainer nodeContainer) return Task.FromResult(false); + + var result = nodeContainer.PlaceNode(nodeModel); // 放置在容器节点 + if (result) + { + UIContextOperation?.Invoke(() => + { + Event.OnNodePlace(new NodePlaceEventArgs(canvasGuid, nodeGuid, containerNodeGuid)); // 通知UI更改节点放置位置 + }); + } + return Task.FromResult(result); + + } + + /// + /// 将节点从容器节点中脱离 + /// + /// + public Task TakeOutNodeToContainerAsync(string canvasGuid, + string nodeGuid) + { + if (!FlowCanvass.ContainsKey(canvasGuid)) + { + return Task.FromResult(false); + } + // 获取目标节点与容器节点 + if (!TryGetNodeModel(nodeGuid, out var nodeModel)) + { + return Task.FromResult(false); + } + if (nodeModel.ContainerNode is not INodeContainer nodeContainer) + { + return Task.FromResult(false); + } + var result = nodeContainer.TakeOutNode(nodeModel); // 从容器节点取出 + if (result) + { + UIContextOperation?.Invoke(() => + { + Event.OnNodeTakeOut(new NodeTakeOutEventArgs(canvasGuid, nodeGuid)); // 重新放置在画布上 + }); + } + return Task.FromResult(result); + + + } + + + + /// + /// 移除节点 + /// + /// + /// + public async Task RemoveNodeAsync(string canvasGuid, string nodeGuid) + { + if (!TryGetCanvasModel(canvasGuid, out var canvasModel)) + { + return false; + } + if (!TryGetNodeModel(nodeGuid, out var remoteNode)) + { + return false; + } + + if (remoteNode is SingleFlipflopNode flipflopNode) + { + flowTaskManagement?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被移除的是全局触发器,尝试从启动器移除 + } + + //remoteNode.Remove(); // 调用节点的移除方法 + + // 遍历所有前置节点,从那些前置节点中的后继节点集合移除该节点 + foreach (var pnc in remoteNode.PreviousNodes) + { + var pCType = pnc.Key; // 连接类型 + for (int i = 0; i < pnc.Value.Count; i++) + { + IFlowNode? pNode = pnc.Value[i]; + pNode.SuccessorNodes[pCType].Remove(remoteNode); + + UIContextOperation?.Invoke(() => Event.OnNodeConnectChanged(new NodeConnectChangeEventArgs( + canvasGuid, + pNode.Guid, + remoteNode.Guid, + JunctionOfConnectionType.Invoke, + pCType, // 对应的连接关系 + NodeConnectChangeEventArgs.ConnectChangeType.Remove))); // 通知UI + + } + } + + + if (remoteNode.ControlType == NodeControlType.FlowCall) + { + + } + else + { + // 遍历所有后继节点,从那些后继节点中的前置节点集合移除该节点 + foreach (var snc in remoteNode.SuccessorNodes) + { + var connectionType = snc.Key; // 连接类型 + for (int i = 0; i < snc.Value.Count; i++) + { + IFlowNode? toNode = snc.Value[i]; + + await RemoteConnectAsync(canvasGuid, remoteNode, toNode, connectionType); + + } + } + + } + + + + // 从集合中移除节点,解除与画布的绑定关系 + NodeModels.Remove(nodeGuid); + UIContextOperation?.Invoke(() => canvasModel.Nodes.Remove(remoteNode)); + + UIContextOperation?.Invoke(() => Event.OnNodeRemoved(new NodeRemoveEventArgs(canvasGuid, nodeGuid))); + return true; + } + + /// + /// 连接节点,创建方法调用关系 + /// + /// 起始节点 + /// 目标节点 + /// 起始节点控制点 + /// 目标节点控制点 + /// 连接关系 + public Task ConnectInvokeNodeAsync(string canvasGuid, + string fromNodeGuid, + string toNodeGuid, + JunctionType fromNodeJunctionType, + JunctionType toNodeJunctionType, + ConnectionInvokeType invokeType) + { + + // 获取起始节点与目标节点 + if (!FlowCanvass.ContainsKey(canvasGuid) || !TryGetNodeModel(fromNodeGuid, out var fromNode) || !TryGetNodeModel(toNodeGuid, out var toNode)) + { + return Task.FromResult(false); + } + + if (fromNode is null || toNode is null) return Task.FromResult(false); + (var type, var state) = CheckConnect(fromNode, toNode, fromNodeJunctionType, toNodeJunctionType); + if (!state) + { + SereinEnv.WriteLine(InfoType.WARN, "出现非预期的连接行为"); + return Task.FromResult(false); // 出现不符预期的连接行为,忽略此次连接行为 + } + + if (type == JunctionOfConnectionType.Invoke) + { + if (fromNodeJunctionType == JunctionType.Execute) + { + // 如果 起始控制点 是“方法调用”,需要反转 from to 节点 + (fromNode, toNode) = (toNode, fromNode); + } + // 从起始节点“下一个方法”控制点,连接到目标节点“方法调用”控制点 + state = ConnectInvokeOfNode(canvasGuid, fromNode, toNode, invokeType); // 本地环境进行连接 + } + return Task.FromResult(state); + + } + + + /// + /// 设置两个节点某个类型的方法调用关系为优先调用 + /// + /// 起始节点 + /// 目标节点 + /// 连接关系 + /// 是否成功调用 + public Task SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) + { + // 获取起始节点与目标节点 + if (!TryGetNodeModel(fromNodeGuid, out var fromNode) || !TryGetNodeModel(toNodeGuid, out var toNode)) + { + return Task.FromResult(false); + } + if (fromNode is null || toNode is null) return Task.FromResult(false); + if (fromNode.SuccessorNodes.TryGetValue(connectionType, out var nodes)) + { + var idx = nodes.IndexOf(toNode); + if (idx > -1) + { + nodes.RemoveAt(idx); + nodes.Insert(0, toNode); + return Task.FromResult(true); + } + } + return Task.FromResult(false); + } + + /// + /// 移除连接节点之间方法调用的关系 + /// + /// 起始节点Guid + /// 目标节点Guid + /// 连接关系 + /// + public async Task RemoveConnectInvokeAsync(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) + { + // 获取起始节点与目标节点 + if (!FlowCanvass.ContainsKey(canvasGuid) || !TryGetNodeModel(fromNodeGuid, out var fromNode) || !TryGetNodeModel(toNodeGuid, out var toNode)) + { + return false; + } + if (fromNode is null || toNode is null) return false; + + var result = await RemoteConnectAsync(canvasGuid, fromNode, toNode, connectionType); + return result; + } + + /// + /// 创建节点之间的参数来源关系 + /// + /// 起始节点 + /// 目标节点 + /// 起始节点控制点(result控制点) + /// 目标节点控制点(argData控制点) + /// 目标节点的第几个参数 + /// 调用目标节点对应方法时,对应参数来源类型 + /// + public async Task ConnectArgSourceNodeAsync(string canvasGuid, + string fromNodeGuid, + string toNodeGuid, + JunctionType fromNodeJunctionType, + JunctionType toNodeJunctionType, + ConnectionArgSourceType connectionArgSourceType, + int argIndex) + { + + // 获取起始节点与目标节点 + if (!FlowCanvass.ContainsKey(canvasGuid) || !TryGetNodeModel(fromNodeGuid, out var fromNode) || !TryGetNodeModel(toNodeGuid, out var toNode)) + { + return false; + } + if (fromNode is null || toNode is null) return false; + (var type, var state) = CheckConnect(fromNode, toNode, fromNodeJunctionType, toNodeJunctionType); + if (!state) + { + SereinEnv.WriteLine(InfoType.WARN, "出现非预期的连接行为"); + return false; // 出现不符预期的连接行为,忽略此次连接行为 + } + + if (type == JunctionOfConnectionType.Arg) + { + // 从起始节点“返回值”控制点,连接到目标节点“方法入参”控制点 + if (fromNodeJunctionType == JunctionType.ArgData) + { + // 如果 起始控制点 是“方法入参”,需要反转 from to 节点 + (fromNode, toNode) = (toNode, fromNode); + } + + // 确定方法入参关系 + state = await ConnectArgSourceOfNodeAsync(canvasGuid, fromNode, toNode, connectionArgSourceType, argIndex); // 本地环境进行连接 + } + return state; + + } + + + /// + /// 移除连接节点之间参数传递的关系 + /// + /// 起始节点Guid + /// 目标节点Guid + /// 连接到第几个参数 + public async Task RemoveConnectArgSourceAsync(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex) + { + // 获取起始节点与目标节点 + if (!FlowCanvass.ContainsKey(canvasGuid) || !TryGetNodeModel(fromNodeGuid, out var fromNode) || !TryGetNodeModel(toNodeGuid, out var toNode)) + { + return false; + } + if (fromNode is null || toNode is null) return false; + var result = await RemoteConnectAsync(canvasGuid, fromNode, toNode, argIndex); + return result; + } + + + /// + /// 获取方法描述 + /// + + public bool TryGetMethodDetailsInfo(string assemblyName, string methodName, out MethodDetailsInfo? mdInfo) + { + var isPass = FlowLibraryManagement.TryGetMethodDetails(assemblyName, methodName, out var md); + if (!isPass || md is null) + { + mdInfo = null; + return false; + } + else + { + mdInfo = md?.ToInfo(); + return true; + } + } + + /// + /// 通过方法名称获取对应的Emit委托 + /// 方法无入参时需要传入空数组,void方法自动返回null + /// 普通方法:Func<object,object[],object> + /// 异步方法:Func<object,object[],Task> + /// 异步有返回值方法:Func<object,object[],Task<object>> + /// + /// + /// + /// + public bool TryGetDelegateDetails(string assemblyName, string methodName, out DelegateDetails? delegateDetails) + { + return FlowLibraryManagement.TryGetDelegateDetails(assemblyName, methodName, out delegateDetails); + } + + /// + /// 设置在UI线程操作的线程上下文 + /// + /// + public void SetUIContextOperation(UIContextOperation uiContextOperation) + { + if (uiContextOperation is not null) + { + this.UIContextOperation = uiContextOperation; + } + } + + /// + public void UseExternalIOC(ISereinIOC ioc) + { + this.FlowTaskIOC = ioc; // 设置IOC容器 + } + + + /// + /// 设置起点控件 + /// + /// 画布 + /// 节点Guid + public Task SetStartNodeAsync(string canvasGuid, string newNodeGuid) + { + if (!TryGetCanvasModel(canvasGuid, out var canvasModel) || !TryGetNodeModel(newNodeGuid, out var newStartNodeModel)) + { + return Task.FromResult(string.Empty); + } + SetStartNode(canvasModel, newStartNodeModel); + return Task.FromResult(canvasModel.StartNode.Guid ?? string.Empty); + } + + /// + /// 启动器调用,运行到某个节点时触发了监视对象的更新(对象预览视图将会自动更新) + /// + /// + /// + /// + public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType) + { + Event.OnMonitorObjectChanged(new MonitorObjectEventArgs(nodeGuid, monitorData, sourceType)); + } + + /// + /// 启动器调用,节点触发了中断。 + /// + /// 节点 + /// 表达式 + /// 类型,0用户主动的中断,1表达式中断 + public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type) + { + Event.OnInterruptTriggered(new InterruptTriggerEventArgs(nodeGuid, expression, type)); + } + + + ///// + ///// 环境执行中断 + ///// + ///// + //public async Task InterruptNode() + //{ + // IsGlobalInterrupt = true; + // var result = await ChannelFlowInterrupt.GetOrCreateChannelAsync(EnvName); + // return result; + //} + + /// + /// 记录节点更改数据,防止重复更改 + /// + public HashSet<(string, string, object)> NodeValueChangeLogger = new HashSet<(string, string, object)>(); + + /// + /// 数据更改通知(来自远程) + /// + /// 发生在哪个节点 + /// 属性路径 + /// 变化后的属性值 + /// + public Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value) + { + // "NodeModel.Path" + if (TryGetNodeModel(nodeGuid, out var nodeModel)) + { + SerinExpressionEvaluator.Evaluate($"@Set .{path} = {value}", nodeModel, out _); // 更改对应的数据 + } + + + return Task.CompletedTask; + //if (NodeValueChangeLogger.Remove((nodeGuid, path, value))) + //{ + // // 说明存在过重复的修改 + // return; + //} + //NodeValueChangeLogger.Add((nodeGuid, path, value)); + + //lock (NodeValueChangeLogger) + //{ + + // Interlocked.Add(ref i, 1); + // Console.WriteLine(i); + // var getExp = $"@Get .{path}"; + // var setExp = $"@Set .{path} = {value}"; // 生成 set 表达式 + // var oldValue = SerinExpressionEvaluator.Evaluate(getExp, nodeModel, out _); + // if(oldValue != value) + // { + // Console.WriteLine($"旧值:{getExp},result : {oldValue}"); + // SerinExpressionEvaluator.Evaluate(setExp, nodeModel, out _); // 更改对应的数据 + // Console.WriteLine($"新值:{getExp},result : {SerinExpressionEvaluator.Evaluate(getExp, nodeModel, out _)}"); + // } + + //} + + + + } + + + /// + /// 改变可选参数的数目 + /// + /// 对应的节点Guid + /// true,增加参数;false,减少参数 + /// 以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数) + /// + public Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) + { + if (!TryGetNodeModel(nodeGuid, out var nodeModel)) + { + return Task.FromResult(false); + } + if (nodeModel is null) return Task.FromResult(false); + bool isPass; + if (isAdd) + { + isPass = nodeModel.MethodDetails.AddParamsArg(paramIndex); + } + else + { + isPass = nodeModel.MethodDetails.RemoveParamsArg(paramIndex); + } + return Task.FromResult(isPass); + } + + + /// + /// 从Guid获取画布 + /// + /// 节点Guid + /// 节点Model + /// 无法获取节点、Guid/节点为null时报错 + public bool TryGetCanvasModel(string nodeGuid, out FlowCanvasDetails canvasDetails) + { + if (string.IsNullOrEmpty(nodeGuid)) + { + canvasDetails = null; + return false; + } + return FlowCanvass.TryGetValue(nodeGuid, out canvasDetails) && canvasDetails is not null; + + } + + /// + /// 从Guid获取节点 + /// + /// 节点Guid + /// 节点Model + /// 无法获取节点、Guid/节点为null时报错 + public bool TryGetNodeModel(string nodeGuid, out IFlowNode nodeModel) + { + if (string.IsNullOrEmpty(nodeGuid)) + { + nodeModel = null; + return false; + } + return NodeModels.TryGetValue(nodeGuid, out nodeModel) && nodeModel is not null; + + } + + #endregion + + #region 流程依赖类库的接口 + + + /// + /// 运行时加载 + /// + /// 文件名 + /// + public bool LoadNativeLibraryOfRuning(string file) + { + + return NativeDllHelper.LoadDll(file); + } + + /// + /// 运行时加载指定目录下的类库 + /// + /// 目录 + /// 是否递归加载 + public void LoadAllNativeLibraryOfRuning(string path, bool isRecurrence = true) + { + NativeDllHelper.LoadAllDll(path); + } + + #endregion + + #region 私有方法 + + #region 暂时注释 + /* + /// + /// 加载指定路径的DLL文件 + /// + /// + private void LoadDllNodeInfo(string dllPath) + { + + var fileName = Path.GetFileName(dllPath); + AssemblyLoadContext flowAlc = new AssemblyLoadContext(fileName, true); + flowAlc.LoadFromAssemblyPath(dllPath); // 加载指定路径的程序集 + + foreach(var assemblt in flowAlc.Assemblies) + { + (var registerTypes, var mdlist) = LoadAssembly(assemblt); + if (mdlist.Count > 0) + { + var nodeLibraryInfo = new NodeLibraryInfo + { + //Assembly = assembly, + AssemblyName = assemblt.FullName, + FileName = Path.GetFileName(dllPath), + FilePath = dllPath, + }; + + LibraryInfos.TryAdd(nodeLibraryInfo.AssemblyName, nodeLibraryInfo); + MethodDetailsOfLibraryInfos.TryAdd(nodeLibraryInfo, mdlist); + + foreach (var md in mdlist) + { + MethodDetailss.TryAdd(md.MethodName, md); + } + + foreach (var kv in registerTypes) + { + if (!AutoRegisterTypes.TryGetValue(kv.Key, out var types)) + { + types = new List(); + AutoRegisterTypes.Add(kv.Key, types); + } + types.AddRange(kv.Value); + } + var mdInfos = mdlist.Select(md => md.ToInfo()).ToList(); // 转换成方法信息 + + if (OperatingSystem.IsWindows()) + { + UIContextOperation?.Invoke(() => OnDllLoad?.Invoke(new LoadDllEventArgs(nodeLibraryInfo, mdInfos))); // 通知UI创建dll面板显示 + + } + } + + + } + + + + + }*/ + #endregion + + + /// + /// 从节点信息创建节点,并返回状态指示是否创建成功 + /// + /// + /// + private bool CreateNodeFromNodeInfo(NodeInfo nodeInfo) + { + if (!EnumHelper.TryConvertEnum(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(this, controlType, methodDetails); // 加载项目时创建节点 + if (nodeModel is null) + { + nodeInfo.Guid = string.Empty; + return false; + } + if (FlowCanvass.TryGetValue(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(() => + Event.OnNodeCreated(new NodeCreateEventArgs(nodeInfo.CanvasGuid, nodeModel, nodeInfo.Position))); // 添加到UI上 + return true; + } + + + /// + /// 移除连接关系 + /// + /// 起始节点Model + /// 目标节点Model + /// 连接关系 + /// + private async Task RemoteConnectAsync(string canvasGuid, IFlowNode fromNode, IFlowNode toNode, ConnectionInvokeType connectionType) + { + if (!FlowCanvass.ContainsKey(canvasGuid)) + { + return false; + } + fromNode.SuccessorNodes[connectionType].Remove(toNode); + toNode.PreviousNodes[connectionType].Remove(fromNode); + + + if (OperatingSystem.IsWindows()) + { + UIContextOperation?.Invoke(() => Event.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + canvasGuid, + fromNode.Guid, + toNode.Guid, + JunctionOfConnectionType.Invoke, + connectionType, + NodeConnectChangeEventArgs.ConnectChangeType.Remove))); + } + return true; + } + /// + /// 移除连接关系 + /// + /// 起始节点Model + /// 目标节点Model + /// 连接关系 + /// + private async Task RemoteConnectAsync(string canvasGuid, IFlowNode fromNode, IFlowNode toNode, int argIndex) + { + if (!FlowCanvass.ContainsKey(canvasGuid)) + { + return false; + } + if (string.IsNullOrEmpty(toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid)) + { + return false; + } + toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid = null; + toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值 + + if (OperatingSystem.IsWindows()) + { + UIContextOperation?.Invoke(() => Event.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + canvasGuid, + fromNode.Guid, + toNode.Guid, + argIndex, + JunctionOfConnectionType.Arg, + ConnectionArgSourceType.GetPreviousNodeData, + NodeConnectChangeEventArgs.ConnectChangeType.Remove))); + } + return true; + } + + + /// + /// 创建节点 + /// + /// + private bool TryAddNode(IFlowNode nodeModel) + { + nodeModel.Guid ??= Guid.NewGuid().ToString(); + NodeModels.TryAdd(nodeModel.Guid, nodeModel); + + + // 如果是触发器,则需要添加到专属集合中 + if (nodeModel is SingleFlipflopNode flipflopNode) + { + var guid = flipflopNode.Guid; + if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid))) + { + FlipflopNodes.Add(flipflopNode); + } + } + return true; + } + + /// + /// 检查连接 + /// + /// 发起连接的起始节点 + /// 要连接的目标节点 + /// 发起连接节点的控制点类型 + /// 被连接节点的控制点类型 + /// + public static (JunctionOfConnectionType, bool) CheckConnect(IFlowNode fromNode, + IFlowNode toNode, + JunctionType fromNodeJunctionType, + JunctionType toNodeJunctionType) + { + var type = JunctionOfConnectionType.None; + var state = false; + if (fromNodeJunctionType == JunctionType.Execute) + { + if (toNodeJunctionType == JunctionType.NextStep && !fromNode.Guid.Equals(toNode.Guid)) + { + // “方法执行”控制点拖拽到“下一节点”控制点,且不是同一个节点, 添加方法执行关系 + type = JunctionOfConnectionType.Invoke; + state = true; + } + } + else if (fromNodeJunctionType == JunctionType.NextStep && !fromNode.Guid.Equals(toNode.Guid)) + { + // “下一节点”控制点只能拖拽到“方法执行”控制点,且不能是同一个节点 + if (toNodeJunctionType == JunctionType.Execute && !fromNode.Guid.Equals(toNode.Guid)) + { + type = JunctionOfConnectionType.Invoke; + state = true; + } + } + else if (fromNodeJunctionType == JunctionType.ArgData) + { + if (toNodeJunctionType == JunctionType.ReturnData && !fromNode.Guid.Equals(toNode.Guid)) + { + // “”控制点拖拽到“方法返回值”控制点,且不是同一个节点,添加获取参数关系,生成参数时从目标节点获取flowdata + type = JunctionOfConnectionType.Arg; + state = true; + } + } + else if (fromNodeJunctionType == JunctionType.ReturnData) + { + if (toNodeJunctionType == JunctionType.ArgData && !fromNode.Guid.Equals(toNode.Guid)) + { + // “方法返回值”控制点拖拽到“方法入参”控制点,且不是同一个节点,添加获取参数关系,生成参数时从目标节点获取flowdata + type = JunctionOfConnectionType.Arg; + state = true; + } + } + // 剩下的情况都是不符预期的连接行为,忽略。 + return (type, state); + } + + /// + /// 连接节点 + /// + /// 起始节点 + /// 目标节点 + /// 连接关系 + private bool ConnectInvokeOfNode(string canvasGuid, IFlowNode fromNode, IFlowNode toNode, ConnectionInvokeType invokeType) + { + if (fromNode.ControlType == NodeControlType.FlowCall) + { + SereinEnv.WriteLine(InfoType.ERROR, $"流程接口节点不可调用下一个节点。" + + $"{Environment.NewLine}流程节点:{fromNode.Guid}"); + return false; + } + if (!FlowCanvass.ContainsKey(canvasGuid)) + { + return false; + } + if (fromNode is null || toNode is null || fromNode == toNode) + { + return false; + } + + var ToExistOnFrom = true; + var FromExistInTo = true; + ConnectionInvokeType[] ct = [ConnectionInvokeType.IsSucceed, + ConnectionInvokeType.IsFail, + ConnectionInvokeType.IsError, + ConnectionInvokeType.Upstream]; + + if (toNode is SingleFlipflopNode flipflopNode) + { + flowTaskManagement?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被连接的是全局触发器,尝试移除 + } + var isOverwriting = false; + ConnectionInvokeType overwritingCt = ConnectionInvokeType.None; + var isPass = false; + foreach (ConnectionInvokeType ctType in ct) + { + var FToTo = fromNode.SuccessorNodes[ctType].Where(it => it.Guid.Equals(toNode.Guid)).ToArray(); + var ToOnF = toNode.PreviousNodes[ctType].Where(it => it.Guid.Equals(fromNode.Guid)).ToArray(); + ToExistOnFrom = FToTo.Length > 0; + FromExistInTo = ToOnF.Length > 0; + if (ToExistOnFrom && FromExistInTo) + { + if (ctType == invokeType) + { + SereinEnv.WriteLine(InfoType.WARN, $"起始节点已与目标节点存在连接。" + + $"{Environment.NewLine}起始节点:{fromNode.Guid}" + + $"{Environment.NewLine}目标节点:{toNode.Guid}"); + return false; + } + isOverwriting = true; + overwritingCt = ctType; + } + else + { + // 检查是否可能存在异常 + if (!ToExistOnFrom && FromExistInTo) + { + SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的父节点,目标节点却是起始节点的子节点。" + + $"{Environment.NewLine}起始节点:{fromNode.Guid}" + + $"{Environment.NewLine}目标节点:{toNode.Guid}"); + isPass = false; + } + else if (ToExistOnFrom && !FromExistInTo) + { + // + SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的父节点,目标节点却是起始节点的子节点。" + + $"{Environment.NewLine}起始节点:{fromNode.Guid}" + + $"{Environment.NewLine}目标节点:{toNode.Guid}" + + $""); + isPass = false; + } + else + { + isPass = true; + } + } + } + if (isPass) + { + if (isOverwriting) // 需要替换 + { + fromNode.SuccessorNodes[overwritingCt].Remove(toNode); // 从起始节点子分支中移除 + toNode.PreviousNodes[overwritingCt].Remove(fromNode); // 从目标节点父分支中移除 + } + fromNode.SuccessorNodes[invokeType].Add(toNode); // 添加到起始节点的子分支 + toNode.PreviousNodes[invokeType].Add(fromNode); // 添加到目标节点的父分支 + if (OperatingSystem.IsWindows()) + { + + UIContextOperation?.Invoke(() => + Event.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + canvasGuid, + fromNode.Guid, // 从哪个节点开始 + toNode.Guid, // 连接到那个节点 + JunctionOfConnectionType.Invoke, + invokeType, // 连接线的样式类型 + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + ))); // 通知UI + } + // Invoke + // GetResult + return true; + } + else + { + return false; + } + + + } + + /// + /// 连接节点参数 + /// + /// + /// + /// + /// + /// + private async Task ConnectArgSourceOfNodeAsync(string canvasGuid, + IFlowNode fromNode, + IFlowNode toNode, + ConnectionArgSourceType connectionArgSourceType, + int argIndex) + { + if (!FlowCanvass.ContainsKey(canvasGuid)) + { + return false; + } + + var toNodeArgSourceGuid = toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid; + var toNodeArgSourceType = toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType; + if (fromNode.Guid == toNodeArgSourceGuid && toNodeArgSourceType == connectionArgSourceType) + { + SereinEnv.WriteLine(InfoType.INFO, $"节点之间已建立过连接关系,此次操作将不会执行" + + $"起始节点:{fromNode.Guid}" + + $"目标节点:{toNode.Guid}" + + $"参数索引:{argIndex}" + + $"参数类型:{connectionArgSourceType}"); + UIContextOperation?.Invoke(() => + Event.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + canvasGuid, + fromNode.Guid, // 从哪个节点开始 + toNode.Guid, // 连接到那个节点 + argIndex, // 连接线的样式类型 + JunctionOfConnectionType.Arg, + connectionArgSourceType, + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + ))); // 通知UI + + return true; + } + + if (!string.IsNullOrEmpty(toNodeArgSourceGuid)) + { + await RemoteConnectAsync(canvasGuid, fromNode, toNode, argIndex); + } + + toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid = fromNode.Guid; + toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType = connectionArgSourceType; + + UIContextOperation?.Invoke(() => + Event.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + canvasGuid, + fromNode.Guid, // 从哪个节点开始 + toNode.Guid, // 连接到那个节点 + argIndex, // 连接线的样式类型 + JunctionOfConnectionType.Arg, + connectionArgSourceType, + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + ))); // 通知UI + return true; + } + + + /// + /// 更改起点节点 + /// + /// 节点所在的画布 + /// 起始节点 + private void SetStartNode(FlowCanvasDetails cavnasModel, IFlowNode newStartNode) + { + var oldNodeGuid = cavnasModel.StartNode?.Guid; + /*if(TryGetNodeModel(oldNodeGuid, out var newStartNodeModel)) + { + newStartNode.IsStart = false; + }*/ + cavnasModel.StartNode = newStartNode; + //newStartNode.IsStart = true; + UIContextOperation?.Invoke(() => Event.OnStartNodeChanged(new StartNodeChangeEventArgs(cavnasModel.Guid, oldNodeGuid, cavnasModel.StartNode.Guid))); + + } + + + #endregion + + #region 视觉效果 + + /// + /// 定位节点 + /// + /// + public void NodeLocate(string nodeGuid) + { + if (OperatingSystem.IsWindows()) + { + UIContextOperation?.Invoke(() => Event.OnNodeLocated(new NodeLocatedEventArgs(nodeGuid))); + } + + } + + #endregion + + + } + + +} diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs index decee0f..a008873 100644 --- a/NodeFlow/Env/RemoteFlowEnvironment.cs +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -46,7 +46,6 @@ namespace Serein.NodeFlow.Env /// private Dictionary NodeModels { get; } = []; - public ISereinIOC IOC => throw new NotImplementedException(); /// @@ -73,7 +72,7 @@ namespace Serein.NodeFlow.Env public IFlowEnvironment CurrentEnv => this; public UIContextOperation UIContextOperation { get; } - public NodeMVVMManagement NodeMVVMManagement { get; } + public NodeMVVMService NodeMVVMManagement { get; } /// @@ -658,8 +657,8 @@ namespace Serein.NodeFlow.Env Event.OnNodeConnectChanged(new NodeConnectChangeEventArgs(canvasGuid, fromNodeGuid, toNodeGuid, - JunctionOfConnectionType.Arg, argIndex, + JunctionOfConnectionType.Arg, argSourceType, NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI } @@ -729,8 +728,8 @@ namespace Serein.NodeFlow.Env Event.OnNodeConnectChanged(new NodeConnectChangeEventArgs(canvasGuid, fromNodeGuid, toNodeGuid, - JunctionOfConnectionType.Arg, argIndex, + JunctionOfConnectionType.Arg, ConnectionArgSourceType.GetPreviousNodeData, NodeConnectChangeEventArgs.ConnectChangeType.Remove)); // 通知UI }); @@ -1283,8 +1282,8 @@ namespace Serein.NodeFlow.Env canvasGuid, fromNode.Guid, // 从哪个节点开始 toNode.Guid, // 连接到那个节点 + pd.Index, // 连接线的样式类型 JunctionOfConnectionType.Arg, - (int)pd.Index, // 连接线的样式类型 pd.ArgDataSourceType, NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 ))); // 通知UI diff --git a/NodeFlow/FlowNodeExtension.cs b/NodeFlow/FlowNodeExtension.cs index 1b63c82..ebdca1f 100644 --- a/NodeFlow/FlowNodeExtension.cs +++ b/NodeFlow/FlowNodeExtension.cs @@ -1,7 +1,7 @@ using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; -using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Node; using System.Collections.Concurrent; using System.ComponentModel; using System.Reflection; diff --git a/NodeFlow/Model/NodeModelBaseData.cs b/NodeFlow/Model/Node/NodeModelBaseData.cs similarity index 89% rename from NodeFlow/Model/NodeModelBaseData.cs rename to NodeFlow/Model/Node/NodeModelBaseData.cs index 4fbbdfe..e023e77 100644 --- a/NodeFlow/Model/NodeModelBaseData.cs +++ b/NodeFlow/Model/Node/NodeModelBaseData.cs @@ -96,11 +96,19 @@ namespace Serein.NodeFlow.Model { PreviousNodes = new Dictionary>(); SuccessorNodes = new Dictionary>(); + NeedResultNodes = new Dictionary>(); + foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) { PreviousNodes[ctType] = new List(); SuccessorNodes[ctType] = new List(); } + + foreach (ConnectionArgSourceType ctType in NodeStaticConfig.ConnectionArgSourceTypes) + { + NeedResultNodes[ctType] = new List(); + } + ChildrenNode = new List(); DebugSetting = new NodeDebugSetting(this); this.Env = environment; @@ -116,6 +124,11 @@ namespace Serein.NodeFlow.Model /// 不同分支的子节点(流程调用) /// public Dictionary> SuccessorNodes { get; set; } + + /// + /// 需要该节点返回值作为入参参数的节点集合 + /// + public Dictionary> NeedResultNodes { get;} /// /// 该节点的容器节点 diff --git a/NodeFlow/Model/NodeModelBaseFunc.cs b/NodeFlow/Model/Node/NodeModelBaseFunc.cs similarity index 99% rename from NodeFlow/Model/NodeModelBaseFunc.cs rename to NodeFlow/Model/Node/NodeModelBaseFunc.cs index a56f1db..d1ce2d8 100644 --- a/NodeFlow/Model/NodeModelBaseFunc.cs +++ b/NodeFlow/Model/Node/NodeModelBaseFunc.cs @@ -57,7 +57,7 @@ namespace Serein.NodeFlow.Model return; } - /// + /* /// /// 移除该节点 /// public virtual void Remove() @@ -101,7 +101,7 @@ namespace Serein.NodeFlow.Model this.DisplayName = null; this.Env = null; - } + }*/ /// /// 执行节点对应的方法 diff --git a/NodeFlow/Model/SingleActionNode.cs b/NodeFlow/Model/Node/SingleActionNode.cs similarity index 90% rename from NodeFlow/Model/SingleActionNode.cs rename to NodeFlow/Model/Node/SingleActionNode.cs index 496b1fb..8f807e9 100644 --- a/NodeFlow/Model/SingleActionNode.cs +++ b/NodeFlow/Model/Node/SingleActionNode.cs @@ -2,7 +2,7 @@ using Serein.Library; using System.Security.AccessControl; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Node { /// /// 单动作节点(用于动作控件) diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/Node/SingleConditionNode.cs similarity index 100% rename from NodeFlow/Model/SingleConditionNode.cs rename to NodeFlow/Model/Node/SingleConditionNode.cs diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/Node/SingleExpOpNode.cs similarity index 100% rename from NodeFlow/Model/SingleExpOpNode.cs rename to NodeFlow/Model/Node/SingleExpOpNode.cs diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/Node/SingleFlipflopNode.cs similarity index 85% rename from NodeFlow/Model/SingleFlipflopNode.cs rename to NodeFlow/Model/Node/SingleFlipflopNode.cs index f20f15c..ab479e6 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/Node/SingleFlipflopNode.cs @@ -3,7 +3,7 @@ using Serein.Library; using Serein.Library.Utils; using System; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Node { /// /// 触发器节点 @@ -27,9 +27,9 @@ namespace Serein.NodeFlow.Model #region 执行前中断 if (DebugSetting.IsInterrupt) // 执行触发前 { - string guid = this.Guid.ToString(); - await this.DebugSetting.GetInterruptTask.Invoke(); - await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已取消,开始执行后继分支"); + string guid = Guid.ToString(); + await DebugSetting.GetInterruptTask.Invoke(); + await Console.Out.WriteLineAsync($"[{MethodDetails.MethodName}]中断已取消,开始执行后继分支"); } #endregion @@ -61,7 +61,7 @@ namespace Serein.NodeFlow.Model if (dynamicFlipflopContext.Type == TriggerDescription.Overtime) { - throw new FlipflopException(base.MethodDetails.MethodName + "触发器超时触发。Guid" + base.Guid); + throw new FlipflopException(MethodDetails.MethodName + "触发器超时触发。Guid" + Guid); } object result = dynamicFlipflopContext.Value; var flowReslt = new FlowResult(this, context, result); diff --git a/NodeFlow/Model/SingleFlowCallNode.cs b/NodeFlow/Model/Node/SingleFlowCallNode.cs similarity index 98% rename from NodeFlow/Model/SingleFlowCallNode.cs rename to NodeFlow/Model/Node/SingleFlowCallNode.cs index cf0a2b5..7987162 100644 --- a/NodeFlow/Model/SingleFlowCallNode.cs +++ b/NodeFlow/Model/Node/SingleFlowCallNode.cs @@ -119,7 +119,7 @@ namespace Serein.NodeFlow.Model partial void OnIsShareParamChanged(bool value) { - if (targetNode is null) + if (targetNode is null || targetNode.MethodDetails is null) { return; } @@ -219,14 +219,13 @@ namespace Serein.NodeFlow.Model } - public override void Remove() + /*public override void Remove() { var tmp = this; targetNode = null; CacheMethodDetails = null; - } - + */ /// diff --git a/NodeFlow/Model/SingleGlobalDataNode.cs b/NodeFlow/Model/Node/SingleGlobalDataNode.cs similarity index 97% rename from NodeFlow/Model/SingleGlobalDataNode.cs rename to NodeFlow/Model/Node/SingleGlobalDataNode.cs index b74fddb..6bda89a 100644 --- a/NodeFlow/Model/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/Node/SingleGlobalDataNode.cs @@ -96,7 +96,7 @@ namespace Serein.NodeFlow.Model { foreach (var nodeModel in ChildrenNode) { - await nodeModel.Env.TakeOutNodeToContainerAsync(nodeModel.CanvasDetails.Guid, nodeModel.Guid); + nodeModel.Env.TakeOutNodeToContainer(nodeModel.CanvasDetails.Guid, nodeModel.Guid); } DataNode = null; } @@ -162,7 +162,7 @@ namespace Serein.NodeFlow.Model KeyName = nodeInfo.CustomData?.KeyName; } - /// + /* /// /// 需要移除数据节点 /// public override void Remove() @@ -172,7 +172,7 @@ namespace Serein.NodeFlow.Model } // 移除数据节点 _ = this.Env.RemoveNodeAsync(DataNode.CanvasDetails.Guid, DataNode.Guid); - } + }*/ } } diff --git a/NodeFlow/Model/SingleNetScriptNode.cs b/NodeFlow/Model/Node/SingleNetScriptNode.cs similarity index 100% rename from NodeFlow/Model/SingleNetScriptNode.cs rename to NodeFlow/Model/Node/SingleNetScriptNode.cs diff --git a/NodeFlow/Model/SingleScriptNode.cs b/NodeFlow/Model/Node/SingleScriptNode.cs similarity index 99% rename from NodeFlow/Model/SingleScriptNode.cs rename to NodeFlow/Model/Node/SingleScriptNode.cs index 9927089..0956ace 100644 --- a/NodeFlow/Model/SingleScriptNode.cs +++ b/NodeFlow/Model/Node/SingleScriptNode.cs @@ -226,7 +226,7 @@ namespace Serein.NodeFlow.Model scriptContext.OnExit(); }; - var envEvent = (IFlowEnvironmentEvent)context.Env; + var envEvent = context.Env.Event; envEvent.FlowRunComplete += onFlowStop; // 防止运行后台流程 if (token.IsCancellationRequested) return null; diff --git a/NodeFlow/Model/SingleUINode.cs b/NodeFlow/Model/Node/SingleUINode.cs similarity index 94% rename from NodeFlow/Model/SingleUINode.cs rename to NodeFlow/Model/Node/SingleUINode.cs index 42111b8..7ce9c9a 100644 --- a/NodeFlow/Model/SingleUINode.cs +++ b/NodeFlow/Model/Node/SingleUINode.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow.Model +namespace Serein.NodeFlow.Model.Node { public class SingleUINode : NodeModelBase { @@ -24,7 +24,7 @@ namespace Serein.NodeFlow.Model var result = await base.ExecutingAsync(context, token); if (result.Value is IEmbeddedContent adapter) { - this.Adapter = adapter; + Adapter = adapter; context.NextOrientation = ConnectionInvokeType.IsSucceed; } else diff --git a/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs b/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs new file mode 100644 index 0000000..e3fc394 --- /dev/null +++ b/NodeFlow/Model/Operation/ChangeNodeConnectionOperation.cs @@ -0,0 +1,451 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.NodeFlow.Env; +using Serein.NodeFlow.Model.Node; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Serein.Library.Api.NodeConnectChangeEventArgs; + +namespace Serein.NodeFlow.Model.Operation +{ + /// + /// 节点连接状态发生改变 + /// + internal class ChangeNodeConnectionOperation : OperationBase + { + public override string Theme => nameof(ChangeNodeConnectionOperation); + + /// + /// 所在画布 + /// + public required string CanvasGuid { get; set; } + + /// + /// 连接关系中始节点的Guid + /// + public required string FromNodeGuid { get; set; } + + /// + /// 连接关系中目标节点的Guid + /// + public required string ToNodeGuid { get; set; } + + /// + /// 起始节点连接控制点类型 + /// + public JunctionType FromNodeJunctionType { get; set; } + + /// + /// 目标节点连接控制点类型 + /// + public JunctionType ToNodeJunctionType { get; set; } + + /// 连接类型 + /// + public ConnectionInvokeType ConnectionInvokeType { get; set; } + /// + /// 表示此次需要在两个节点之间创建连接关系,或是移除连接关系 + /// + public ConnectChangeType ChangeType { get; set; } + /// + /// 指示需要创建什么类型的连接线 + /// + public JunctionOfConnectionType JunctionOfConnectionType { get; set; } = JunctionOfConnectionType.None; + /// + /// 节点对应的方法入参所需参数来源 + /// + public ConnectionArgSourceType ConnectionArgSourceType { get; set; } + + /// + /// 第几个参数 + /// + public int ArgIndex { get; set; } = -1; + + public override bool IsCanUndo => false; + + #region 私有参数 + private FlowCanvasDetails FlowCanvas; + private IFlowNode FromNode; + private IFlowNode ToNode; + #endregion + + public override bool ValidationParameter() + { + if (JunctionOfConnectionType == JunctionOfConnectionType.None) + return false; + if (JunctionOfConnectionType == JunctionOfConnectionType.Arg && ArgIndex == -1) + return false; + + if (!flowModelService.ContainsCanvasModel(CanvasGuid) // 不存在画布 + || !flowModelService.ContainsNodeModel(FromNodeGuid) // 不存在节点 + || !flowModelService.ContainsNodeModel(ToNodeGuid)) // 不存在节点 + { + return false; + } + + return true; + } + + public override bool Execute() + { + if (!ValidationParameter()) return false; + if (!flowModelService.TryGetCanvasModel(CanvasGuid, out FlowCanvas) // 不存在画布 + || !flowModelService.TryGetNodeModel(FromNodeGuid, out FromNode) // 不存在节点 + || !flowModelService.TryGetNodeModel(ToNodeGuid, out ToNode)) // 不存在节点 + { + return false; + } + + if(ChangeType == ConnectChangeType.Create) // 创建连线时需要检查 + { + (var jcType, var isCanConnection) = CheckConnect(FromNode, ToNode, FromNodeJunctionType, ToNodeJunctionType); + if (!isCanConnection) + { + SereinEnv.WriteLine(InfoType.WARN, "出现非预期的连接行为"); + return false; // 出现不符预期的连接行为,忽略此次连接行为 + } + + // 如果起始控制点是“方法执行”,目标控制点是“方法调用”,需要反转 from to 节点 + if (jcType == JunctionOfConnectionType.Invoke + && FromNodeJunctionType == JunctionType.Execute + && ToNodeJunctionType == JunctionType.NextStep) + { + // 如果 起始控制点 是“方法调用”,需要反转 from to 节点 + (FromNode, ToNode) = (ToNode, FromNode); + } + + // 如果起始控制点是“方法入参”,目标控制点是“返回值”,需要反转 from to 节点 + if (jcType == JunctionOfConnectionType.Arg + && FromNodeJunctionType == JunctionType.ArgData + && ToNodeJunctionType == JunctionType.ReturnData) + { + (FromNode, ToNode) = (ToNode, FromNode); + } + } + + + //if (toNode is SingleFlipflopNode flipflopNode) + //{ + // flowTaskManagement?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被连接的是全局触发器,尝试移除 + //} + + var state = (JunctionOfConnectionType, ChangeType) switch + { + (JunctionOfConnectionType.Invoke, NodeConnectChangeEventArgs.ConnectChangeType.Create) => CreateInvokeConnection(), // 创建节点之间的调用关系 + (JunctionOfConnectionType.Invoke, NodeConnectChangeEventArgs.ConnectChangeType.Remove) => RemoveInvokeConnection(), // 移除节点之间的调用关系 + (JunctionOfConnectionType.Arg, NodeConnectChangeEventArgs.ConnectChangeType.Create) => CreateArgConnection(), // 创建节点之间的参数传递关系 + (JunctionOfConnectionType.Arg, NodeConnectChangeEventArgs.ConnectChangeType.Remove) => RemoveArgConnection(), // 移除节点之间的参数传递关系 + _ => false + }; + return state; + } + + public override void ToInfo() + { + throw new NotImplementedException(); + } + + /// + /// 创建方法调用关系 + /// + private bool CreateInvokeConnection() + { + IFlowNode fromNode = FromNode ; + IFlowNode toNode = ToNode; + ConnectionInvokeType invokeType = ConnectionInvokeType; + if (fromNode.ControlType == NodeControlType.FlowCall) + { + SereinEnv.WriteLine(InfoType.ERROR, $"流程接口节点不可调用下一个节点。" + + $"{Environment.NewLine}流程节点:{fromNode.Guid}"); + return false; + } + + + var isOverwriting = false; + ConnectionInvokeType overwritingCt = ConnectionInvokeType.None; + var isPass = false; + + #region 检查是否存在对应的连接 + foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) + { + var count1 = fromNode.SuccessorNodes[ctType].Count(it => it.Guid.Equals(toNode.Guid)); + var count2 = toNode.PreviousNodes[ctType].Count(it => it.Guid.Equals(fromNode.Guid)); + var hasError1 = count1 > 0; + var hasError2 = count2 > 0; + if (hasError1 && hasError2) + { + if (ctType == invokeType) + { + SereinEnv.WriteLine(InfoType.WARN, $"起始节点已与目标节点存在连接。" + + $"{Environment.NewLine}起始节点:{fromNode.Guid}" + + $"{Environment.NewLine}目标节点:{toNode.Guid}"); + return false; + } + isOverwriting = true; // 需要移除连接再创建连接 + overwritingCt = ctType; + } + else + { + // 检查是否可能存在异常 + if (!hasError1 && hasError2) + { + SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的父节点,目标节点却是起始节点的子节点。" + + $"{Environment.NewLine}起始节点:{fromNode.Guid}" + + $"{Environment.NewLine}目标节点:{toNode.Guid}"); + isPass = false; + } + else if (hasError1 && !hasError2) + { + // + SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的父节点,目标节点却是起始节点的子节点。" + + $"{Environment.NewLine}起始节点:{fromNode.Guid}" + + $"{Environment.NewLine}目标节点:{toNode.Guid}" + + $""); + isPass = false; + } + else + { + isPass = true; + } + } + } + #endregion + + + if (isPass) + { + if (isOverwriting) // 需要替换 + { + fromNode.SuccessorNodes[overwritingCt].Remove(toNode); // 从起始节点原有类别的子分支中移除 + toNode.PreviousNodes[overwritingCt].Remove(fromNode); // 从目标节点原有类别的父分支中移除 + } + fromNode.SuccessorNodes[invokeType].Add(toNode); // 添加到起始节点新类别的子分支 + toNode.PreviousNodes[invokeType].Add(fromNode); // 添加到目标节点新类别的父分支 + flowEnvironmentEvent.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + FlowCanvas.Guid, + fromNode.Guid, // 从哪个节点开始 + toNode.Guid, // 连接到那个节点 + JunctionOfConnectionType.Invoke, + invokeType, // 连接线的样式类型 + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + )); + // Invoke + // GetResult + return true; + } + else + { + return false; + } + + + } + + /// + /// 移除方法调用关系 + /// + private bool RemoveInvokeConnection() + { + FromNode.SuccessorNodes[ConnectionInvokeType].Remove(ToNode); + ToNode.PreviousNodes[ConnectionInvokeType].Remove(FromNode); + + flowEnvironmentEvent.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + FlowCanvas.Guid, + FromNode.Guid, + ToNode.Guid, + JunctionOfConnectionType.Invoke, + ConnectionInvokeType, + NodeConnectChangeEventArgs.ConnectChangeType.Remove)); + + + /* if (string.IsNullOrEmpty(ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid)) + { + return false; + } + toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid = null; + toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值 + + if (OperatingSystem.IsWindows()) + { + UIContextOperation?.Invoke(() => Event.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + canvasGuid, + fromNode.Guid, + toNode.Guid, + argIndex, + JunctionOfConnectionType.Arg, + ConnectionArgSourceType.GetPreviousNodeData, + NodeConnectChangeEventArgs.ConnectChangeType.Remove))); + }*/ + return true; + } + + /// + /// 创建参数连接关系 + /// + /// + private bool CreateArgConnection() + { + IFlowNode fromNodeControl = ToNode; + IFlowNode toNodeControl = ToNode; + ConnectionArgSourceType type = ConnectionArgSourceType; + int index = ArgIndex; + + + + var toNodeArgSourceGuid = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid; // 目标节点对应参数可能已经有其它连接 + var toNodeArgSourceType = ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType; + + if (FromNode.Guid == toNodeArgSourceGuid + && toNodeArgSourceType == ConnectionArgSourceType) + { + SereinEnv.WriteLine(InfoType.INFO, $"节点之间已建立过连接关系,此次操作将不会执行" + + $"起始节点:{FromNode.Guid}" + + $"目标节点:{ToNode.Guid}" + + $"参数索引:{ArgIndex}" + + $"参数类型:{ConnectionArgSourceType}"); + /*flowEnvironmentEvent.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + FlowCanvas.Guid, + FromNode.Guid, // 从哪个节点开始 + ToNode.Guid, // 连接到那个节点 + ArgIndex, // 连接线的样式类型 + JunctionOfConnectionType.Arg, + ConnectionArgSourceType, + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + )); // 通知UI */ + return true; + } + + if (!string.IsNullOrEmpty(toNodeArgSourceGuid)) // 更改关系获取 + { + ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = null; + ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值 + flowEnvironmentEvent.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + FlowCanvas.Guid, + FromNode.Guid, + ToNode.Guid, + ArgIndex, + JunctionOfConnectionType.Arg, + ConnectionArgSourceType.GetPreviousNodeData, + NodeConnectChangeEventArgs.ConnectChangeType.Remove)); + } + + ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = FromNode.Guid; // 设置 + ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType = ConnectionArgSourceType; + + flowEnvironmentEvent.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + FlowCanvas.Guid, + FromNode.Guid, // 从哪个节点开始 + ToNode.Guid, // 连接到那个节点 + ArgIndex, // 连接线的样式类型 + JunctionOfConnectionType.Arg, + ConnectionArgSourceType, + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + )); // 通知UI + return true; + + } + + /// + /// 移除参数连接关系 + /// + /// + /// + /// + private bool RemoveArgConnection() + { + ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceNodeGuid = null; + ToNode.MethodDetails.ParameterDetailss[ArgIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值 + + if (OperatingSystem.IsWindows()) + { + flowEnvironmentEvent.OnNodeConnectChanged( + new NodeConnectChangeEventArgs( + FlowCanvas.Guid, + FromNode.Guid, + ToNode.Guid, + ArgIndex, + JunctionOfConnectionType.Arg, + ConnectionArgSourceType.GetPreviousNodeData, + NodeConnectChangeEventArgs.ConnectChangeType.Remove)); + } + return true; + } + + /// + /// 检查连接是否合法 + /// + /// 发起连接的起始节点 + /// 要连接的目标节点 + /// 发起连接节点的控制点类型 + /// 被连接节点的控制点类型 + /// + public static (JunctionOfConnectionType, bool) CheckConnect(IFlowNode fromNode, + IFlowNode toNode, + JunctionType fromNodeJunctionType, + JunctionType toNodeJunctionType) + { + var type = JunctionOfConnectionType.None; + var state = false; + if (fromNodeJunctionType == JunctionType.Execute) + { + if (toNodeJunctionType == JunctionType.NextStep && !fromNode.Guid.Equals(toNode.Guid)) + { + // “方法执行”控制点拖拽到“下一节点”控制点,且不是同一个节点, 添加方法执行关系 + type = JunctionOfConnectionType.Invoke; + state = true; + } + //else if (toNodeJunctionType == JunctionType.ArgData && fromNode.Guid.Equals(toNode.Guid)) + //{ + // // “方法执行”控制点拖拽到“方法入参”控制点,且是同一个节点,则添加获取参数关系,表示生成入参参数时自动从该节点的上一节点获取flowdata + // type = JunctionOfConnectionType.Arg; + // state = true; + //} + } + else if (fromNodeJunctionType == JunctionType.NextStep && !fromNode.Guid.Equals(toNode.Guid)) + { + // “下一节点”控制点只能拖拽到“方法执行”控制点,且不能是同一个节点 + if (toNodeJunctionType == JunctionType.Execute && !fromNode.Guid.Equals(toNode.Guid)) + { + type = JunctionOfConnectionType.Invoke; + state = true; + } + } + else if (fromNodeJunctionType == JunctionType.ArgData) + { + //if (toNodeJunctionType == JunctionType.Execute && fromNode.Guid.Equals(toNode.Guid)) // 添加获取参数关系 + //{ + // // “方法入参”控制点拖拽到“方法执行”控制点,且是同一个节点,则添加获取参数关系,生成入参参数时自动从该节点的上一节点获取flowdata + // type = JunctionOfConnectionType.Arg; + // state = true; + //} + if (toNodeJunctionType == JunctionType.ReturnData && !fromNode.Guid.Equals(toNode.Guid)) + { + // “”控制点拖拽到“方法返回值”控制点,且不是同一个节点,添加获取参数关系,生成参数时从目标节点获取flowdata + type = JunctionOfConnectionType.Arg; + state = true; + } + } + else if (fromNodeJunctionType == JunctionType.ReturnData) + { + if (toNodeJunctionType == JunctionType.ArgData && !fromNode.Guid.Equals(toNode.Guid)) + { + // “方法返回值”控制点拖拽到“方法入参”控制点,且不是同一个节点,添加获取参数关系,生成参数时从目标节点获取flowdata + type = JunctionOfConnectionType.Arg; + state = true; + } + } + // 剩下的情况都是不符预期的连接行为,忽略。 + return (type, state); + } + + } +} diff --git a/NodeFlow/Model/Operation/ChangeParameterOperation.cs b/NodeFlow/Model/Operation/ChangeParameterOperation.cs new file mode 100644 index 0000000..6269fc1 --- /dev/null +++ b/NodeFlow/Model/Operation/ChangeParameterOperation.cs @@ -0,0 +1,90 @@ +using Microsoft.CodeAnalysis; +using Serein.Library.Api; +using Serein.NodeFlow.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Operation +{ + internal class ChangeParameterOperation : OperationBase + { + public override string Theme => nameof(ChangeParameterOperation); + + public string NodeGuid { get; set; } + public bool IsAdd{ get; set; } + + public int ParamIndex { get; set; } + + + private IFlowNode nodeModel; + + + public override bool ValidationParameter() + { + if (!flowModelService.TryGetNodeModel(NodeGuid, out var nodeModel)) + { + return false; + } + this.nodeModel = nodeModel; + + var pds = nodeModel.MethodDetails.ParameterDetailss; + var parameterCount = pds.Length; + if (ParamIndex >= parameterCount) + { + return false; // 需要被添加的下标索引大于入参数组的长度 + } + if (IsAdd) + { + if (pds[ParamIndex].IsParams == false) + { + return false; // 对应的入参并非可选参数中的一部分 + } + } + else + { + + } + + return true; + } + + + public override bool Execute() + { + if (!ValidationParameter()) return false; + + if (IsAdd) + { + if (nodeModel.MethodDetails.AddParamsArg(ParamIndex)) + { + return true; + } + else + { + return false; + } + } + else + { + if (nodeModel.MethodDetails.RemoveParamsArg(ParamIndex)) + { + return true; + } + else + { + return true; + } + } + } + + public override void ToInfo() + { + throw new NotImplementedException(); + } + + + } +} diff --git a/NodeFlow/Model/Operation/ContainerPlaceNodeOperation.cs b/NodeFlow/Model/Operation/ContainerPlaceNodeOperation.cs new file mode 100644 index 0000000..828d5b8 --- /dev/null +++ b/NodeFlow/Model/Operation/ContainerPlaceNodeOperation.cs @@ -0,0 +1,98 @@ +using Serein.Library.Api; +using Serein.NodeFlow.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Operation +{ + /// + /// 放置节点操作 + /// + internal class ContainerPlaceNodeOperation : OperationBase + { + public override string Theme => nameof(ContainerPlaceNodeOperation); + + /// + /// 所在画布 + /// + public string CanvasGuid { get; set; } + + /// + /// 子节点,该数据为此次事件的主节点 + /// + public string NodeGuid { get; set; } + /// + /// 父节点 + /// + public string ContainerNodeGuid { get; set; } + + + /// + /// 父节点 + /// + private INodeContainer ContainerNode; + + /// + /// 子节点,该数据为此次事件的主节点 + /// + private IFlowNode Node; + + + + public override bool ValidationParameter() + { + if (!flowModelService.ContainsCanvasModel(CanvasGuid)) + { + return false; + } + // 获取目标节点与容器节点 + if (!flowModelService.TryGetNodeModel(NodeGuid, out var nodeModel)) + { + return false; + } + if (!flowModelService.TryGetNodeModel(ContainerNodeGuid, out var containerNode)) + { + return false; + } + if (nodeModel.ContainerNode is INodeContainer tmpContainer) + { + //SereinEnv.WriteLine(InfoType.WARN, $"节点放置失败,节点[{nodeGuid}]已经放置于容器节点[{((IFlowNode)tmpContainer).Guid}]"); + return false; + } + if(containerNode is not INodeContainer containerNode2) + { + return false; + } + + Node = nodeModel; + ContainerNode = containerNode2; + return true; + } + + public override bool Execute() + { + if (!ValidationParameter()) return false; + + ContainerNode.PlaceNode(Node); + flowEnvironmentEvent.OnNodePlace(new NodePlaceEventArgs(CanvasGuid, NodeGuid, ContainerNodeGuid)); // 通知UI更改节点放置位置 + return true; + } + + public override bool Undo() + { + ContainerNode.TakeOutNode(Node); + flowEnvironmentEvent.OnNodeTakeOut(new NodeTakeOutEventArgs(CanvasGuid, NodeGuid)); // 重新放置在画布上 + return true; + } + + + public override void ToInfo() + { + } + + + } +} diff --git a/NodeFlow/Model/Operation/ContainerTakeOutNodeOperation.cs b/NodeFlow/Model/Operation/ContainerTakeOutNodeOperation.cs new file mode 100644 index 0000000..650dd91 --- /dev/null +++ b/NodeFlow/Model/Operation/ContainerTakeOutNodeOperation.cs @@ -0,0 +1,89 @@ +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Operation +{ + + /// + /// 取出节点操作 + /// + internal class ContainerTakeOutNodeOperation : OperationBase + { + public override string Theme => nameof(ContainerTakeOutNodeOperation); + + /// + /// 所在画布 + /// + public string CanvasGuid { get; set; } + + /// + /// 子节点,该数据为此次事件的主节点 + /// + public string NodeGuid { get; set; } + + + /// + /// 父节点 + /// + private INodeContainer ContainerNode; + + /// + /// 子节点,该数据为此次事件的主节点 + /// + private IFlowNode Node; + + + + public override bool ValidationParameter() + { + if (!flowModelService.ContainsCanvasModel(CanvasGuid)) + { + flowEnvironment.WriteLine(Library.InfoType.INFO, $"节点取出失败,目标画布不存在[{NodeGuid}]"); + return false; + } + // 获取目标节点与容器节点 + if (!flowModelService.TryGetNodeModel(NodeGuid, out var nodeModel)) + { + flowEnvironment.WriteLine(Library.InfoType.INFO, $"节点取出失败,目标节点不存在[{NodeGuid}]"); + return false; + } + if (nodeModel.ContainerNode is not INodeContainer containerNode) + { + flowEnvironment.WriteLine(Library.InfoType.INFO, $"节点取出失败,节点并非容器节点[{nodeModel.Guid}]"); + return false; + } + Node = nodeModel; + ContainerNode = containerNode; + return true; + } + + public override bool Execute() + { + if (!ValidationParameter()) return false; + + ContainerNode.TakeOutNode(Node); + flowEnvironmentEvent.OnNodeTakeOut(new NodeTakeOutEventArgs(CanvasGuid, NodeGuid)); // 重新放置在画布上 + return true; + } + + public override bool Undo() + { + ContainerNode.PlaceNode(Node); + if (ContainerNode is IFlowNode containerFlowNode) + { + flowEnvironmentEvent.OnNodePlace(new NodePlaceEventArgs(CanvasGuid, NodeGuid, containerFlowNode.Guid)); // 通知UI更改节点放置位置 + + } + return true; + } + + + public override void ToInfo() + { + } + } +} diff --git a/NodeFlow/Model/Operation/CreateCanvasOperation.cs b/NodeFlow/Model/Operation/CreateCanvasOperation.cs new file mode 100644 index 0000000..747f6b1 --- /dev/null +++ b/NodeFlow/Model/Operation/CreateCanvasOperation.cs @@ -0,0 +1,51 @@ +using Serein.Library; +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Operation +{ + internal class CreateCanvasOperation : OperationBase + { + public override string Theme => nameof(CreateCanvasOperation); + public override bool IsCanUndo => false; + + public required FlowCanvasDetailsInfo CanvasInfo { get; set; } + + + + private FlowCanvasDetails? flowCanvasDetails; + + public override bool ValidationParameter() + { + if (CanvasInfo is null) + return false; // 没有必须的参数 + if (string.IsNullOrEmpty(CanvasInfo.Guid)) + return false; // 不能没有Guid + if(flowModelService.ContainsCanvasModel(CanvasInfo.Guid)) + return false; // 画布已存在 + return true; + } + + public override bool Execute() + { + if(!ValidationParameter()) return false; + + var cavasnModel = new FlowCanvasDetails(flowEnvironment); + cavasnModel.LoadInfo(CanvasInfo); + flowModelService.AddCanvasModel(cavasnModel); + this.flowCanvasDetails = cavasnModel; ; + flowEnvironmentEvent.OnCanvasCreated(new CanvasCreateEventArgs(cavasnModel)); + return true; + } + + public override void ToInfo() + { + throw new NotImplementedException(); + } + + } +} diff --git a/NodeFlow/Model/Operation/CreateNodeOperation.cs b/NodeFlow/Model/Operation/CreateNodeOperation.cs new file mode 100644 index 0000000..e28fa66 --- /dev/null +++ b/NodeFlow/Model/Operation/CreateNodeOperation.cs @@ -0,0 +1,145 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.NodeFlow.Model.Node; +using Serein.NodeFlow.Services; +using Serein.NodeFlow.Tool; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Operation +{ + internal class CreateNodeOperation : OperationBase + { + public override string Theme => nameof(CreateNodeOperation); + + + public required string CanvasGuid { get; set; } + public required NodeControlType NodeControlType { get; set; } + public required PositionOfUI Position { get; set; } + public required MethodDetailsInfo? MethodDetailsInfo { get; set; } + + + /// + /// 是否为基础节点 + /// + private bool IsBaseNode => NodeControlType.IsBaseNode(); + + /// + /// 执行成功后所创建的节点 + /// + private IFlowNode? flowNode; + + /// + /// 节点所在画布 + /// + private FlowCanvasDetails flowCanvasDetails; + + + + public override bool ValidationParameter() + { + // 检查是否存在画布 + + var canvasModel = flowModelService.GetCanvasModel(CanvasGuid); + if(canvasModel is null) + return false; + + // 检查类型(防非预期的调用) + if (NodeControlType == NodeControlType.None) + return false; + + // 检查放置位置是否超限(防非预期的调用) + if (Position.X < 0 || Position.Y < 0 + || Position.X > canvasModel.Width + || Position.Y > canvasModel.Height) + return false; + + // 所创建的节点并非基础节点,却没有传入方法信息,将会导致创建失败 + if (!IsBaseNode && MethodDetailsInfo is null) + return false; + + // 缓存画布model,提高性能 + this.flowCanvasDetails = canvasModel; + + return true; + } + + public override bool Execute() + { + if (!ValidationParameter()) return false; // 执行时验证 + + IFlowNode? nodeModel; + if (IsBaseNode) + { + nodeModel = FlowNodeExtension.CreateNode(flowEnvironment, NodeControlType); // 加载基础节点 + } + else + { + if(MethodDetailsInfo is null) + { + return false; + //throw new InvalidOperationException($"无法创建节点,因为MethodDetailsInfo属性为null"); + } + if (!flowLibraryManagement.TryGetMethodDetails(MethodDetailsInfo.AssemblyName, // 创建节点 + MethodDetailsInfo.MethodName, + out var methodDetails)) + { + return false; + //throw new InvalidOperationException($"无法创建节点,因为没有找到{MethodDetailsInfo.AssemblyName}.{MethodDetailsInfo.MethodName}方法,请检查是否已加载对应程序集"); + } + nodeModel = FlowNodeExtension.CreateNode(flowEnvironment, NodeControlType, methodDetails); // 一般的加载节点方法 + } + + nodeModel.Guid ??= Guid.NewGuid().ToString(); + nodeModel.Position = Position; // 设置位置 + + // 节点与画布互相绑定 + nodeModel.CanvasDetails = flowCanvasDetails; + flowCanvasDetails.Nodes.Add(nodeModel); + + flowModelService.AddNodeModel(nodeModel); + this.flowNode = nodeModel; + flowEnvironmentEvent.OnNodeCreated(new NodeCreateEventArgs(flowCanvasDetails.Guid, nodeModel, Position)); + return true; + } + + + public override bool Undo() + { + if (!ValidationParameter()) return false; // 撤销时验证 + if(flowNode is null) return false; // 没有创建过节点 + var canvasGuid = flowCanvasDetails.Guid; + var nodeGuid = flowNode.Guid; + flowEnvironment.RemoveNode(canvasGuid, nodeGuid); + return true; + } + + + public override void ToInfo() + { + throw new NotImplementedException(); + } + + + /*private bool TryAddNode(IFlowNode nodeModel) + { + nodeModel.Guid ??= Guid.NewGuid().ToString(); + NodeModels.TryAdd(nodeModel.Guid, nodeModel); + + + // 如果是触发器,则需要添加到专属集合中 + if (nodeModel is SingleFlipflopNode flipflopNode) + { + var guid = flipflopNode.Guid; + if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid))) + { + FlipflopNodes.Add(flipflopNode); + } + } + return true; + }*/ + } +} diff --git a/NodeFlow/Model/Operation/OperationBase.cs b/NodeFlow/Model/Operation/OperationBase.cs index 92ae550..86ca91d 100644 --- a/NodeFlow/Model/Operation/OperationBase.cs +++ b/NodeFlow/Model/Operation/OperationBase.cs @@ -1,92 +1,118 @@ -using System; +using Serein.Library; +using Serein.Library.Api; +using Serein.NodeFlow.Services; +using Serein.NodeFlow.Tool; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Serein.NodeFlow.Model.Operation { + internal interface IOperation + { + /// + /// 用于判断是否可以撤销 + /// + bool IsCanUndo { get; } + /// + /// 执行操作前验证数据 + /// + /// + bool ValidationParameter(); + /// + /// 执行操作 + /// + bool Execute(); + /// + /// 撤销操作 + /// + bool Undo(); + } - class Test { + internal abstract class OperationBase : IOperation + { + /// + /// 运行环境 + /// + [AutoInjection] + protected IFlowEnvironment flowEnvironment; /// - /// 撤销栈 + /// 节点管理服务 /// - private Stack undoStack = []; + [AutoInjection] + protected FlowModelService flowModelService; + /// - /// 重做栈 + /// 流程依赖服务 /// - private Stack redoStack = []; + [AutoInjection] + protected FlowLibraryManagement flowLibraryManagement; + + /// + /// 流程事件服务 + /// + [AutoInjection] + protected IFlowEnvironmentEvent flowEnvironmentEvent; + + public abstract string Theme { get;} + + /// + /// 是否支持特效 + /// + public virtual bool IsCanUndo => true; - /* - // 执行新命令时,将命令推入撤销栈,并清空重做栈 - undoStack.Push(operation); - redoStack.Clear(); - */ + /// + /// 验证参数 + /// + /// + public abstract bool ValidationParameter(); + /// + /// 执行 + /// + public abstract bool Execute(); /// /// 撤销 /// - public void Undo() + public virtual bool Undo() { - if (undoStack.Count > 0) + if (!IsCanUndo) { - var command = undoStack.Pop(); - command.Undo(); // 执行撤销 - redoStack.Push(command); // 将撤销的命令推入重做栈 + Debug.WriteLine($"该操作暂未提供撤销功能[{Theme}]"); + return false; } + return true; } /// - /// 重做 + /// 导出操作信息 /// - public void Redo() - { - if (redoStack.Count > 0) - { - var command = redoStack.Pop(); - command.Execute(); - undoStack.Push(command); // 将重做的命令推入撤销栈 - } - } + public abstract void ToInfo(); + + } - internal class OperationInfo { } - internal abstract class OperationBase : IOperation - { - /// - /// 操作的主题 - /// - public required string Theme { get; set; } - public abstract void Execute(); - public abstract void Undo(); - public abstract void ToInfo(); + class Test { - protected OperationBase() - { - - } - protected OperationBase(OperationInfo info) - { - - } + } - internal interface IOperation - { - void Execute(); // 执行操作 - void Undo(); // 撤销操作 - - } + + + } diff --git a/NodeFlow/Model/Operation/RemoveCanvasOperation.cs b/NodeFlow/Model/Operation/RemoveCanvasOperation.cs new file mode 100644 index 0000000..fc25d4a --- /dev/null +++ b/NodeFlow/Model/Operation/RemoveCanvasOperation.cs @@ -0,0 +1,59 @@ +using Serein.Library; +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Operation +{ + internal class RemoveCanvasOperation : OperationBase + { + public override string Theme => nameof(RemoveCanvasOperation); + public override bool IsCanUndo => false; + public required string CanvasGuid { get; set; } + + private FlowCanvasDetailsInfo? flowCanvasDetailsInfo; + private FlowCanvasDetails? flowCanvasDetails; + + public override bool ValidationParameter() + { + var canvasModel = flowModelService.GetCanvasModel(CanvasGuid); + if (canvasModel is null) return false; // 画布不存在 + var nodeCount = canvasModel.Nodes.Count; + if (nodeCount > 0) + { + SereinEnv.WriteLine(InfoType.WARN, "无法删除具有节点的画布"); + return false; + } + this.flowCanvasDetails = canvasModel; + return true; + } + + public override bool Execute() + { + if (!ValidationParameter()) return false; + + if (flowCanvasDetails is null) + { + // 验证过画布存在,但这时画布不存在了 + // 考虑到多线程操作影响,一般不会进入这个逻辑分支 + var canvasModel = flowModelService.GetCanvasModel(CanvasGuid); + if (canvasModel is null) return false; // 画布不存在 + flowCanvasDetails = canvasModel; + } + + flowModelService.RemoveCanvasModel(flowCanvasDetails); + flowCanvasDetailsInfo = flowCanvasDetails.ToInfo(); + flowEnvironmentEvent.OnCanvasRemoved(new CanvasRemoveEventArgs(flowCanvasDetails.Guid)); + return true; + } + + public override void ToInfo() + { + throw new NotImplementedException(); + } + + } +} diff --git a/NodeFlow/Model/Operation/RemoveNodeOperation.cs b/NodeFlow/Model/Operation/RemoveNodeOperation.cs new file mode 100644 index 0000000..d99d014 --- /dev/null +++ b/NodeFlow/Model/Operation/RemoveNodeOperation.cs @@ -0,0 +1,216 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Serein.Library; +using Serein.Library.Api; +using Serein.Script.Node; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Operation +{ + internal class RemoveNodeOperation : OperationBase + { + public override string Theme => throw new NotImplementedException(); + + public required string CanvasGuid { get; internal set; } + public required string NodeGuid { get; internal set; } + + /// + /// 节点所在画布 + /// + private FlowCanvasDetails flowCanvasDetails; + /// + /// 被删除的节点 + /// + private IFlowNode flowNode; + + /// + /// 移除节点时删除连线所触发的事件参数的缓存 + /// + private List EventArgs { get; } = new List(); + + public override bool ValidationParameter() + { + var canvasModel = flowModelService.GetCanvasModel(CanvasGuid); + var nodeModel = flowModelService.GetNodeModel(NodeGuid); + if(canvasModel is null) + { + return false; + } + if(nodeModel is null) + { + return false; + } + flowCanvasDetails = canvasModel; + flowNode = nodeModel; + return true; + } + + public override bool Execute() + { + if (!ValidationParameter()) return false; + + // 需要移除对应的方法调用、以及参数获取调用 + // 还需要记录移除的事件参数,用以撤销恢复 + + #region 移除方法调用关系 + foreach (var item in flowNode.PreviousNodes) + { + + var connectionType = item.Key; // 连接类型 + var previousNodes = item.Value; // 对应类型的父节点集合 + foreach (IFlowNode previousNode in previousNodes) + { + previousNode.SuccessorNodes[connectionType].Remove(flowNode); + var e = new NodeConnectChangeEventArgs( + CanvasGuid, // 画布 + previousNode.Guid, // 父节点Guid + flowNode.Guid, // 被移除的节点Guid + JunctionOfConnectionType.Invoke, // 方法调用关系 + connectionType, // 对应的连接关系 + NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线 + EventArgs.Add(e); // 缓存事件参数 + flowEnvironmentEvent.OnNodeConnectChanged(e); + + } + } + + if (flowNode.ControlType == NodeControlType.FlowCall) + { + // 根据流程接口节点目前的设计,暂未支持能连接下一个节点 + } + else + { + // 遍历所有后继节点,从那些后继节点中的前置节点集合中移除该节点 + foreach (var item in flowNode.SuccessorNodes) + { + + var connectionType = item.Key; // 方法调用连接类型 + var successorNodes = item.Value; // 对应类型的父节点集合 + foreach (IFlowNode successorNode in successorNodes) + { + successorNode.SuccessorNodes[connectionType].Remove(flowNode); + var e = new NodeConnectChangeEventArgs( + CanvasGuid, // 画布 + flowNode.Guid, // 被移除的节点Guid + successorNode.Guid, // 子节点Guid + JunctionOfConnectionType.Invoke, // 方法调用关系 + connectionType, // 对应的连接关系 + NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线 + EventArgs.Add(e); // 缓存事件参数 + flowEnvironmentEvent.OnNodeConnectChanged(e); + } + } + } + + #endregion + + #region 移除参数获取关系 + // 需要找到有哪些节点的入参参数,被设置为了该节点,然后将其删除 + // 因为节点自身没有记录哪些节点选取了自己作为参数来源节点,所以需要遍历所有节点 + + foreach (var item in flowNode.NeedResultNodes) + { + var connectionType = item.Key; // 参数来源连接类型 + var argNodes = item.Value; // 对应类型的入参需求节点集合 + foreach (var argNode in argNodes) + { + var md = argNode.MethodDetails; + if (md is null) continue; + var pds = md.ParameterDetailss; + if (pds is null || pds.Length == 0) continue; + foreach(var parameter in pds) + { + if (!parameter.ArgDataSourceNodeGuid.Equals(flowNode.Guid)) continue; + // 找到了对应的入参控制点了 + var e = new NodeConnectChangeEventArgs( + CanvasGuid, // 画布 + flowNode.Guid, // 被移除的节点Guid + argNode.Guid, // 子节点Guid + parameter.Index, // 作用在第几个参数上,用于指示移除第几个参数的连线 + JunctionOfConnectionType.Arg, // 指示移除的是参数连接线 + connectionType, // 对应的连接关系 + NodeConnectChangeEventArgs.ConnectChangeType.Remove); // 移除连线 + EventArgs.Add(e); // 缓存事件参数 + flowEnvironmentEvent.OnNodeConnectChanged(e); + } + } + } + #endregion + + flowModelService.RemoveNodeModel(flowNode); // 从记录中移除 + //flowNode.Remove(); // 调用节点的移除方法 + + if(flowEnvironment.UIContextOperation is null) + { + flowCanvasDetails?.Nodes.Remove(flowNode); + } + else + { + // 存在UI上下文操作,当前运行环境极有可能运行在有UI线程的平台上 + // 为了避免直接修改 ObservableCollection 集合导致异常产生,故而使用UI线程上下文操作运行 + flowEnvironment.UIContextOperation?.Invoke(() => + { + flowCanvasDetails?.Nodes.Remove(flowNode); + }); + } + flowEnvironmentEvent.OnNodeRemoved(new NodeRemoveEventArgs(CanvasGuid, NodeGuid)); + return true; + } + + public override bool Undo() + { + // 先恢复被删除的节点 + + + + // 撤销删除节点时,还需要恢复连线状态 + foreach (NodeConnectChangeEventArgs e in EventArgs) + { + NodeConnectChangeEventArgs? newEventArgs = null; + if (e.JunctionOfConnectionType == JunctionOfConnectionType.Invoke) + { + newEventArgs = new NodeConnectChangeEventArgs( + e.CanvasGuid, // 画布 + e.FromNodeGuid, // 被移除的节点Guid + e.ToNodeGuid, // 子节点Guid + e.JunctionOfConnectionType, // 指示需要恢复的是方法调用线 + e.ConnectionInvokeType, // 对应的连接关系 + NodeConnectChangeEventArgs.ConnectChangeType.Create); // 创建连线 + } + else if (e.JunctionOfConnectionType == JunctionOfConnectionType.Arg) + { + newEventArgs = new NodeConnectChangeEventArgs( + e.CanvasGuid, // 画布 + e.FromNodeGuid, // 被移除的节点Guid + e.ToNodeGuid, // 子节点Guid + e.ArgIndex, // 作用在第几个参数上,用于指示移除第几个参数的连线 + e.JunctionOfConnectionType, // 指示需要恢复的是参数连接线 + e.ConnectionArgSourceType, // 对应的连接关系 + NodeConnectChangeEventArgs.ConnectChangeType.Create); // 创建连线 + + } + else + { + newEventArgs = null; + } + if (newEventArgs != null) + { + // 使用反转了的事件参数进行触发 + flowEnvironmentEvent.OnNodeConnectChanged(newEventArgs); + } + } + return true; + } + + + public override void ToInfo() + { + throw new NotImplementedException(); + } + } +} diff --git a/NodeFlow/Model/Operation/SetConnectPriorityInvokeOperation.cs b/NodeFlow/Model/Operation/SetConnectPriorityInvokeOperation.cs new file mode 100644 index 0000000..8b0e4ce --- /dev/null +++ b/NodeFlow/Model/Operation/SetConnectPriorityInvokeOperation.cs @@ -0,0 +1,91 @@ +using Serein.Library; +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Operation +{ + + /// + /// 将调用顺序置为优先 + /// + internal class SetConnectPriorityInvokeOperation : OperationBase + { + public override string Theme => nameof(SetConnectPriorityInvokeOperation); + + public string FromNodeGuid { get; set; } + public string ToNodeGuid { get; set; } + public ConnectionInvokeType ConnectionType { get; set; } + + private IFlowNode FromNode; + private IFlowNode ToNode; + private int lastIdx = -1; + + public override bool ValidationParameter() + { + if (ConnectionType == ConnectionInvokeType.None) + { + return false; + } + // 获取起始节点与目标节点 + if (!flowModelService.TryGetNodeModel(FromNodeGuid, out var fromNode) || !flowModelService.TryGetNodeModel(ToNodeGuid, out var toNode)) + { + return false; + } + if (fromNode is null || toNode is null) return false; + + FromNode = fromNode; + ToNode = toNode; + return true; + + } + + /// + /// 成为首项 + /// + public override bool Execute() + { + if(!ValidationParameter()) return false; + + if (FromNode.SuccessorNodes.TryGetValue(ConnectionType, out var nodes)) + { + var idx = nodes.IndexOf(ToNode); + if (idx > -1) + { + lastIdx = idx; + nodes.RemoveAt(idx); + nodes.Insert(0, ToNode); + } + } + return true; + } + + /// + /// 恢复原来的位置 + /// + public override bool Undo() + { + if (FromNode.SuccessorNodes.TryGetValue(ConnectionType, out var nodes)) + { + var idx = nodes.IndexOf(ToNode); + if (idx > -1) + { + nodes.RemoveAt(idx); + nodes.Insert(lastIdx, ToNode); + lastIdx = 0; + } + } + return true; + } + + public override void ToInfo() + { + throw new NotImplementedException(); + } + + + } +} diff --git a/NodeFlow/Model/Operation/SetStartNodeOperation.cs b/NodeFlow/Model/Operation/SetStartNodeOperation.cs new file mode 100644 index 0000000..184c1e1 --- /dev/null +++ b/NodeFlow/Model/Operation/SetStartNodeOperation.cs @@ -0,0 +1,78 @@ +using Serein.Library; +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Model.Operation +{ + /// + /// 设置起始节点 + /// + internal class SetStartNodeOperation : OperationBase + { + public override string Theme => nameof(SetStartNodeOperation); + + + public string CanvasGuid { get; set; } + public string NewNodeGuid { get; set; } + + + private FlowCanvasDetails CanvasModel; + private IFlowNode NewStartNodeModel; + private IFlowNode? OldStartNodeModel; + + + public override bool ValidationParameter() + { + if (!flowModelService.TryGetCanvasModel(CanvasGuid, out CanvasModel) + || !flowModelService.TryGetNodeModel(NewNodeGuid, out NewStartNodeModel)) + { + return false; + } + return true; + } + public override bool Execute() + { + if (!ValidationParameter()) return false; + + if (CanvasModel.StartNode is not null + && flowModelService.TryGetNodeModel(CanvasModel.StartNode.Guid, out var flowNode)) + { + OldStartNodeModel = flowNode; + } + + CanvasModel.StartNode = NewStartNodeModel; + flowEnvironmentEvent.OnStartNodeChanged(new StartNodeChangeEventArgs(CanvasModel.Guid, OldStartNodeModel?.Guid, NewStartNodeModel.Guid)); + return true; + } + + public override bool Undo() + { + if(OldStartNodeModel is null) + { + return false; + } + var newStartNode = OldStartNodeModel; + var oldStartNode = NewStartNodeModel; + + NewStartNodeModel = newStartNode; + OldStartNodeModel = oldStartNode; + CanvasModel.StartNode = oldStartNode; + + flowEnvironmentEvent.OnStartNodeChanged(new StartNodeChangeEventArgs(CanvasModel.Guid, oldStartNode?.Guid, newStartNode.Guid)); + return true; + + } + + + public override void ToInfo() + { + throw new NotImplementedException(); + } + + + } +} diff --git a/NodeFlow/Serein.NodeFlow.csproj b/NodeFlow/Serein.NodeFlow.csproj index 17b8984..60d8a0f 100644 --- a/NodeFlow/Serein.NodeFlow.csproj +++ b/NodeFlow/Serein.NodeFlow.csproj @@ -40,6 +40,12 @@ + + + + + + diff --git a/NodeFlow/Services/FlowModelService.cs b/NodeFlow/Services/FlowModelService.cs new file mode 100644 index 0000000..a759281 --- /dev/null +++ b/NodeFlow/Services/FlowModelService.cs @@ -0,0 +1,99 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.NodeFlow.Model.Node; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Services +{ + public class FlowModelService + { + private readonly IFlowEnvironment environment; + + public FlowModelService(IFlowEnvironment environment) + { + this.environment = environment; + } + + /// + /// 环境加载的节点集合 + /// Node Guid - Node Model + /// + private Dictionary NodeModels { get; } = []; + + /// + /// 运行环境加载的画布集合 + /// + private Dictionary FlowCanvass { get; } = []; + + /// + /// 存放触发器节点(运行时全部调用) + /// + private List FlipflopNodes { get; } = []; + + public IFlowNode? GetNodeModel(string guid) + { + NodeModels.TryGetValue(guid, out var nodeModel); + return nodeModel; + } + + public FlowCanvasDetails? GetCanvasModel(string guid) + { + FlowCanvass.TryGetValue(guid, out var nodeModel); + return nodeModel; + } + + public bool TryGetNodeModel(string guid,out IFlowNode flowNode) + { + return NodeModels.TryGetValue(guid, out flowNode!); + } + + public bool TryGetCanvasModel(string guid,out FlowCanvasDetails flowCanvas) + { + return FlowCanvass.TryGetValue(guid, out flowCanvas!);; + } + + + public bool ContainsNodeModel(string guid) + { + return NodeModels.ContainsKey(guid); + } + + public bool ContainsCanvasModel(string guid) + { + return FlowCanvass.ContainsKey(guid); + } + + public bool AddNodeModel(IFlowNode flowNode) + { + ArgumentNullException.ThrowIfNull(flowNode); + ArgumentNullException.ThrowIfNull(flowNode.Guid); + return NodeModels.TryAdd(flowNode.Guid, flowNode); + } + public bool AddCanvasModel(FlowCanvasDetails flowCanvasDetails) + { + ArgumentNullException.ThrowIfNull(flowCanvasDetails); + ArgumentNullException.ThrowIfNull(flowCanvasDetails.Guid); + return FlowCanvass.TryAdd(flowCanvasDetails.Guid, flowCanvasDetails); + } + public bool RemoveNodeModel(IFlowNode flowNode) + { + ArgumentNullException.ThrowIfNull(flowNode.Guid); + return NodeModels.Remove(flowNode.Guid); + } + public bool RemoveCanvasModel(FlowCanvasDetails flowCanvasDetails) + { + ArgumentNullException.ThrowIfNull(flowCanvasDetails.Guid); + return FlowCanvass.Remove(flowCanvasDetails.Guid); + } + + public List GetAllNodeModel() => [.. NodeModels.Values]; + public List GetAllCanvasModel() => [.. FlowCanvass.Values]; + + + + } +} diff --git a/NodeFlow/Services/FlowOperationService.cs b/NodeFlow/Services/FlowOperationService.cs new file mode 100644 index 0000000..ae9fa09 --- /dev/null +++ b/NodeFlow/Services/FlowOperationService.cs @@ -0,0 +1,81 @@ +using Serein.Library.Api; +using Serein.NodeFlow.Model.Operation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Services +{ + public class FlowOperationService + { + private readonly ISereinIOC sereinIOC; + + public FlowOperationService(ISereinIOC sereinIOC) + { + this.sereinIOC = sereinIOC; + } + + /// + /// 撤销栈 + /// + private Stack undoStack = []; + /// + /// 重做栈 + /// + private Stack redoStack = []; + + + /* + // 执行新命令时,将命令推入撤销栈,并清空重做栈 + */ + /// + /// 撤销 + /// + public void Undo() + { + if (undoStack.Count > 0) + { + var command = undoStack.Pop(); + var state = command.Undo(); // 执行撤销 + if (state) + { + redoStack.Push(command); // 将撤销的命令推入重做栈 + + } + } + } + + /// + /// 重做 + /// + public void Redo() + { + if (redoStack.Count > 0) + { + var command = redoStack.Pop(); + var state = command.Execute(); + if (state) + { + undoStack.Push(command); // 将重做的命令推入撤销栈 + } + } + } + + + internal void Execute(IOperation operation) + { + sereinIOC.InjectDependenciesProperty(operation); // 注入所需要的依赖 + var state = operation.Execute(); + if (state) + { + // 执行后,推入撤销栈,并清空重做栈 + undoStack.Push(operation); + redoStack.Clear(); + } + + } + + } +} diff --git a/NodeFlow/FlowWorkManagement.cs b/NodeFlow/Services/FlowWorkManagement.cs similarity index 98% rename from NodeFlow/FlowWorkManagement.cs rename to NodeFlow/Services/FlowWorkManagement.cs index c2ca0b2..7af53f0 100644 --- a/NodeFlow/FlowWorkManagement.cs +++ b/NodeFlow/Services/FlowWorkManagement.cs @@ -2,14 +2,14 @@ using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; -using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Node; using Serein.NodeFlow.Tool; using System; using System.Collections.Concurrent; using System.Threading.Tasks.Dataflow; using System.Xml.Linq; -namespace Serein.NodeFlow +namespace Serein.NodeFlow.Services { /// /// 流程任务管理 @@ -201,8 +201,8 @@ namespace Serein.NodeFlow var pool = WorkOptions.FlowContextPool; var ioc = WorkOptions.Environment.IOC; - var fit = ioc.Get(); - fit.CancelAllTrigger(); // 取消所有中断 + // var fit = ioc.Get(); + // fit.CancelAllTrigger(); // 取消所有中断 foreach (var md in mds) // 结束时 { if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行初始化 diff --git a/NodeFlow/Services/NodeService.cs b/NodeFlow/Services/NodeService.cs deleted file mode 100644 index a1f6391..0000000 --- a/NodeFlow/Services/NodeService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Serein.NodeFlow.Services -{ - internal class NodeService - { - } -} diff --git a/Workbench/Customs/FlowMethodInfoListBox.xaml.cs b/Workbench/Customs/FlowMethodInfoListBox.xaml.cs index 30df405..0630b65 100644 --- a/Workbench/Customs/FlowMethodInfoListBox.xaml.cs +++ b/Workbench/Customs/FlowMethodInfoListBox.xaml.cs @@ -40,7 +40,7 @@ namespace Serein.Workbench.Customs /// /// FlowMethodInfoListBox.xaml 的交互逻辑 /// - public partial class FlowMethodInfoListBox : UserControl,System.ComponentModel.INotifyPropertyChanged + public partial class FlowMethodInfoListBox : UserControl, System.ComponentModel.INotifyPropertyChanged { private object viewMethodInfo; public object ViewMethodInfo diff --git a/Workbench/Node/Junction/JunctionControlBase.cs b/Workbench/Node/Junction/JunctionControlBase.cs index dcb9f1d..eb1fe6e 100644 --- a/Workbench/Node/Junction/JunctionControlBase.cs +++ b/Workbench/Node/Junction/JunctionControlBase.cs @@ -50,7 +50,7 @@ namespace Serein.Workbench.Node.View this.MouseDown += ParamsArg_OnMouseDown; // 增加或删除 this.MouseMove += ParamsArgControl_MouseMove; this.MouseLeave += ParamsArgControl_MouseLeave; - AddOrRemoveParamsTask = AddParamAsync; + AddOrRemoveParamsAction = AddParamAsync; } @@ -112,11 +112,11 @@ namespace Serein.Workbench.Node.View private bool isMouseOver; // 鼠标悬停状态 - private Func AddOrRemoveParamsTask; // 增加或删除参数 + private Action AddOrRemoveParamsAction; // 增加或删除参数 - public async void ParamsArg_OnMouseDown(object sender, MouseButtonEventArgs e) + public void ParamsArg_OnMouseDown(object sender, MouseButtonEventArgs e) { - await AddOrRemoveParamsTask.Invoke(); + AddOrRemoveParamsAction.Invoke(); } private void ParamsArgControl_MouseMove(object sender, MouseEventArgs e) @@ -133,7 +133,7 @@ namespace Serein.Workbench.Node.View // 如果焦点仍在控件上时,则改变点击事件 if (isMouseOver) { - AddOrRemoveParamsTask = RemoveParamAsync; + AddOrRemoveParamsAction = RemoveParamAsync; this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘 } @@ -149,7 +149,7 @@ namespace Serein.Workbench.Node.View private void ParamsArgControl_MouseLeave(object sender, MouseEventArgs e) { isMouseOver = false; - AddOrRemoveParamsTask = AddParamAsync; // 鼠标焦点离开时恢复点击事件 + AddOrRemoveParamsAction = AddParamAsync; // 鼠标焦点离开时恢复点击事件 cts?.Cancel(); this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘 @@ -157,13 +157,13 @@ namespace Serein.Workbench.Node.View - private async Task AddParamAsync() + private void AddParamAsync() { - await this.MyNode.Env.ChangeParameter(MyNode.Guid, true, ArgIndex); + this.MyNode.Env.ChangeParameter(MyNode.Guid, true, ArgIndex); } - private async Task RemoveParamAsync() + private void RemoveParamAsync() { - await this.MyNode.Env.ChangeParameter(MyNode.Guid, false, ArgIndex); + this.MyNode.Env.ChangeParameter(MyNode.Guid, false, ArgIndex); } } diff --git a/Workbench/Node/View/ConnectionControl.cs b/Workbench/Node/View/ConnectionControl.cs index 6d27972..0cbbb30 100644 --- a/Workbench/Node/View/ConnectionControl.cs +++ b/Workbench/Node/View/ConnectionControl.cs @@ -243,11 +243,11 @@ namespace Serein.Workbench.Node.View var jctEnd = End.JunctionType.ToConnectyionType(); if (jct == JunctionOfConnectionType.Invoke) { - env.RemoveConnectInvokeAsync(canvasGuid, Start.MyNode.Guid, End.MyNode.Guid, InvokeType); + env.RemoveInvokeConnect(canvasGuid, Start.MyNode.Guid, End.MyNode.Guid, InvokeType); } else if (jct == JunctionOfConnectionType.Arg) { - env.RemoveConnectArgSourceAsync(canvasGuid,Start.MyNode.Guid, End.MyNode.Guid, ArgIndex) ; + env.RemoveArgSourceConnect(canvasGuid,Start.MyNode.Guid, End.MyNode.Guid, ArgIndex) ; } } diff --git a/Workbench/Node/ViewModel/ActionNodeControlViewModel.cs b/Workbench/Node/ViewModel/ActionNodeControlViewModel.cs index 8325690..38139e4 100644 --- a/Workbench/Node/ViewModel/ActionNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/ActionNodeControlViewModel.cs @@ -1,4 +1,4 @@ -using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Node; using Serein.Workbench.Node.View; namespace Serein.Workbench.Node.ViewModel diff --git a/Workbench/Node/ViewModel/FlipflopNodeControlViewModel.cs b/Workbench/Node/ViewModel/FlipflopNodeControlViewModel.cs index 3ab4e2e..57caa6a 100644 --- a/Workbench/Node/ViewModel/FlipflopNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/FlipflopNodeControlViewModel.cs @@ -1,4 +1,4 @@ -using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Node; using Serein.Workbench.Node.View; namespace Serein.Workbench.Node.ViewModel diff --git a/Workbench/Node/ViewModel/UINodeControlViewModel.cs b/Workbench/Node/ViewModel/UINodeControlViewModel.cs index 01fabea..94904a5 100644 --- a/Workbench/Node/ViewModel/UINodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/UINodeControlViewModel.cs @@ -1,7 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using Serein.Library; using Serein.Library.Api; -using Serein.NodeFlow.Model; +using Serein.NodeFlow.Model.Node; using System; using System.Collections.Generic; using System.Linq; diff --git a/Workbench/Services/FlowEEForwardingService.cs b/Workbench/Services/FlowEEForwardingService.cs index 131fa85..1a0e93c 100644 --- a/Workbench/Services/FlowEEForwardingService.cs +++ b/Workbench/Services/FlowEEForwardingService.cs @@ -27,7 +27,7 @@ namespace Serein.Workbench.Services /// private readonly IFlowEnvironment flowEnvironment; private readonly IFlowEnvironmentEvent flowEnvironmentEvent; - private readonly UIContextOperation uIContextOperation; + private readonly UIContextOperation uiContextOperation; /// /// 转发流程运行环境各个事件的实现类 @@ -41,7 +41,7 @@ namespace Serein.Workbench.Services { this.flowEnvironment = flowEnvironment; this.flowEnvironmentEvent = flowEnvironmentEvent; - this.uIContextOperation = uIContextOperation; + this.uiContextOperation = uIContextOperation; InitFlowEnvironmentEvent(); } @@ -184,7 +184,7 @@ namespace Serein.Workbench.Services /// private void FlowEnvironment_OnEnvOutEvent(InfoType type, string value) { - uIContextOperation.Invoke(() => + uiContextOperation.Invoke(() => { EnvOutput?.Invoke(type, value); }); @@ -245,7 +245,10 @@ namespace Serein.Workbench.Services /// private void FlowEnvironmentEvent_OnCanvasCreate(CanvasCreateEventArgs eventArgs) { - CanvasCreated?.Invoke(eventArgs); + uiContextOperation?.Invoke(() => + { + CanvasCreated?.Invoke(eventArgs); + }); } /// diff --git a/Workbench/Services/FlowNodeService.cs b/Workbench/Services/FlowNodeService.cs index ce75c20..509794f 100644 --- a/Workbench/Services/FlowNodeService.cs +++ b/Workbench/Services/FlowNodeService.cs @@ -656,11 +656,7 @@ namespace Serein.Workbench.Services { int width = 1200; int height = 780; - _ = Task.Run(async () => - { - var result = await flowEnvironment.CreateCanvasAsync("", width, height); - Console.WriteLine(result.Guid); - }); + flowEnvironment.CreateCanvas("", width, height); } /// @@ -673,7 +669,7 @@ namespace Serein.Workbench.Services return; } var model = ((FlowCanvasViewModel)CurrentSelectCanvas.DataContext).Model; - _ = flowEnvironment.RemoveCanvasAsync(model.Guid); + flowEnvironment.RemoveCanvas(model.Guid); } /// @@ -692,7 +688,7 @@ namespace Serein.Workbench.Services { return; } - _ = flowEnvironment.CreateNodeAsync(canvasGuid, nodeType, position, methodDetailsInfo); + flowEnvironment.CreateNode(canvasGuid, nodeType, position, methodDetailsInfo); } /// @@ -711,7 +707,7 @@ namespace Serein.Workbench.Services return; } - _ = flowEnvironment.RemoveNodeAsync(model.CanvasDetails.Guid, model.Guid); + flowEnvironment.RemoveNode(model.CanvasDetails.Guid, model.Guid); } #endregion diff --git a/Workbench/Services/FlowProjectService.cs b/Workbench/Services/FlowProjectService.cs index 7bc033e..bee1c80 100644 --- a/Workbench/Services/FlowProjectService.cs +++ b/Workbench/Services/FlowProjectService.cs @@ -23,6 +23,11 @@ namespace Serein.Workbench.Services this.flowEnvironment = flowEnvironment; } + public void StartProjectManagementServer() + { + // CollabrationSideManagement + } + public void LoadLocalProject(string filePath) { if (File.Exists(filePath)) diff --git a/Workbench/Themes/MethodDetailsControl.xaml.cs b/Workbench/Themes/MethodDetailsControl.xaml.cs index 1be57d8..a25cd1f 100644 --- a/Workbench/Themes/MethodDetailsControl.xaml.cs +++ b/Workbench/Themes/MethodDetailsControl.xaml.cs @@ -123,7 +123,7 @@ namespace Serein.Workbench.Themes private void ExecuteAddParams(object parameter) { // 方法逻辑 - this.MethodDetails.AddParamsArg(); + this.MethodDetails.AddParamsArg(0); } diff --git a/Workbench/ViewModels/MainMenuBarViewModel.cs b/Workbench/ViewModels/MainMenuBarViewModel.cs index 722ac4d..c8f5e8e 100644 --- a/Workbench/ViewModels/MainMenuBarViewModel.cs +++ b/Workbench/ViewModels/MainMenuBarViewModel.cs @@ -8,7 +8,7 @@ namespace Serein.Workbench.ViewModels { public class MainMenuBarViewModel : ObservableObject { - private readonly IFlowEnvironment environment; + private readonly IFlowEnvironment flowEnvironment; private readonly FlowNodeService flowNodeService; private readonly FlowProjectService flowProjectService; @@ -61,12 +61,18 @@ namespace Serein.Workbench.ViewModels public ICommand OpenDynamicCompilerCommand { get; private set; } + /// + /// 开启远程服务 + /// + public ICommand OpenRemoteServerCommand { get; private set; } - public MainMenuBarViewModel(IFlowEnvironment environment, + + + public MainMenuBarViewModel(IFlowEnvironment flowEnvironment, FlowNodeService flowNodeService, FlowProjectService flowProjectService) { - this.environment = environment; + this.flowEnvironment = flowEnvironment; this.flowNodeService = flowNodeService; this.flowProjectService = flowProjectService; SaveProjectCommand = new RelayCommand(SaveProject); // 保存项目 @@ -81,29 +87,51 @@ namespace Serein.Workbench.ViewModels StopCurrentCanvasFlowCommand = new RelayCommand(StopCurrentCanvasFlow); // 停止当前流程 OpenEnvOutWindowCommand = new RelayCommand(OpenEnvOutWindow); // 打开运行输出窗口 - OpenDynamicCompilerCommand = new RelayCommand(OpenDynamicCompiler); // 打开动态编译仓库窗口 + OpenDynamicCompilerCommand = new RelayCommand(OpenDynamicCompiler); // 打开动态编译窗口 + + OpenRemoteServerCommand = new RelayCommand(OpenRemoteServer); // 打开动态编译窗口 this.flowProjectService = flowProjectService; } - private void SaveProject() => environment.SaveProject(); // 保存项目 - private void LoadLocalProject() { + private void SaveProject() => flowEnvironment.SaveProject(); // 保存项目 + private void LoadLocalProject() + { flowProjectService.SelectProjectFile(); //选择项目 } - private void LoadRemoteProject() - { + private void LoadRemoteProject() + { } private void CreateFlowCanvas() => flowNodeService.CreateFlowCanvas(); private void RemoteFlowCanvas() => flowNodeService.RemoveFlowCanvas(); - private void StartUIFlow() => environment.StartFlowAsync([.. flowNodeService.FlowCanvass.Select(c => c.Guid)]); - private void StartCurrentCanvasFlow() => environment.StartFlowAsync([flowNodeService.CurrentSelectCanvas.Guid]); + private void StartUIFlow() + { + var canvass = flowNodeService.FlowCanvass; + if(canvass.Length > 0) + { + string[] guids = [..canvass.Select(c => c.Guid)]; + flowEnvironment.StartFlowAsync(guids); + } + + } + private void StartCurrentCanvasFlow() + { + var canvas = flowNodeService.CurrentSelectCanvas; + if (canvas is null) return; + flowEnvironment.StartFlowAsync([canvas.Guid]); + } private void StopCurrentCanvasFlow() { } private void OpenDynamicCompiler() { } private void OpenEnvOutWindow() => LogWindow.Instance?.Show(); + private void OpenRemoteServer() + { + flowEnvironment.StartRemoteServerAsync(); + } + } } diff --git a/Workbench/Views/FlowCanvasView.xaml.cs b/Workbench/Views/FlowCanvasView.xaml.cs index 2dc0fdf..a7569d4 100644 --- a/Workbench/Views/FlowCanvasView.xaml.cs +++ b/Workbench/Views/FlowCanvasView.xaml.cs @@ -372,7 +372,7 @@ namespace Serein.Workbench.Views if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器 { // 通知运行环境调用加载节点子项的方法 - _ = flowEnvironment.PlaceNodeToContainerAsync(Guid, + flowEnvironment.PlaceNodeToContainer(Guid, nodeControl.ViewModel.NodeModel.Guid, // 待移动的节点 regionControl.ViewModel.NodeModel.Guid); // 目标的容器节点 return; @@ -566,7 +566,8 @@ namespace Serein.Workbench.Views // F5 调试当前选定节点 var nodeModel = selectNodeControls[0].ViewModel.NodeModel; SereinEnv.WriteLine(InfoType.INFO, $"调试运行当前节点:{nodeModel.Guid}"); - _ = nodeModel.StartFlowAsync(new DynamicContext(flowEnvironment), new CancellationToken()); + _ = flowEnvironment.StartFlowFromSelectNodeAsync(nodeModel.Guid); + //_ = nodeModel.StartFlowAsync(new DynamicContext(flowEnvironment), new CancellationToken()); } } @@ -898,7 +899,7 @@ namespace Serein.Workbench.Views { var canvasGuid = this.Guid; - await flowEnvironment.ConnectInvokeNodeAsync( + flowEnvironment.ConnectInvokeNode( canvasGuid, cd.StartJunction.MyNode.Guid, cd.CurrentJunction.MyNode.Guid, @@ -922,7 +923,7 @@ namespace Serein.Workbench.Views } var canvasGuid = this.Guid; - await flowEnvironment.ConnectArgSourceNodeAsync( + flowEnvironment.ConnectArgSourceNode( canvasGuid, cd.StartJunction.MyNode.Guid, cd.CurrentJunction.MyNode.Guid, @@ -1269,7 +1270,7 @@ namespace Serein.Workbench.Views if (!string.IsNullOrEmpty(guid)) { var canvasGuid = this.Guid; - flowEnvironment.RemoveNodeAsync(canvasGuid, guid); + flowEnvironment.RemoveNode(canvasGuid, guid); } } } @@ -1491,10 +1492,10 @@ namespace Serein.Workbench.Views - contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("设为起点", (s, e) => flowEnvironment.SetStartNodeAsync(canvasGuid, nodeGuid))); + contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("设为起点", (s, e) => flowEnvironment.SetStartNode(canvasGuid, nodeGuid))); contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除", async (s, e) => { - var result = await flowEnvironment.RemoveNodeAsync(canvasGuid, nodeGuid); + flowEnvironment.RemoveNode(canvasGuid, nodeGuid); })); #region 右键菜单功能 - 控件对齐 diff --git a/Workbench/Views/MainMenuBarView.xaml b/Workbench/Views/MainMenuBarView.xaml index c941476..56395c6 100644 --- a/Workbench/Views/MainMenuBarView.xaml +++ b/Workbench/Views/MainMenuBarView.xaml @@ -14,7 +14,7 @@ - + @@ -24,19 +24,19 @@ - + - + - + - - --> +