From 7a6f8c407b58c4f31e5ae0cb8b42545672e7f554 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Thu, 26 Dec 2024 16:42:05 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=BA=86=E4=B8=AD=E6=96=AD?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=9A=84=E5=90=8E=E5=8F=B0=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E4=B8=8EUI=E4=BA=A4=E4=BA=92=EF=BC=88=E5=BE=85=E9=87=8D?= =?UTF-8?q?=E5=86=99=EF=BC=89=EF=BC=9B=E9=87=8D=E5=86=99=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E8=8A=82=E7=82=B9=E8=8E=B7=E5=8F=96=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95=EF=BC=9B=E9=87=8D=E5=86=99=E4=BA=86?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E5=AE=B9=E5=99=A8=E7=9A=84=E4=BA=92=E5=8A=A8?= =?UTF-8?q?=EF=BC=9B=E5=AE=8C=E5=96=84=E4=BA=86WebSocket=E8=BF=9C=E7=A8=8B?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=EF=BC=9B=E5=AE=8C=E5=96=84=E4=BA=86=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=96=87=E4=BB=B6=E7=9A=84=E5=8A=A0=E8=BD=BD=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library.Core/FlipflopContext.cs | 1 + Library.Framework/FlipflopContext.cs | 1 + Library/Api/IFlipflopContext.cs | 1 + Library/Api/IFlowEnvironment.cs | 21 +- Library/Api/IFlowTrigger.cs | 12 +- Library/Api/INodeContainer.cs | 4 +- Library/FlowNode/NodeDebugSetting.cs | 5 +- Library/FlowNode/NodeModelBaseFunc.cs | 364 +++-------------- Library/FlowNode/ParameterDetails.cs | 139 ++++++- Library/Network/WebSocket/Handle/Attribute.cs | 2 +- Library/Serein.Library.csproj | 2 + Library/Utils/ChannelFlowInterrupt.cs | 353 ----------------- .../ChannelFlowInterrupt.cs} | 57 +-- .../{ => FlowTrigger}/ChannelFlowTrigger.cs | 6 +- Library/Utils/FlowTrigger/TaskFlowTrigger.cs | 160 ++++++++ Library/Utils/FlowTrigger/TriggerResult.cs | 112 ++++++ Net462DllTest/Trigger/PrakingDevice.cs | 1 + Net462DllTest/Trigger/SiemensPlcDevice.cs | 1 + Net462DllTest/Trigger/ViewManagement.cs | 1 + NodeFlow/Env/FlowEnvironment.cs | 366 ++++++------------ NodeFlow/Env/FlowEnvironmentDecorator.cs | 39 +- NodeFlow/Env/MsgControllerOfClient.cs | 21 + NodeFlow/Env/MsgControllerOfServer.cs | 43 +- NodeFlow/Env/RemoteFlowEnvironment.cs | 59 ++- NodeFlow/FlowStarter.cs | 10 +- NodeFlow/Model/SingleActionNode.cs | 9 - NodeFlow/Model/SingleFlipflopNode.cs | 7 +- NodeFlow/Model/SingleGlobalDataNode.cs | 59 +-- NodeFlow/Model/SingleScriptNode.cs | 2 +- .../ParameterDetailsPropertyGenerator.cs | 2 +- WorkBench/App.xaml.cs | 2 +- WorkBench/MainWindow.xaml.cs | 80 ++-- WorkBench/Themes/NodeTreeViewControl.xaml.cs | 2 +- WorkBench/Themes/ObjectViewerControl.xaml.cs | 22 +- Workbench/Node/INodeContainerControl.cs | 4 +- Workbench/Node/NodeControlBase.cs | 22 +- Workbench/Node/View/GlobalDataControl.xaml.cs | 15 +- 37 files changed, 880 insertions(+), 1127 deletions(-) delete mode 100644 Library/Utils/ChannelFlowInterrupt.cs rename Library/Utils/{TaskFlowTrigger.cs => FlowTrigger/ChannelFlowInterrupt.cs} (79%) rename Library/Utils/{ => FlowTrigger}/ChannelFlowTrigger.cs (98%) create mode 100644 Library/Utils/FlowTrigger/TaskFlowTrigger.cs create mode 100644 Library/Utils/FlowTrigger/TriggerResult.cs diff --git a/Library.Core/FlipflopContext.cs b/Library.Core/FlipflopContext.cs index cabf969..2b59544 100644 --- a/Library.Core/FlipflopContext.cs +++ b/Library.Core/FlipflopContext.cs @@ -1,4 +1,5 @@ using Serein.Library.Api; +using Serein.Library.Utils; namespace Serein.Library.Core { diff --git a/Library.Framework/FlipflopContext.cs b/Library.Framework/FlipflopContext.cs index c3fa51a..c4a9e2c 100644 --- a/Library.Framework/FlipflopContext.cs +++ b/Library.Framework/FlipflopContext.cs @@ -1,4 +1,5 @@ using Serein.Library.Api; +using Serein.Library.Utils; using System; using System.Threading.Tasks; diff --git a/Library/Api/IFlipflopContext.cs b/Library/Api/IFlipflopContext.cs index 10be21e..5931213 100644 --- a/Library/Api/IFlipflopContext.cs +++ b/Library/Api/IFlipflopContext.cs @@ -1,4 +1,5 @@ using Serein.Library; +using Serein.Library.Utils; namespace Serein.Library.Api diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 3da5c7b..81da808 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.Library.Api { @@ -203,7 +202,7 @@ namespace Serein.Library.Api /// /// 移除 /// - Remote, + Remove, } /// @@ -311,9 +310,9 @@ namespace Serein.Library.Api //} /// - /// 节点Model对象,目前需要手动转换对应的类型 + /// 节点Model对象 /// - public object NodeModel { get; private set; } + public NodeModelBase NodeModel { get; private set; } public PositionOfUI Position { get; private set; } //public bool IsAddInRegion { get; private set; } public string RegeionGuid { get; private set; } @@ -934,13 +933,17 @@ namespace Serein.Library.Api void TerminateFlipflopNode(string nodeGuid); - /// + + #region 节点中断、表达式 +#if false + +/// /// 设置节点中断 /// /// 更改中断状态的节点Guid /// 是否中断 /// - Task SetNodeInterruptAsync(string nodeGuid,bool isInterrup); + Task SetNodeInterruptAsync(string nodeGuid, bool isInterrup); /// /// 添加作用于某个对象的中断表达式 @@ -955,7 +958,7 @@ namespace Serein.Library.Api /// /// 需要监视的对象 /// 是否启用监视 - void SetMonitorObjState(string key,bool isMonitor); + void SetMonitorObjState(string key, bool isMonitor); /// /// 检查一个对象是否处于监听状态,如果是,则传出与该对象相关的表达式(用于中断),如果不是,则返回false。 @@ -971,7 +974,9 @@ namespace Serein.Library.Api /// /// /// - Task GetOrCreateGlobalInterruptAsync(); + Task InterruptNode(); +#endif + #endregion /// /// (用于远程)通知节点属性变更 diff --git a/Library/Api/IFlowTrigger.cs b/Library/Api/IFlowTrigger.cs index 33b07bb..844547e 100644 --- a/Library/Api/IFlowTrigger.cs +++ b/Library/Api/IFlowTrigger.cs @@ -1,11 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using Serein.Library.Utils; +using System; using System.Threading.Tasks; namespace Serein.Library.Api { + + + + + + /// /// 触发器接口 /// diff --git a/Library/Api/INodeContainer.cs b/Library/Api/INodeContainer.cs index 5473605..777902d 100644 --- a/Library/Api/INodeContainer.cs +++ b/Library/Api/INodeContainer.cs @@ -15,13 +15,13 @@ namespace Serein.Library.Api /// 放置一个节点 /// /// - void PlaceNode(NodeModelBase nodeModel); + bool PlaceNode(NodeModelBase nodeModel); /// /// 取出一个节点 /// /// - void TakeOutNode(NodeModelBase nodeModel); + bool TakeOutNode(NodeModelBase nodeModel); /// /// 取出所有节点(用于删除容器) diff --git a/Library/FlowNode/NodeDebugSetting.cs b/Library/FlowNode/NodeDebugSetting.cs index e7e3edd..492aa70 100644 --- a/Library/FlowNode/NodeDebugSetting.cs +++ b/Library/FlowNode/NodeDebugSetting.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; -using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.Library { @@ -42,7 +41,7 @@ namespace Serein.Library /// /// 中断级别,暂时停止继续执行后继分支。 /// - [PropertyInfo(IsNotification = true, CustomCodeAtEnd = "NodeModel?.Env?.SetNodeInterruptAsync(NodeModel?.Guid, value);")] // CustomCode = "NodeModel?.Env?.SetNodeInterruptAsync(NodeModel?.Guid, value);" + [PropertyInfo(IsNotification = true, CustomCodeAtEnd = "// NodeModel?.Env?.SetNodeInterruptAsync(NodeModel?.Guid, value);")] // CustomCode = "NodeModel?.Env?.SetNodeInterruptAsync(NodeModel?.Guid, value);" private bool _isInterrupt = false; /// @@ -55,7 +54,7 @@ namespace Serein.Library /// 中断Task(用来中断) /// [PropertyInfo] - private Func> _getInterruptTask; + private Func _getInterruptTask; } diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs index 11407b6..fb8746d 100644 --- a/Library/FlowNode/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -16,7 +16,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; -using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.Library { @@ -366,16 +365,15 @@ namespace Serein.Library /// 节点传回数据对象 public virtual async Task ExecutingAsync(IDynamicContext context) { - if(context.NextOrientation == ConnectionInvokeType.IsError) - { - Console.WriteLine(""); - } + //if(context.NextOrientation == ConnectionInvokeType.IsError) + //{ + //} #region 调试中断 if (DebugSetting.IsInterrupt) // 执行触发检查是否需要中断 { - var cancelType = await this.DebugSetting.GetInterruptTask(); // 等待中断结束 - await Console.Out.WriteLineAsync($"[{this.MethodDetails?.MethodName}]中断已{cancelType},开始执行后继分支"); + //var cancelType = await this.DebugSetting.GetInterruptTask(); // 等待中断结束 + await Console.Out.WriteLineAsync($"[{this.MethodDetails?.MethodName}]中断已取消,开始执行后继分支"); } #endregion @@ -394,7 +392,7 @@ namespace Serein.Library md.ActingInstance = context.Env.IOC.Get(md.ActingInstanceType); } - object[] args = await GetParametersAsync(context, this); + object[] args = await GetParametersAsync(context); var result = await dd.InvokeAsync(md.ActingInstance, args); return result; @@ -403,296 +401,50 @@ namespace Serein.Library /// /// 获取对应的参数数组 /// - public static async Task GetParametersAsync(IDynamicContext context, - NodeModelBase nodeModel) + public async Task GetParametersAsync(IDynamicContext context) { - // 用正确的大小初始化参数数组 - var md = nodeModel.MethodDetails; - if (md.ParameterDetailss.Length == 0) + if (MethodDetails.ParameterDetailss.Length == 0) { - return null;// md.ActingInstance + return new object[0];// md.ActingInstance } - object[] parameters; + object[] args; Array paramsArgs = null; // 初始化可选参数 int paramsArgIndex = 0; // 可选参数下标,与 object[] paramsArgs 一起使用 - - if (md.ParamsArgIndex >= 0) + if (MethodDetails.ParamsArgIndex >= 0) // 存在可变入参参数 { - // 存在可变入参参数 - var paramsArgType = md.ParameterDetailss[md.ParamsArgIndex].DataType; // 获取可变参数的参数类型 - // 可变参数数组长度 = 方法参数个数 - ( 可选入参下标 + 1 ) - int paramsLength = md.ParameterDetailss.Length - md.ParamsArgIndex; + var paramsArgType = MethodDetails.ParameterDetailss[MethodDetails.ParamsArgIndex].DataType; // 获取可变参数的参数类型 + int paramsLength = MethodDetails.ParameterDetailss.Length - MethodDetails.ParamsArgIndex; // 可变参数数组长度 = 方法参数个数 - ( 可选入参下标 + 1 ) paramsArgs = Array.CreateInstance(paramsArgType, paramsLength);// 可变参数 - parameters = new object[md.ParamsArgIndex+1]; // 调用方法的入参数组 - parameters[md.ParamsArgIndex] = paramsArgs; // 如果存在可选参数,入参参数最后一项则为可变参数 + args = new object[MethodDetails.ParamsArgIndex + 1]; // 调用方法的入参数组 + args[MethodDetails.ParamsArgIndex] = paramsArgs; // 如果存在可选参数,入参参数最后一项则为可变参数 } else { // 不存在可选参数 - parameters = new object[md.ParameterDetailss.Length]; // 调用方法的入参数组 + args = new object[MethodDetails.ParameterDetailss.Length]; // 调用方法的入参数组 } - bool hasParams = false; - for (int i = 0; i < md.ParameterDetailss.Length; i++) + for (int i = 0; i < args.Length; i++) { + var pd = MethodDetails.ParameterDetailss[i]; + args[i] = await pd.ToMethodArgData(context); // 获取数据 + } + + if(MethodDetails.ParamsArgIndex >= 0) { - var pd = md.ParameterDetailss[i]; // 方法入参描述 - var argDataType = pd.DataType; - - // 入参参数下标循环到可选参数时,开始写入到可选参数数组 - if (paramsArgs != null && i >= md.ParamsArgIndex) + for (int i = 0; i < paramsArgs.Length; i++) { - // 控制参数赋值方向: - // true => paramsArgs - // false => parameters - hasParams = true; + var pd = MethodDetails.ParameterDetailss[paramsArgIndex + i]; + var data = await pd.ToMethodArgData(context); // 获取数据 + paramsArgs.SetValue(data, i);// 设置到数组中 } - - #region 获取基础的上下文数据 - if (argDataType == typeof(IFlowEnvironment)) // 获取流程上下文 - { - parameters[i] = nodeModel.Env; - continue; - } - if (argDataType == typeof(IDynamicContext)) // 获取流程上下文 - { - parameters[i] = context; - continue; - } - #endregion - - #region 确定[预入参]数据 - object inputParameter; // 存放解析的临时参数 - if (pd.IsExplicitData && !pd.DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase)) // 判断是否使用显示的输入参数 - { - // 使用输入的固定值 - inputParameter = pd.DataValue; - } - else - { - #region (默认的)从运行时上游节点获取其返回值 - if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) - { - var previousNode = context.GetPreviousNode(nodeModel); - if (previousNode is null) - { - inputParameter = null; - } - else - { - inputParameter = context.GetFlowData(previousNode.Guid); // 当前传递的数据 - } - } - #endregion - #region 从指定节点获取其返回值 - else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) - { - // 获取指定节点的数据 - // 如果指定节点没有被执行,会返回null - // 如果执行过,会获取上一次执行结果作为预入参数据 - inputParameter = context.GetFlowData(pd.ArgDataSourceNodeGuid); - } - #endregion - #region 立刻执行指定节点,然后获取返回值 - else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) - { - // 立刻调用对应节点获取数据。 - try - { - var result = await context.Env.InvokeNodeAsync(context, pd.ArgDataSourceNodeGuid); - inputParameter = result; - } - catch (Exception ex) - { - context.NextOrientation = ConnectionInvokeType.IsError; - context.ExceptionOfRuning = ex; - throw; - } - } - #endregion - #region 意料之外的参数 - else - { - throw new Exception("节点执行方法获取入参参数时,ConnectionArgSourceType枚举是意外的枚举值"); - } - #endregion - } - - - - #region 处理 @Get / @DTC 表达式 (Data type conversion) / @Data (全局数据) - if (pd.IsExplicitData) - { - - // @Get 表达式 (从上一节点获取对象) - if (pd.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase)) - { - inputParameter = SerinExpressionEvaluator.Evaluate(pd.DataValue, inputParameter, out _); - } - - // @DTC 表达式 (Data type conversion) - if (pd.DataValue.StartsWith("@dtc", StringComparison.OrdinalIgnoreCase)) - { - inputParameter = SerinExpressionEvaluator.Evaluate(pd.DataValue, inputParameter, out _); - } - - // @Data 表达式 (获取全局数据) - if (pd.DataValue.StartsWith("@data", StringComparison.OrdinalIgnoreCase)) - { - inputParameter = SerinExpressionEvaluator.Evaluate(pd.DataValue, inputParameter, out _); - } - - } - - #endregion - - #region 对于非值类型的null检查 - if (!argDataType.IsValueType && inputParameter is null) - { - parameters[i] = null; - throw new Exception($"[arg{pd.Index}][{pd.Name}][{argDataType}]参数不能为null"); - continue; - } - #endregion - - #endregion - - //#region 入参存在取值转换器,调用对应的转换器获取入参数据,如果获取成功(不为null)会跳过循环 - //if (pd.ExplicitType.IsEnum && !(pd.Convertor is null)) - //{ - // //var resultEnum = Enum.ToObject(ed.ExplicitType, ed.DataValue); - // var resultEnum = Enum.Parse(pd.ExplicitType, pd.DataValue); - // var value = pd.Convertor(resultEnum); - // if (value is null) - // { - // throw new InvalidOperationException("转换器调用失败"); - - // } - // else - // { - // if (hasParams) - // { - // paramsArgs.SetValue(value, paramsArgIndex++); - // // 处理可选参数 - // //paramsArgs[paramsArgIndex++] = value; - // } - // else - // { - // parameters[i] = value; - // } - // continue; - // } - //} - //#endregion - - #region 入参存在基于BinValue的类型转换器,获取枚举转换器中记录的类型,如果获取成功(不为null)会跳过循环 - // 入参存在基于BinValue的类型转换器,获取枚举转换器中记录的类型 - if (pd.ExplicitType.IsEnum && argDataType != pd.ExplicitType) - { - var resultEnum = Enum.Parse(pd.ExplicitType, pd.DataValue); - // 获取绑定的类型 - var type = EnumHelper.GetBoundValue(pd.ExplicitType, resultEnum, attr => attr.Value); - if (type is Type enumBindType && !(enumBindType is null)) - { - var value = nodeModel.Env.IOC.Instantiate(enumBindType); - if (value is null) - { - - } - else - { - if (hasParams) - { - // 处理可选参数 - paramsArgs.SetValue(value, paramsArgIndex++); - //paramsArgs[paramsArgIndex++] = value; - } - else - { - parameters[i] = value; - } - continue; - } - } - } - - #endregion - - #region 对入参数据尝试进行转换 - object tmpVaue = null; // 临时存放数据,最后才判断是否放置可选参数数组 - var inputParameterType = inputParameter.GetType(); - if (inputParameterType == argDataType) - { - tmpVaue = inputParameter; // 类型一致无需转换,直接装入入参数组 - } - else if (argDataType.IsValueType) - { - // 值类型 - var valueStr = inputParameter?.ToString(); - tmpVaue = valueStr.ToValueData(argDataType); // 类型不一致,尝试进行转换,如果转换失败返回类型对应的默认值 - } - else - { - // 引用类型 - if (argDataType == typeof(string)) // 转为字符串 - { - var valueStr = inputParameter?.ToString(); - tmpVaue = valueStr; - } - else if(argDataType.IsSubclassOf(inputParameterType)) // 入参类型 是 预入参数据类型 的 子类/实现类 - { - // 方法入参中,父类不能隐式转为子类,这里需要进行强制转换 - tmpVaue = ObjectConvertHelper.ConvertParentToChild(inputParameter, argDataType); - } - else if(argDataType.IsAssignableFrom(inputParameterType)) // 入参类型 是 预入参数据类型 的 父类/接口 - { - tmpVaue = inputParameter; - } - // 集合类型 - //else if(inputParameter is IEnumerable collection) - //{ - // var enumerableMethods = typeof(Enumerable).GetMethods(); // 获取所有的 Enumerable 扩展方法 - // MethodInfo conversionMethod; - // if (argDataType.IsArray) // 转为数组 - // { - // parameters[i] = inputParameter; - // conversionMethod = enumerableMethods.FirstOrDefault(m => m.Name == "ToArray" && m.IsGenericMethodDefinition); - // } - // else if (argDataType.GetGenericTypeDefinition() == typeof(List<>)) // 转为集合 - // { - // conversionMethod = enumerableMethods.FirstOrDefault(m => m.Name == "ToList" && m.IsGenericMethodDefinition); - // } - // else - // { - // throw new InvalidOperationException("输入对象不是集合或目标类型不支持(目前仅支持Array、List的自动转换)"); - // } - // var genericMethod = conversionMethod.MakeGenericMethod(argDataType); - // var result = genericMethod.Invoke(null, new object[] { collection }); - // parameters[i] = result; - //} - - } - - - if (hasParams) - { - // 处理可选参数 - paramsArgs.SetValue(tmpVaue, paramsArgIndex++); - //paramsArgs[paramsArgIndex++] = tmpVaue; - } - else - { - parameters[i] = tmpVaue; - } - #endregion - + args[args.Length - 1] = paramsArgs; } + - - return parameters; + return args; } - /// /// 更新节点数据,并检查监视表达式是否生效 /// @@ -733,37 +485,37 @@ namespace Serein.Library { return; } - (var isMonitor, var exps) = await context.Env.CheckObjMonitorStateAsync(key); - if (isMonitor) // 如果新的数据处于查看状态,通知UI进行更新?交给运行环境判断? - { - context.Env.MonitorObjectNotification(nodeModel.Guid, data, sourceType); // 对象处于监视状态,通知UI更新数据显示 - if (exps.Length > 0) - { - // 表达式环境下判断是否需要执行中断 - bool isExpInterrupt = false; - string exp = ""; - // 判断执行监视表达式,直到为 true 时退出 - for (int i = 0; i < exps.Length && !isExpInterrupt; i++) - { - exp = exps[i]; - if (string.IsNullOrEmpty(exp)) continue; - // isExpInterrupt = SereinConditionParser.To(data, exp); - } + //(var isMonitor, var exps) = await context.Env.CheckObjMonitorStateAsync(key); + //if (isMonitor) // 如果新的数据处于查看状态,通知UI进行更新?交给运行环境判断? + //{ + // context.Env.MonitorObjectNotification(nodeModel.Guid, data, sourceType); // 对象处于监视状态,通知UI更新数据显示 + // if (exps.Length > 0) + // { + // // 表达式环境下判断是否需要执行中断 + // bool isExpInterrupt = false; + // string exp = ""; + // // 判断执行监视表达式,直到为 true 时退出 + // for (int i = 0; i < exps.Length && !isExpInterrupt; i++) + // { + // exp = exps[i]; + // if (string.IsNullOrEmpty(exp)) continue; + // // isExpInterrupt = SereinConditionParser.To(data, exp); + // } - if (isExpInterrupt) // 触发中断 - { - nodeModel.DebugSetting.IsInterrupt = true; - if (await context.Env.SetNodeInterruptAsync(nodeModel.Guid,true)) - { - context.Env.TriggerInterrupt(nodeModel.Guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Exp); - var cancelType = await nodeModel.DebugSetting.GetInterruptTask(); - await Console.Out.WriteLineAsync($"[{data}]中断已{cancelType},开始执行后继分支"); - nodeModel.DebugSetting.IsInterrupt = false; - } - } - } + // if (isExpInterrupt) // 触发中断 + // { + // nodeModel.DebugSetting.IsInterrupt = true; + // if (await context.Env.SetNodeInterruptAsync(nodeModel.Guid,true)) + // { + // context.Env.TriggerInterrupt(nodeModel.Guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Exp); + // var cancelType = await nodeModel.DebugSetting.GetInterruptTask(); + // await Console.Out.WriteLineAsync($"[{data}]中断已{cancelType},开始执行后继分支"); + // nodeModel.DebugSetting.IsInterrupt = false; + // } + // } + // } - } + //} } ///// diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index ac6f27d..751c7ce 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -1,8 +1,11 @@ using Serein.Library.Api; using Serein.Library.Utils; +using Serein.Library.Utils.SereinExpression; using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Threading.Tasks; namespace Serein.Library { @@ -111,7 +114,6 @@ namespace Serein.Library { } - /// /// 为节点实例化新的入参描述 @@ -178,6 +180,141 @@ namespace Serein.Library return pd; } + /// + /// 转为方法入参数据 + /// + /// + public async ValueTask ToMethodArgData(IDynamicContext context) + { + var nodeModel = NodeModel; + var env = nodeModel.Env; + #region 显然的流程基本类型 + // 返回运行环境 + if (DataType == typeof(IFlowEnvironment)) + { + return env; + } + // 返回流程上下文 + if (DataType == typeof(IDynamicContext)) + { + return context; + } + // 显式设置的参数 + if (IsExplicitData && !DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase)) + { + return DataValue.ToConvert(DataType); // 并非表达式,同时是显式设置的参数 + } + #endregion + #region “枚举-类型”转换器 + if (ExplicitType.IsEnum && DataType != ExplicitType) + { + var resultEnum = Enum.Parse(ExplicitType, DataValue); + // 获取绑定的类型 + var type = EnumHelper.GetBoundValue(ExplicitType, resultEnum, attr => attr.Value); + if (type is Type enumBindType && !(enumBindType is null)) + { + var value = nodeModel.Env.IOC.Instantiate(enumBindType); + return value; + } + } + #endregion + + // 需要获取预入参数据 + object inputParameter; + #region (默认的)从运行时上游节点获取其返回值 + if (ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) + { + var previousNode = context.GetPreviousNode(nodeModel); + if (previousNode is null) + { + inputParameter = null; + } + else + { + inputParameter = context.GetFlowData(previousNode.Guid); // 当前传递的数据 + } + } + #endregion + #region 从指定节点获取其返回值 + else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) + { + // 获取指定节点的数据 + // 如果指定节点没有被执行,会返回null + // 如果执行过,会获取上一次执行结果作为预入参数据 + inputParameter = context.GetFlowData(ArgDataSourceNodeGuid); + } + #endregion + #region 立刻执行指定节点,然后获取返回值 + else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) + { + // 立刻调用对应节点获取数据。 + try + { + var result = await env.InvokeNodeAsync(context, ArgDataSourceNodeGuid); + inputParameter = result; + } + catch (Exception ex) + { + context.NextOrientation = ConnectionInvokeType.IsError; + context.ExceptionOfRuning = ex; + throw; + } + } + #endregion + #region 意料之外的参数 + else + { + throw new Exception("节点执行方法获取入参参数时,ConnectionArgSourceType枚举是意外的枚举值"); + } + #endregion + #region 判断是否执行表达式 + if (IsExplicitData) + { + // @Get 表达式 (从上一节点获取对象) + if (DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase)) + { + inputParameter = SerinExpressionEvaluator.Evaluate(DataValue, inputParameter, out _); + } + + // @DTC 表达式 (Data type conversion) + else if (DataValue.StartsWith("@dtc", StringComparison.OrdinalIgnoreCase)) + { + inputParameter = SerinExpressionEvaluator.Evaluate(DataValue, inputParameter, out _); + } + + // @Data 表达式 (获取全局数据) + else if (DataValue.StartsWith("@data", StringComparison.OrdinalIgnoreCase)) + { + inputParameter = SerinExpressionEvaluator.Evaluate(DataValue, inputParameter, out _); + } + + } + + #endregion + + // 对引用类型检查 null + if (!DataType.IsValueType && inputParameter is null) + { + throw new Exception($"[arg{Index}][{Name}][{DataType}]参数不能为null"); + } + if (DataType == typeof(string)) // 转为字符串 + { + return inputParameter.ToString(); + } + var inputParameterType = inputParameter.GetType(); + if (DataType.IsSubclassOf(inputParameterType)) // 入参类型 是 预入参数据类型 的 子类/实现类 + { + // 方法入参中,父类不能隐式转为子类,这里需要进行强制转换 + return ObjectConvertHelper.ConvertParentToChild(inputParameter, DataType); + } + if (DataType.IsAssignableFrom(inputParameterType)) // 入参类型 是 预入参数据类型 的 父类/接口 + { + return inputParameter; + } + + throw new Exception($"[arg{Index}][{Name}][{DataType}]入参类型不符合,当前预入参类型为{inputParameterType}"); + } + public override string ToString() { return $"[{this.Index}] {this.Name} : {this.ExplicitType?.FullName} -> {this.DataType?.FullName}"; diff --git a/Library/Network/WebSocket/Handle/Attribute.cs b/Library/Network/WebSocket/Handle/Attribute.cs index 421a71a..9dc1ae5 100644 --- a/Library/Network/WebSocket/Handle/Attribute.cs +++ b/Library/Network/WebSocket/Handle/Attribute.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace Serein.Library.Network.WebSocketCommunication.Handle { /// - /// 表示参数可以为空(Net462不能使用NutNull的情况) + /// 表示参数不能为空(Net462不能使用NutNull的情况) /// public sealed class NeedfulAttribute : Attribute { diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index 310bff3..a64f74a 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -38,6 +38,8 @@ + + diff --git a/Library/Utils/ChannelFlowInterrupt.cs b/Library/Utils/ChannelFlowInterrupt.cs deleted file mode 100644 index 9d3ba60..0000000 --- a/Library/Utils/ChannelFlowInterrupt.cs +++ /dev/null @@ -1,353 +0,0 @@ -#region plan 2 -using System; -using System.Collections.Concurrent; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; - -namespace Serein.Library.Utils -{ - /// - /// 流程中断管理 - /// - public class ChannelFlowInterrupt - { - /// - /// 中断取消类型 - /// - public enum CancelType - { - Manual, - Error, - Overtime - } - - // 使用并发字典管理每个信号对应的 Channel - private readonly ConcurrentDictionary> _channels = new ConcurrentDictionary>(); - - /// - /// 创建信号并指定超时时间,到期后自动触发(异步方法) - /// - /// 信号标识符 - /// 超时时间 - /// 等待任务 - public async Task GetCreateChannelWithTimeoutAsync(string signal, TimeSpan outTime) - { - var channel = GetOrCreateChannel(signal); - var cts = new CancellationTokenSource(); - - // 异步任务:超时后自动触发信号 - _ = Task.Run(async () => - { - try - { - await Task.Delay(outTime, cts.Token); - if (!cts.Token.IsCancellationRequested) - { - await channel.Writer.WriteAsync(CancelType.Overtime); - } - } - catch (OperationCanceledException) - { - // 超时任务被取消 - } - finally - { - cts?.Dispose(); - } - }, cts.Token); - - // 等待信号传入(超时或手动触发) - try - { - var result = await channel.Reader.ReadAsync(); - return result; - } - catch - { - return CancelType.Error; - } - - } - - - /// - /// 创建信号,直到手动触发(异步方法) - /// - /// 信号标识符 - /// 等待任务 - public async Task GetOrCreateChannelAsync(string signal) - { - try - { - var channel = GetOrCreateChannel(signal); - // 等待信号传入(超时或手动触发) - var result = await channel.Reader.ReadAsync(); - return result; - } - catch - { - return CancelType.Manual; - } - } - - /// - /// 创建信号并指定超时时间,到期后自动触发(同步阻塞方法) - /// - /// 信号标识符 - /// 超时时间 - public async Task CreateChannelWithTimeoutSync(string signal, TimeSpan timeout) - { - var channel = GetOrCreateChannel(signal); - var cts = new CancellationTokenSource(); - CancellationToken token = cts.Token; - - // 异步任务:超时后自动触发信号 - _ = Task.Run(async () => - { - try - { - await Task.Delay(timeout, token); - await channel.Writer.WriteAsync(CancelType.Overtime); - } - catch (OperationCanceledException ex) - { - // 任务被取消 - await Console.Out.WriteLineAsync(ex.Message); - } - }); - - // 同步阻塞直到信号触发或超时 - var result = await channel.Reader.ReadAsync(); - return result; - - } - - /// - /// 触发信号 - /// - /// 信号字符串 - /// 是否成功触发 - public bool TriggerSignal(string signal) - { - //if (_channels.TryGetValue(signal, out var channel)) - //{ - // // 手动触发信号 - // channel.Writer.TryWrite(CancelType.Manual); - // return true; - //} - //return false; - - - try - { - if (_channels.TryGetValue(signal, out var channel)) - { - // 手动触发信号 - channel.Writer.TryWrite(CancelType.Manual); - - // 完成写入,标记该信号通道不再接受新写入 - channel.Writer.Complete(); - - // 触发后移除信号 - _channels.TryRemove(signal, out _); - - return true; - } - return false; - } - catch - { - - return false; - } - - } - - /// - /// 取消所有任务 - /// - public void CancelAllTasks() - { - foreach (var channel in _channels.Values) - { - try - { - channel.Writer.Complete(); - } - finally - { - - } - } - _channels.Clear(); - } - - /// - /// 获取或创建指定信号的 Channel - /// - /// 信号字符串 - /// 对应的 Channel - private Channel GetOrCreateChannel(string signal) - { - return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded()); - } - } -} - -#endregion - -#region plan 3 - -//using System; -//using System.Collections.Concurrent; -//using System.Threading; -//using System.Threading.Channels; -//using System.Threading.Tasks; - -//namespace Serein.Library.Utils -//{ -// /// -// /// 流程中断管理类,提供了基于 Channel 的异步中断机制 -// /// -// public class ChannelFlowInterrupt -// { -// /// -// /// 中断取消类型 -// /// -// public enum CancelType -// { -// Manual, // 手动触发 -// Overtime, // 超时触发 -// Discard // 丢弃触发 -// } - -// // 使用并发字典管理每个信号对应的 Channel 和状态 -// private readonly ConcurrentDictionary Channel, bool IsCancelled, bool IsDiscardMode)> _channels -// = new ConcurrentDictionary, bool, bool)>(); - -// // 锁对象,用于保护并发访问 -// private readonly object _lock = new object(); - -// /// -// /// 创建带有超时功能的信号,超时后自动触发 -// /// -// public async Task GetCreateChannelWithTimeoutAsync(string signal, TimeSpan outTime) -// { -// var (channel, isCancelled, isDiscardMode) = GetOrCreateChannel(signal); - -// // 如果信号已取消或在丢弃模式下,立即返回丢弃类型 -// if (isCancelled || isDiscardMode) return CancelType.Discard; - -// var cts = new CancellationTokenSource(); - -// _ = Task.Run(async () => -// { -// try -// { -// await Task.Delay(outTime, cts.Token); -// if (!cts.Token.IsCancellationRequested && !isCancelled) -// { -// await channel.Writer.WriteAsync(CancelType.Overtime); -// } -// } -// catch (OperationCanceledException) -// { -// // 处理任务取消的情况 -// } -// finally -// { -// cts.Dispose(); -// } -// }, cts.Token); - -// return await channel.Reader.ReadAsync(); -// } - -// /// -// /// 创建或获取现有信号,等待手动触发 -// /// -// public async Task GetOrCreateChannelAsync(string signal) -// { -// var (channel, isCancelled, isDiscardMode) = GetOrCreateChannel(signal); - -// // 如果信号已取消或在丢弃模式下,立即返回丢弃类型 -// if (isCancelled || isDiscardMode) return CancelType.Discard; - -// return await channel.Reader.ReadAsync(); -// } - -// /// -// /// 触发信号并将其移除 -// /// -// public bool TriggerSignal(string signal) -// { -// lock (_lock) -// { -// if (_channels.TryGetValue(signal, out var channelInfo)) -// { -// var (channel, isCancelled, isDiscardMode) = channelInfo; - -// // 如果信号未被取消,则触发并标记为已取消 -// if (!isCancelled) -// { -// channel.Writer.TryWrite(CancelType.Manual); -// _channels[signal] = (channel, true, false); // 标记为已取消 -// _channels.TryRemove(signal, out _); // 从字典中移除信号 -// return true; -// } -// } -// } -// return false; -// } - -// /// -// /// 启用丢弃模式,所有后续获取的信号将直接返回丢弃类型 -// /// -// /// 信号标识符 -// public void EnableDiscardMode(string signal,bool state = true) -// { -// lock (_lock) -// { -// if (_channels.TryGetValue(signal, out var channelInfo)) -// { -// var (channel, isCancelled, _) = channelInfo; -// _channels[signal] = (channel, isCancelled, state); // 标记为丢弃模式 -// } -// } -// } - -// /// -// /// 取消所有任务 -// /// -// public void CancelAllTasks() -// { -// foreach (var (channel, _, _) in _channels.Values) -// { -// try -// { -// channel.Writer.Complete(); -// } -// catch -// { -// // 忽略完成时的异常 -// } -// } -// _channels.Clear(); -// } - -// /// -// /// 获取或创建指定信号的 Channel 通道 -// /// -// private (Channel, bool, bool) GetOrCreateChannel(string signal) -// { -// lock (_lock) -// { -// return _channels.GetOrAdd(signal, _ => (Channel.CreateUnbounded(), false, false)); -// } -// } -// } -//} - - -#endregion \ No newline at end of file diff --git a/Library/Utils/TaskFlowTrigger.cs b/Library/Utils/FlowTrigger/ChannelFlowInterrupt.cs similarity index 79% rename from Library/Utils/TaskFlowTrigger.cs rename to Library/Utils/FlowTrigger/ChannelFlowInterrupt.cs index e6621ab..85c8919 100644 --- a/Library/Utils/TaskFlowTrigger.cs +++ b/Library/Utils/FlowTrigger/ChannelFlowInterrupt.cs @@ -10,41 +10,15 @@ using System.Threading.Channels; using System.Threading.Tasks; using System.Transactions; -namespace Serein.Library +namespace Serein.Library.Utils { - /// - /// 触发类型 - /// - public enum TriggerDescription - { - /// - /// 外部触发 - /// - External, - /// - /// 超时触发 - /// - Overtime, - /// - /// 触发了,但类型不一致 - /// - TypeInconsistency - } - - - public class TriggerResult - { - public TriggerDescription Type { get; set; } - public TResult Value { get; set; } - } - /// /// 信号触发器类,带有消息广播功能。 /// 使用枚举作为标记,创建 /// - public class TaskFlowTrigger : IFlowTrigger where TSignal : struct, Enum + public class ValueTaskFlowTrigger { // 使用并发字典管理每个信号对应的广播列表 private readonly ConcurrentDictionary>> _subscribers = new ConcurrentDictionary>>(); @@ -73,7 +47,7 @@ namespace Serein.Library } - + /// /// 等待触发器并指定超时的时间 /// @@ -81,7 +55,7 @@ namespace Serein.Library /// 等待信号 /// 超时时间 /// - public async Task> WaitTriggerWithTimeoutAsync(TSignal signal, TimeSpan outTime) + public async ValueTask> WaitTriggerWithTimeoutAsync(TSignal signal, TimeSpan outTime) { var subject = GetOrCreateSubject(signal); var cts = new CancellationTokenSource(); @@ -121,13 +95,14 @@ namespace Serein.Library /// /// /// - public async Task> WaitTriggerAsync(TSignal signal) + public async ValueTask> WaitTriggerAsync(TSignal signal) { + var taskCompletionSource = new TaskCompletionSource>(); var subscription = Subscribe(signal, taskCompletionSource.SetResult); var result = await taskCompletionSource.Task; subscription.Dispose(); // 取消订阅 - if(result.Value is TResult data) + if (result.Value is TResult data) { return new TriggerResult() { @@ -184,23 +159,5 @@ namespace Serein.Library - /// - /// 观察者类,用于包装 Action - /// - public class Observer : IObserver - { - private readonly Action _onNext; - public Observer(Action onNext) - { - _onNext = onNext; - } - - public void OnCompleted() { } - public void OnError(Exception error) { } - public void OnNext(T value) - { - _onNext?.Invoke(value); - } - } } diff --git a/Library/Utils/ChannelFlowTrigger.cs b/Library/Utils/FlowTrigger/ChannelFlowTrigger.cs similarity index 98% rename from Library/Utils/ChannelFlowTrigger.cs rename to Library/Utils/FlowTrigger/ChannelFlowTrigger.cs index d65e1d6..edbf796 100644 --- a/Library/Utils/ChannelFlowTrigger.cs +++ b/Library/Utils/FlowTrigger/ChannelFlowTrigger.cs @@ -1,6 +1,4 @@ - - -using Serein.Library.Api; +using Serein.Library.Api; using System; using System.Collections.Concurrent; using System.Threading; @@ -13,7 +11,7 @@ namespace Serein.Library.Utils - public class ChannelFlowTrigger : IFlowTrigger + public class ChannelFlowTrigger : IFlowTrigger { // 使用并发字典管理每个枚举信号对应的 Channel private readonly ConcurrentDictionary>> _channels = new ConcurrentDictionary>>(); diff --git a/Library/Utils/FlowTrigger/TaskFlowTrigger.cs b/Library/Utils/FlowTrigger/TaskFlowTrigger.cs new file mode 100644 index 0000000..10c1fd8 --- /dev/null +++ b/Library/Utils/FlowTrigger/TaskFlowTrigger.cs @@ -0,0 +1,160 @@ +using Microsoft.Extensions.ObjectPool; +using Newtonsoft.Json.Linq; +using Serein.Library.Api; +using Serein.Library.Utils; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using System.Transactions; + +namespace Serein.Library.Utils +{ + + /// + /// 信号触发器类,带有消息广播功能。 + /// 使用枚举作为标记,创建 + /// + public class TaskFlowTrigger : IFlowTrigger + { + // 使用并发字典管理每个信号对应的广播列表 + private readonly ConcurrentDictionary>> _subscribers = new ConcurrentDictionary>>(); + private readonly TriggerResultPool _triggerResultPool = new TriggerResultPool(); + /// + /// 获取或创建指定信号的 Subject(消息广播者) + /// + /// 枚举信号标识符 + /// 对应的 Subject + private Subject> GetOrCreateSubject(TSignal signal) + { + return _subscribers.GetOrAdd(signal, _ => new Subject>()); + } + + /// + /// 订阅指定信号的消息 + /// + /// 枚举信号标识符 + /// 订阅者 + /// 取消订阅的句柄 + private IDisposable Subscribe(TSignal signal, Action> action) + { + IObserver> observer = new Observer>(action); + var subject = GetOrCreateSubject(signal); + return subject.Subscribe(observer); // 返回取消订阅的句柄 + } + + + + /// + /// 等待触发器并指定超时的时间 + /// + /// 返回值类型 + /// 等待信号 + /// 超时时间 + /// + public async Task> WaitTriggerWithTimeoutAsync(TSignal signal, TimeSpan outTime) + { + var subject = GetOrCreateSubject(signal); + var cts = new CancellationTokenSource(); + + // 超时任务:延迟后触发超时信号 + var timeoutTask = Task.Delay(outTime, cts.Token).ContinueWith(t => + { + if (!cts.Token.IsCancellationRequested) + { + var outResult = _triggerResultPool.Get(); + outResult.Type = TriggerDescription.Overtime; + subject.OnNext(outResult); + subject.OnCompleted(); + } + }, cts.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); + + var result = await WaitTriggerAsync(signal); // 获取触发的结果 + cts.Cancel(); // 取消超时任务 + await timeoutTask; // 确保超时任务完成 + cts.Dispose(); + return result; + + } + + /// + /// 等待触发 + /// + /// + /// + /// + public async Task> WaitTriggerAsync(TSignal signal) + { + var taskCompletionSource = new TaskCompletionSource>(); + var subscription = Subscribe(signal, taskCompletionSource.SetResult); + var result = await taskCompletionSource.Task; + subscription.Dispose(); // 取消订阅 + var result2 = result.Value is TResult data + ? new TriggerResult { Value = data, Type = TriggerDescription.External } + : new TriggerResult { Type = TriggerDescription.TypeInconsistency }; + _triggerResultPool.Return(result); // 将结果归还池中 + return result2; + } + + + /// + /// 手动触发信号,并广播给所有订阅者 + /// + /// 触发类型 + /// 枚举信号标识符 + /// 传递的数据 + /// 是否成功触发 + public Task InvokeTriggerAsync(TSignal signal, TResult value) + { + if (_subscribers.TryGetValue(signal, out var subject)) + { + var result = _triggerResultPool.Get(); + result.Type = TriggerDescription.External; + result.Value = value; + subject.OnNext(result); // 广播给所有订阅者 + subject.OnCompleted(); // 通知订阅结束 + return Task.FromResult(true); + } + return Task.FromResult(false); + } + /// + /// 取消所有任务 + /// + + public void CancelAllTrigger() + { + foreach (var subject in _subscribers.Values) + { + subject.OnCompleted(); // 通知所有订阅者结束 + } + _subscribers.Clear(); + } + } + + + + + /// + /// 观察者类,用于包装 Action + /// + public class Observer : IObserver + { + private readonly Action _onNext; + + public Observer(Action onNext) + { + _onNext = onNext; + } + + public void OnCompleted() { } + public void OnError(Exception error) { } + public void OnNext(T value) + { + _onNext?.Invoke(value); + } + } +} diff --git a/Library/Utils/FlowTrigger/TriggerResult.cs b/Library/Utils/FlowTrigger/TriggerResult.cs new file mode 100644 index 0000000..1b89f19 --- /dev/null +++ b/Library/Utils/FlowTrigger/TriggerResult.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Utils +{ + public class TriggerResult + { + public TriggerDescription Type { get; set; } + public TResult Value { get; set; } + } + + /// + /// 对象池队列 + /// + public class ConcurrentExpandingObjectPool where T : class, new() + { + private readonly ConcurrentQueue _pool; // 存储池中对象的队列 + + public ConcurrentExpandingObjectPool(int initialCapacity) + { + // 初始化对象池,初始容量为 initialCapacity + _pool = new ConcurrentQueue(); + + // 填充初始对象 + for (int i = 0; i < initialCapacity; i++) + { + _pool.Enqueue(new T()); + } + } + + /// + /// 获取一个对象,如果池中没有对象,则动态创建新的对象 + /// + /// 池中的一个对象 + public T Get() + { + // 尝试从池中获取一个对象 + if (!_pool.TryDequeue(out var item)) + { + // 如果池为空,则创建一个新的对象 + item = new T(); + } + + return item; + } + + /// + /// 将一个对象归还到池中 + /// + /// 需要归还的对象 + public void Return(T item) + { + // 将对象归还到池中 + _pool.Enqueue(item); + } + + /// + /// 获取当前池中的对象数 + /// + public int CurrentSize => _pool.Count; + + /// + /// 清空池中的所有对象 + /// + public void Clear() + { + while (_pool.TryDequeue(out _)) { } // 清空队列 + } + } + + /// + /// 使用 ObjectPool 来复用 TriggerResult 对象 + /// + // 示例 TriggerResult 对象池 + public class TriggerResultPool + { + private readonly ConcurrentExpandingObjectPool> _objectPool; + + public TriggerResultPool(int defaultCapacity = 30) + { + _objectPool = new ConcurrentExpandingObjectPool>(defaultCapacity); + } + + public TriggerResult Get() => _objectPool.Get(); + + public void Return(TriggerResult result) => _objectPool.Return(result); + } + + + /// + /// 触发类型 + /// + public enum TriggerDescription + { + /// + /// 外部触发 + /// + External, + /// + /// 超时触发 + /// + Overtime, + /// + /// 触发了,但类型不一致 + /// + TypeInconsistency + } +} diff --git a/Net462DllTest/Trigger/PrakingDevice.cs b/Net462DllTest/Trigger/PrakingDevice.cs index c4ab80c..edf4539 100644 --- a/Net462DllTest/Trigger/PrakingDevice.cs +++ b/Net462DllTest/Trigger/PrakingDevice.cs @@ -1,5 +1,6 @@ using Net462DllTest.LogicControl; using Serein.Library; +using Serein.Library.Utils.FlowTrigger; namespace Net462DllTest.Trigger { diff --git a/Net462DllTest/Trigger/SiemensPlcDevice.cs b/Net462DllTest/Trigger/SiemensPlcDevice.cs index 8d510e6..5d1b5f6 100644 --- a/Net462DllTest/Trigger/SiemensPlcDevice.cs +++ b/Net462DllTest/Trigger/SiemensPlcDevice.cs @@ -8,6 +8,7 @@ using Net462DllTest.Signal; using Net462DllTest.Utils; using Serein.Library; using Serein.Library.Utils; +using Serein.Library.Utils.FlowTrigger; using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Net462DllTest/Trigger/ViewManagement.cs b/Net462DllTest/Trigger/ViewManagement.cs index 5238d7d..077c7c7 100644 --- a/Net462DllTest/Trigger/ViewManagement.cs +++ b/Net462DllTest/Trigger/ViewManagement.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Windows.Forms; using System.Windows.Threading; using Serein.Library.Utils; +using Serein.Library.Utils.FlowTrigger; namespace Net462DllTest.Trigger { diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 1b58346..cb88d7d 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -14,13 +14,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Numerics; +using System.Reactive; using System.Reflection; using System.Reflection.Metadata.Ecma335; using System.Runtime.Loader; using System.Security.AccessControl; using System.Text; using System.Xml.Linq; -using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.NodeFlow.Env { @@ -31,7 +31,7 @@ namespace Serein.NodeFlow.Env /// /// 运行环境 /// - public class FlowEnvironment : IFlowEnvironment, ISereinIOC + public class FlowEnvironment : IFlowEnvironment, ISereinIOC { /// /// 节点的命名空间 @@ -47,7 +47,6 @@ namespace Serein.NodeFlow.Env public FlowEnvironment(UIContextOperation uiContextOperation) { this.sereinIOC = new SereinIOC(); - this.ChannelFlowInterrupt = new ChannelFlowInterrupt(); this.IsGlobalInterrupt = false; this.flowStarter = null; this.sereinIOC.OnIOCMembersChanged += e => @@ -244,10 +243,10 @@ namespace Serein.NodeFlow.Env /// public bool IsGlobalInterrupt { get; set; } - /// - /// 流程中断器 - /// - public ChannelFlowInterrupt ChannelFlowInterrupt { get; set; } + ///// + ///// 流程中断器 + ///// + //public ChannelFlowInterrupt ChannelFlowInterrupt { get; set; } /// /// 单例模式IOC容器,内部维护了一个实例字典,默认使用类型的FullName作为Key,如果以“接口-实现类”的方式注册,那么将使用接口类型的FullName作为Key。 @@ -377,7 +376,6 @@ namespace Serein.NodeFlow.Env /// public async Task StartFlowAsync() { - ChannelFlowInterrupt?.CancelAllTasks(); flowStarter = new FlowStarter(); var nodes = NodeModels.Values.ToList(); @@ -453,7 +451,7 @@ namespace Serein.NodeFlow.Env /// public async Task InvokeNodeAsync(IDynamicContext context, string nodeGuid) { - object result = true; + object result = new Unit(); if (this.NodeModels.TryGetValue(nodeGuid, out var model)) { result = await model.ExecutingAsync(context); @@ -466,7 +464,6 @@ namespace Serein.NodeFlow.Env /// public Task ExitFlowAsync() { - ChannelFlowInterrupt?.CancelAllTasks(); flowStarter?.Exit(); UIContextOperation?.Invoke(() => OnFlowRunComplete?.Invoke(new FlowEventArgs())); flowStarter = null; @@ -555,8 +552,11 @@ namespace Serein.NodeFlow.Env LoadLibrary(dllFilePath); // 加载项目文件时加载对应的程序集 } - _ = LoadNodeInfosAsync(projectData.Nodes.ToList()); - SetStartNodeAsync(projectData.StartNode); + _ = Task.Run( async () => + { + await LoadNodeInfosAsync(projectData.Nodes.ToList()); + await SetStartNodeAsync(projectData.StartNode); + }); } @@ -745,7 +745,7 @@ namespace Serein.NodeFlow.Env /// /// 节点信息 /// - public Task LoadNodeInfosAsync(List nodeInfos) + public async Task LoadNodeInfosAsync(List nodeInfos) { #region 从NodeInfo创建NodeModel foreach (NodeInfo? nodeInfo in nodeInfos) @@ -779,7 +779,7 @@ namespace Serein.NodeFlow.Env nodeModel.LoadInfo(nodeInfo); // 创建节点model TryAddNode(nodeModel); // 加载项目时将节点加载到环境中 - UIContextOperation?.Invoke(() => + await UIContextOperation.InvokeAsync(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position))); // 添加到UI上 } #endregion @@ -798,77 +798,92 @@ namespace Serein.NodeFlow.Env foreach (NodeInfo nodeInfo in needPlaceNodeInfos) { - if (NodeModels.TryGetValue(nodeInfo.Guid, out var nodeMoel) && + if (NodeModels.TryGetValue(nodeInfo.Guid, out var nodeModel) && NodeModels.TryGetValue(nodeInfo.ParentNodeGuid, out var containerNode) && containerNode is INodeContainer nodeContainer) { - nodeMoel.ContainerNode = containerNode; // 放置节点 - containerNode.ChildrenNode.Add(nodeMoel); - nodeContainer.PlaceNode(nodeMoel); - - UIContextOperation?.Invoke(() => OnNodePlace?.Invoke( - new NodePlaceEventArgs(nodeMoel.Guid, containerNode.Guid))); + var result = nodeContainer.PlaceNode(nodeModel); + if (result) + { + await UIContextOperation.InvokeAsync(() => OnNodePlace?.Invoke( + new NodePlaceEventArgs(nodeModel.Guid, containerNode.Guid))); + } + } } #endregion - _ = Task.Run(async () => + #region 确定节点之间的方法调用关系 + foreach (var nodeInfo in nodeInfos) { - await Task.Delay(100); - #region 确定节点之间的方法调用关系 - foreach (var nodeInfo in nodeInfos) - { - if (!NodeModels.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode)) - { - // 不存在对应的起始节点 - continue; - } - List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes), + var fromNodeModel = GuidToModel(nodeInfo.Guid); + 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)]; - - 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 fromNodes) - { - // 遍历当前类型分支的节点(确认连接关系) - foreach (var toNode in item.toNodes) - { - _ = ConnectInvokeOfNode(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系 - } - } - } - #endregion - - #region 确定节点之间的参数调用关系 - foreach (var toNode in NodeModels.Values) + foreach ((ConnectionInvokeType connectionType, string[] toNodeGuids) item in allToNodes) { - if (toNode.MethodDetails.ParameterDetailss == null) + // 遍历当前类型分支的节点(确认连接关系) + foreach (var toNodeGuid in item.toNodeGuids) { - 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)) - { - - _ = ConnectArgSourceOfNodeAsync(fromNode, toNode, pd.ArgDataSourceType, pd.Index); - } + var toNodeModel = GuidToModel(toNodeGuid); + if (toNodeModel is null) continue; + var isSuccessful = ConnectInvokeOfNode(fromNodeModel, toNodeModel, item.connectionType); // 加载时确定节点间的连接关系 } } - #endregion - }); - UIContextOperation?.Invoke(() => OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs())); - return Task.CompletedTask; + + //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) + { + 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)) + { + + _ = ConnectArgSourceOfNodeAsync(fromNode, toNode, pd.ArgDataSourceType, pd.Index); + } + } + } + #endregion + + + await UIContextOperation.InvokeAsync(() => + { + UIContextOperation?.Invoke(() => OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs())); + }); + + return; } /// @@ -925,24 +940,28 @@ namespace Serein.NodeFlow.Env public async Task PlaceNodeToContainerAsync(string nodeGuid, string containerNodeGuid) { // 获取目标节点与容器节点 - var nodeMoel = GuidToModel(nodeGuid); - if (nodeMoel is null ) return false; + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null ) return false; - if(nodeMoel.ContainerNode is INodeContainer tmpContainer) + if(nodeModel.ContainerNode is INodeContainer tmpContainer) { SereinEnv.WriteLine(InfoType.WARN, $"节点放置失败,节点[{nodeGuid}]已经放置于容器节点[{((NodeModelBase)tmpContainer).Guid}]"); return false; } - var containerNode = GuidToModel(containerNodeGuid); + var containerNode = GuidToModel(containerNodeGuid); // 获取容器节点 if (containerNode is not INodeContainer nodeContainer) return false; - nodeMoel.ContainerNode = containerNode; // 放置节点 - containerNode.ChildrenNode.Add(nodeMoel); - nodeContainer.PlaceNode(nodeMoel); - OnNodePlace?.Invoke(new NodePlaceEventArgs(nodeGuid, containerNodeGuid)); // 通知UI更改节点放置位置 - return true; - + var result = nodeContainer.PlaceNode(nodeModel); // 放置在容器节点 + if (result) + { + _ = UIContextOperation?.InvokeAsync(() => + { + OnNodePlace?.Invoke(new NodePlaceEventArgs(nodeGuid, containerNodeGuid)); // 通知UI更改节点放置位置 + }); + } + return result; + } /// @@ -952,18 +971,24 @@ namespace Serein.NodeFlow.Env public async Task TakeOutNodeToContainerAsync(string nodeGuid) { // 获取目标节点与容器节点 - var nodeMoel = GuidToModel(nodeGuid); - if (nodeMoel is null) return false; + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null) return false; - if(nodeMoel.ContainerNode is not INodeContainer nodeContainer) + if(nodeModel.ContainerNode is not INodeContainer nodeContainer) { return false; } - nodeContainer.TakeOutNode(nodeMoel); // 从容器节点取出 - nodeMoel.ContainerNode = null; // 取消映射关系 - - OnNodeTakeOut?.Invoke(new NodeTakeOutEventArgs(nodeGuid)); // 重新放置在画布上 - return true; + var result = nodeContainer.TakeOutNode(nodeModel); // 从容器节点取出 + if (result) + { + _ = UIContextOperation?.InvokeAsync(() => + { + OnNodeTakeOut?.Invoke(new NodeTakeOutEventArgs(nodeGuid)); // 重新放置在画布上 + }); + } + return result; + + } @@ -1000,7 +1025,7 @@ namespace Serein.NodeFlow.Env remoteNode.Guid, JunctionOfConnectionType.Invoke, pCType, // 对应的连接关系 - NodeConnectChangeEventArgs.ConnectChangeType.Remote))); // 通知UI + NodeConnectChangeEventArgs.ConnectChangeType.Remove))); // 通知UI } } @@ -1240,149 +1265,6 @@ namespace Serein.NodeFlow.Env return Task.FromResult(StartNode?.Guid ?? string.Empty); } - /// - /// 中断指定节点,并指定中断等级。 - /// - /// 被中断的目标节点Guid - /// 中断级别 - /// 操作是否成功 - public Task SetNodeInterruptAsync(string nodeGuid, bool isInterrupt) - { - - - var nodeModel = GuidToModel(nodeGuid); - if (nodeModel is null) - return Task.FromResult(false); - if (!isInterrupt) - { - nodeModel.CancelInterrupt(); - } - else if (isInterrupt) - { - nodeModel.DebugSetting.CancelInterruptCallback?.Invoke(); - nodeModel.DebugSetting.GetInterruptTask = async () => - { - TriggerInterrupt(nodeGuid, "", InterruptTriggerEventArgs.InterruptTriggerType.Monitor); - var result = await ChannelFlowInterrupt.GetOrCreateChannelAsync(nodeGuid); - return result; - }; - nodeModel.DebugSetting.CancelInterruptCallback = () => - { - //nodeModel.DebugSetting.IsInterrupt = false; - ChannelFlowInterrupt.TriggerSignal(nodeGuid); - }; - - } - - //nodeModel.DebugSetting.IsInterrupt = true; - if (OperatingSystem.IsWindows()) - { - - UIContextOperation?.Invoke(() => OnNodeInterruptStateChange?.Invoke(new NodeInterruptStateChangeEventArgs(nodeGuid, isInterrupt))); - } - - return Task.FromResult(true); - } - - - /// - /// 添加表达式中断 - /// - /// 如果是节点,传入Guid;如果是对象,传入类型FullName - /// 合法的条件表达式 - /// - public Task AddInterruptExpressionAsync(string key, string expression) - { - if (string.IsNullOrEmpty(expression)) return Task.FromResult(false); - if (dictMonitorObjExpInterrupt.TryGetValue(key, out var condition)) - { - condition.Clear(); // 暂时 - condition.Add(expression);// 暂时 - } - else - { - var exps = new List(); - exps.Add(expression); - dictMonitorObjExpInterrupt.TryAdd(key, exps); - } - return Task.FromResult(true); - } - - /// - /// 要监视的对象,以及与其关联的表达式 - /// - private ConcurrentDictionary> dictMonitorObjExpInterrupt = []; - - /// - /// 设置对象的监视状态 - /// - /// 如果是节点,传入Guid;如果是对象,传入类型FullName - /// ture监视对象;false取消对象监视 - /// - public void SetMonitorObjState(string key, bool isMonitor) - { - if (string.IsNullOrEmpty(key)) { return; } - var isExist = dictMonitorObjExpInterrupt.ContainsKey(key); - if (isExist) - { - if (!isMonitor) // 对象存在且需要不监视 - { - dictMonitorObjExpInterrupt.Remove(key, out _); - } - } - else - { - if (isMonitor) // 对象不存在且需要监视,添加在集合中。 - { - dictMonitorObjExpInterrupt.TryAdd(key, new List()); ; - } - } - } - - /// - /// 检查一个对象是否处于监听状态,如果是,则传出与该对象相关的表达式(用于中断),如果不是,则返回false。 - /// - /// - /// - /// - public Task<(bool, string[])> CheckObjMonitorStateAsync(string key) - { - if (string.IsNullOrEmpty(key)) - { - var data = (false, Array.Empty()); - return Task.FromResult(data); - } - else - { - var isMonitor = dictMonitorObjExpInterrupt.TryGetValue(key, out var exps); - - if (exps is null) - { - var data = (isMonitor, Array.Empty()); - return Task.FromResult(data); - } - else - { - var data = (isMonitor, exps.ToArray()); - return Task.FromResult(data); - } - - - } - - //if (exps is null) - //{ - // var data = (isMonitor, Array.Empty()); - // return Task.FromResult(data); - //} - //else - //{ - // var data = (isMonitor, exps.ToArray()); - // return Task.FromResult(data); - //} - - } - /// /// 启动器调用,运行到某个节点时触发了监视对象的更新(对象预览视图将会自动更新) /// @@ -1406,16 +1288,16 @@ namespace Serein.NodeFlow.Env } - /// - /// 环境执行中断 - /// - /// - public async Task GetOrCreateGlobalInterruptAsync() - { - IsGlobalInterrupt = true; - var result = await ChannelFlowInterrupt.GetOrCreateChannelAsync(EnvName); - return result; - } + ///// + ///// 环境执行中断 + ///// + ///// + //public async Task InterruptNode() + //{ + // IsGlobalInterrupt = true; + // var result = await ChannelFlowInterrupt.GetOrCreateChannelAsync(EnvName); + // return result; + //} /// /// 记录节点更改数据,防止重复更改 @@ -1642,7 +1524,7 @@ namespace Serein.NodeFlow.Env toNode.Guid, JunctionOfConnectionType.Invoke, connectionType, - NodeConnectChangeEventArgs.ConnectChangeType.Remote))); + NodeConnectChangeEventArgs.ConnectChangeType.Remove))); } return true; } @@ -1670,7 +1552,7 @@ namespace Serein.NodeFlow.Env JunctionOfConnectionType.Arg, argIndex, ConnectionArgSourceType.GetPreviousNodeData, - NodeConnectChangeEventArgs.ConnectChangeType.Remote))); + NodeConnectChangeEventArgs.ConnectChangeType.Remove))); } return true; } diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs index 405721a..3e10fee 100644 --- a/NodeFlow/Env/FlowEnvironmentDecorator.cs +++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs @@ -196,16 +196,7 @@ namespace Serein.NodeFlow.Env currentFlowEnvironment.ActivateFlipflopNode(nodeGuid); } - public async Task AddInterruptExpressionAsync(string key, string expression) - { - return await currentFlowEnvironment.AddInterruptExpressionAsync(key, expression); - } - - - public async Task<(bool, string[])> CheckObjMonitorStateAsync(string key) - { - return await currentFlowEnvironment.CheckObjMonitorStateAsync(key); - } + /// @@ -329,11 +320,7 @@ namespace Serein.NodeFlow.Env return await currentFlowEnvironment.GetEnvInfoAsync(); } - public async Task GetOrCreateGlobalInterruptAsync() - { - return await currentFlowEnvironment.GetOrCreateGlobalInterruptAsync(); - } - + public async Task GetProjectInfoAsync() { return await currentFlowEnvironment.GetProjectInfoAsync(); @@ -439,6 +426,24 @@ namespace Serein.NodeFlow.Env currentFlowEnvironment.WriteLine(type, message, @class); } + + #region MyRegion +#if false + public async Task AddInterruptExpressionAsync(string key, string expression) + { + return await currentFlowEnvironment.AddInterruptExpressionAsync(key, expression); + } + + + public async Task<(bool, string[])> CheckObjMonitorStateAsync(string key) + { + return await currentFlowEnvironment.CheckObjMonitorStateAsync(key); + } + public async Task GetOrCreateGlobalInterruptAsync() + { + return await currentFlowEnvironment.InterruptNode(); + } + public void SetMonitorObjState(string key, bool isMonitor) { currentFlowEnvironment.SetMonitorObjState(key, isMonitor); @@ -447,8 +452,10 @@ namespace Serein.NodeFlow.Env public async Task SetNodeInterruptAsync(string nodeGuid, bool isInterrupt) { return await currentFlowEnvironment.SetNodeInterruptAsync(nodeGuid, isInterrupt); - } + } +#endif + #endregion public async Task SetStartNodeAsync(string nodeGuid) { return await currentFlowEnvironment.SetStartNodeAsync(nodeGuid); diff --git a/NodeFlow/Env/MsgControllerOfClient.cs b/NodeFlow/Env/MsgControllerOfClient.cs index e2aac6f..8eeed44 100644 --- a/NodeFlow/Env/MsgControllerOfClient.cs +++ b/NodeFlow/Env/MsgControllerOfClient.cs @@ -188,6 +188,27 @@ namespace Serein.NodeFlow.Env { _ = remoteFlowEnvironment.InvokeTriggerAsync(msgId, state); } + /// + /// 放置节点 + /// + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.PlaceNode, IsReturnValue = false)] + public void PlaceNode([UseMsgId] string msgId, bool state) + { + _ = remoteFlowEnvironment.InvokeTriggerAsync(msgId, state); + } + /// + /// 取出节点 + /// + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.TakeOutNode, IsReturnValue = false)] + public void TakeOutNode([UseMsgId] string msgId, bool state) + { + _ = remoteFlowEnvironment.InvokeTriggerAsync(msgId, state); + } + /// /// 创建节点之间的调用关系 diff --git a/NodeFlow/Env/MsgControllerOfServer.cs b/NodeFlow/Env/MsgControllerOfServer.cs index 4f59b6c..3487a6a 100644 --- a/NodeFlow/Env/MsgControllerOfServer.cs +++ b/NodeFlow/Env/MsgControllerOfServer.cs @@ -4,6 +4,7 @@ using Serein.Library.Api; using Serein.Library.Network.WebSocketCommunication; using Serein.Library.Network.WebSocketCommunication.Handle; using Serein.Library.Utils; +using System.Diagnostics.CodeAnalysis; namespace Serein.NodeFlow.Env { @@ -267,7 +268,6 @@ namespace Serein.NodeFlow.Env } - /// /// 退出远程环境 /// @@ -298,6 +298,7 @@ namespace Serein.NodeFlow.Env public void LoadDll(string dllPath) { } + /// /// 移除DLL /// @@ -334,9 +335,30 @@ namespace Serein.NodeFlow.Env [AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveNode)] public async Task RemoveNode(string nodeGuid) { - //var result = environment.RemoveNodeAsync(nodeGuid).GetAwaiter().GetResult(); var result = await environment.RemoveNodeAsync(nodeGuid); - //return result; + return new { state = result }; + } + /// + /// 远程从远程环境移除节点 + /// + /// + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.PlaceNode)] + public async Task PlaceNode(string nodeGuid, string containerNodeGuid) + { + var result = await environment.PlaceNodeToContainerAsync(nodeGuid, containerNodeGuid); + return new { state = result }; + } + /// + /// 远程从远程环境移除节点 + /// + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.TakeOutNode)] + public async Task TakeOutNode(string nodeGuid) + { + var result = await environment.TakeOutNodeToContainerAsync(nodeGuid); return new { state = result }; } @@ -528,12 +550,13 @@ namespace Serein.NodeFlow.Env /// /// [AutoSocketHandle(ThemeValue = EnvMsgTheme.SetStartNode)] - public void SetStartNode(string nodeGuid) + public async Task SetStartNode([NotNull]string nodeGuid) { - environment.SetStartNodeAsync(nodeGuid); + return await environment.SetStartNodeAsync(nodeGuid); } +#if false /// /// 中断指定节点,并指定中断等级。 @@ -544,10 +567,9 @@ namespace Serein.NodeFlow.Env [AutoSocketHandle(ThemeValue = EnvMsgTheme.SetNodeInterrupt)] public async Task SetNodeInterruptAsync(string nodeGuid, bool isInterrupt) { - - - return await this.environment.SetNodeInterruptAsync(nodeGuid, isInterrupt); - + return false; + // return await this.environment.SetNodeInterruptAsync(nodeGuid, isInterrupt); + } @@ -573,7 +595,8 @@ namespace Serein.NodeFlow.Env public void SetMonitorObjState(string key, bool isMonitor) { environment.SetMonitorObjState(key, isMonitor); - } + } +#endif /// diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs index d25c371..8893c6b 100644 --- a/NodeFlow/Env/RemoteFlowEnvironment.cs +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -12,8 +12,6 @@ using System.Threading.Channels; namespace Serein.NodeFlow.Env { - - /// /// 远程流程环境 /// @@ -638,7 +636,7 @@ namespace Serein.NodeFlow.Env toNodeGuid, JunctionOfConnectionType.Invoke, invokeType, - NodeConnectChangeEventArgs.ConnectChangeType.Remote)); + NodeConnectChangeEventArgs.ConnectChangeType.Remove)); }); } return result; @@ -667,7 +665,7 @@ namespace Serein.NodeFlow.Env JunctionOfConnectionType.Arg, argIndex, ConnectionArgSourceType.GetPreviousNodeData, - NodeConnectChangeEventArgs.ConnectChangeType.Remote)); // 通知UI + NodeConnectChangeEventArgs.ConnectChangeType.Remove)); // 通知UI }); } return result; @@ -819,7 +817,20 @@ namespace Serein.NodeFlow.Env }); if (isSuuccess) { - OnNodePlace?.Invoke(new NodePlaceEventArgs(nodeGuid, containerNodeGuid)); // 通知UI更改节点放置位置 + var nodeModel = GuidToModel(nodeGuid); // 获取目标节点 + if (nodeModel is null) return false; + var containerNode = GuidToModel(containerNodeGuid); // 获取容器节点 + if (containerNode is not INodeContainer nodeContainer) return false; + var result = nodeContainer.PlaceNode(nodeModel); + if (result) + { + // 通知UI更改 + UIContextOperation.Invoke(() => + { + OnNodePlace?.Invoke(new NodePlaceEventArgs(nodeGuid, containerNodeGuid)); // 通知UI更改节点放置位置 + }); + } + return result; } return isSuuccess; } @@ -836,7 +847,22 @@ namespace Serein.NodeFlow.Env }); if (isSuuccess) { - OnNodeTakeOut?.Invoke(new NodeTakeOutEventArgs(nodeGuid)); // 重新放置在画布上 + var nodeModel = GuidToModel(nodeGuid); // 获取目标节点 + if (nodeModel is null) return false; + if (nodeModel.ContainerNode is not INodeContainer nodeContainer) + { + return false; + } + var result = nodeContainer.TakeOutNode(nodeModel); // 从容器节点取出 + if (result) + { + // 通知UI更改 + UIContextOperation.Invoke(() => + { + OnNodeTakeOut?.Invoke(new NodeTakeOutEventArgs(nodeGuid)); // 重新放置在画布上 + }); + } + return result; } return isSuuccess; } @@ -1027,7 +1053,20 @@ namespace Serein.NodeFlow.Env #region 私有方法 - + private NodeModelBase? GuidToModel(string nodeGuid) + { + if (string.IsNullOrEmpty(nodeGuid)) + { + //throw new ArgumentNullException("not contains - Guid没有对应节点:" + (nodeGuid)); + return null; + } + if (!NodeModels.TryGetValue(nodeGuid, out NodeModelBase? nodeModel) || nodeModel is null) + { + //throw new ArgumentNullException("null - Guid存在对应节点,但节点为null:" + (nodeGuid)); + return null; + } + return nodeModel; + } private bool TryAddNode(NodeModelBase nodeModel) { NodeModels[nodeModel.Guid] = nodeModel; @@ -1190,11 +1229,7 @@ namespace Serein.NodeFlow.Env return null; } - public async Task GetOrCreateGlobalInterruptAsync() - { - this.WriteLine(InfoType.INFO, "远程环境尚未实现的接口:GetOrCreateGlobalInterruptAsync"); - return ChannelFlowInterrupt.CancelType.Error; - } + public bool TryGetMethodDetailsInfo(string libraryName, string methodName, out MethodDetailsInfo mdInfo) { diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index aa8b01f..f82a05d 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -403,8 +403,8 @@ namespace Serein.NodeFlow context.SetPreviousNode(nextNodes[i], singleFlipFlopNode); if (nextNodes[i].DebugSetting.IsInterrupt) // 执行触发前 { - var cancelType = await nextNodes[i].DebugSetting.GetInterruptTask(); - await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支"); + await nextNodes[i].DebugSetting.GetInterruptTask(); + await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已取消,开始执行后继分支"); } await nextNodes[i].StartFlowAsync(context); // 启动执行触发器后继分支的节点 } @@ -421,8 +421,8 @@ namespace Serein.NodeFlow context.SetPreviousNode(nextNodes[i], singleFlipFlopNode); if (nextNodes[i].DebugSetting.IsInterrupt) // 执行触发前 { - var cancelType = await nextNodes[i].DebugSetting.GetInterruptTask(); - await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支"); + await nextNodes[i].DebugSetting.GetInterruptTask(); + await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已取消,开始执行后继分支"); } await nextNodes[i].StartFlowAsync(context); // 启动执行触发器后继分支的节点 } @@ -442,7 +442,7 @@ namespace Serein.NodeFlow catch (Exception ex) { SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{singleFlipFlopNode.Guid}]异常。"+ ex.Message); - await Task.Delay(100); + await Task.Delay(1000); } } diff --git a/NodeFlow/Model/SingleActionNode.cs b/NodeFlow/Model/SingleActionNode.cs index edb2031..496b1fb 100644 --- a/NodeFlow/Model/SingleActionNode.cs +++ b/NodeFlow/Model/SingleActionNode.cs @@ -14,14 +14,5 @@ namespace Serein.NodeFlow.Model } - /// - /// 执行方法 - /// - /// - /// - public override Task ExecutingAsync(IDynamicContext context) - { - return base.ExecutingAsync(context); - } } } diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index 76cc72d..1f7c764 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -1,7 +1,6 @@ using Serein.Library.Api; using Serein.Library; using Serein.Library.Utils; -using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.NodeFlow.Model { @@ -28,8 +27,8 @@ namespace Serein.NodeFlow.Model if (DebugSetting.IsInterrupt) // 执行触发前 { string guid = this.Guid.ToString(); - var cancelType = await this.DebugSetting.GetInterruptTask(); - await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支"); + await this.DebugSetting.GetInterruptTask(); + await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已取消,开始执行后继分支"); } #endregion @@ -40,7 +39,7 @@ namespace Serein.NodeFlow.Model } object instance = md.ActingInstance; - var args = await GetParametersAsync(context, this); + var args = await GetParametersAsync(context); // 因为这里会返回不确定的泛型 IFlipflopContext // 而我们只需要获取到 State 和 Value(返回的数据) // 所以使用 dynamic 类型接收 diff --git a/NodeFlow/Model/SingleGlobalDataNode.cs b/NodeFlow/Model/SingleGlobalDataNode.cs index 94031ed..6860f90 100644 --- a/NodeFlow/Model/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/SingleGlobalDataNode.cs @@ -23,7 +23,7 @@ namespace Serein.NodeFlow.Model /// /// 表达式 /// - [PropertyInfo(IsNotification = true, CustomCodeAtStart = "ChangeName(value);")] + [PropertyInfo(IsNotification = true, CustomCodeAtStart = "// ChangeName(value);")] private string _keyName; } @@ -52,42 +52,51 @@ namespace Serein.NodeFlow.Model private NodeModelBase? DataNode; - public void PlaceNode(NodeModelBase nodeModel) + public bool PlaceNode(NodeModelBase nodeModel) { - // 全局数据节点只有一个子控件 - if (DataNode is not null) + if(DataNode is null) { - _ = Task.Run(async () => - { - await this.Env.RemoveNodeAsync(DataNode?.Guid); - DataNode = nodeModel; - }); + // 放置节点 + nodeModel.ContainerNode = this; + ChildrenNode.Add(nodeModel); + DataNode = nodeModel; + return true; } else { - DataNode = nodeModel; + // 全局数据节点只有一个子控件 + return false; } + + } - public void TakeOutAll() + + public bool TakeOutNode(NodeModelBase nodeModel) { + if (ChildrenNode.Contains(nodeModel)) + { + ChildrenNode.Remove(nodeModel); + nodeModel.ContainerNode = null; + DataNode = null; + return true; + } + else + { + return false; + } + + } + + public async void TakeOutAll() + { + foreach (var nodeModel in ChildrenNode) + { + await nodeModel.Env.TakeOutNodeToContainerAsync(nodeModel.Guid); + } DataNode = null; } - public void TakeOutNode(NodeModelBase nodeModel) - { - DataNode = null; - } - - /// - /// 设置数据节点 - /// - /// - //public void SetDataNode(NodeModelBase dataNode) - //{ - // DataNodeGuid = dataNode.Guid; - //} - private void ChangeName(string newName) { if(SereinEnv.GetFlowGlobalData(_keyName) == null) diff --git a/NodeFlow/Model/SingleScriptNode.cs b/NodeFlow/Model/SingleScriptNode.cs index 1a03c54..b1ab425 100644 --- a/NodeFlow/Model/SingleScriptNode.cs +++ b/NodeFlow/Model/SingleScriptNode.cs @@ -140,7 +140,7 @@ namespace Serein.NodeFlow.Model /// public override async Task ExecutingAsync(IDynamicContext context) { - var @params = await NodeModelBase.GetParametersAsync(context, this); + var @params = await GetParametersAsync(context); ScriptFlowApi.Context= context; context.AddOrUpdate($"{context.Guid}_{this.Guid}_Params", @params[0]); // 后面再改 diff --git a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs index f66c057..db42a7f 100644 --- a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs +++ b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs @@ -125,7 +125,7 @@ namespace Serein.Library.NodeGenerator sb.AppendLine($"using System.Collections.Generic;"); sb.AppendLine($"using Serein.Library;"); sb.AppendLine($"using Serein.Library.Api;"); - sb.AppendLine($"using static Serein.Library.Utils.ChannelFlowInterrupt;"); + //sb.AppendLine($"using static Serein.Library.Utils.ChannelFlowInterrupt;"); sb.AppendLine($""); sb.AppendLine($"namespace {namespaceName}"); sb.AppendLine("{"); diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index ec4d544..e8b4004 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -24,7 +24,7 @@ namespace Serein.Workbench void LoadLocalProject() { #if DEBUG - if (1 == 11) + if (1 == 1) { // 这里是我自己的测试代码,你可以删除 string filePath; diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index 1fae25b..e4833cd 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -198,7 +198,7 @@ namespace Serein.Workbench EnvDecorator.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent; EnvDecorator.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt; EnvDecorator.OnNodeCreate += FlowEnvironment_NodeCreateEvent; - EnvDecorator.OnNodeRemove += FlowEnvironment_NodeRemoteEvent; + EnvDecorator.OnNodeRemove += FlowEnvironment_NodeRemoveEvent; EnvDecorator.OnNodePlace += EnvDecorator_OnNodePlaceEvent; EnvDecorator.OnNodeTakeOut += EnvDecorator_OnNodeTakeOutEvent; EnvDecorator.OnFlowRunComplete += FlowEnvironment_OnFlowRunCompleteEvent; @@ -228,7 +228,7 @@ namespace Serein.Workbench EnvDecorator.OnStartNodeChange -= FlowEnvironment_StartNodeChangeEvent; EnvDecorator.OnNodeConnectChange -= FlowEnvironment_NodeConnectChangeEvemt; EnvDecorator.OnNodeCreate -= FlowEnvironment_NodeCreateEvent; - EnvDecorator.OnNodeRemove -= FlowEnvironment_NodeRemoteEvent; + EnvDecorator.OnNodeRemove -= FlowEnvironment_NodeRemoveEvent; EnvDecorator.OnNodePlace -= EnvDecorator_OnNodePlaceEvent; EnvDecorator.OnNodeTakeOut -= EnvDecorator_OnNodeTakeOutEvent; EnvDecorator.OnFlowRunComplete -= FlowEnvironment_OnFlowRunCompleteEvent; @@ -247,7 +247,7 @@ namespace Serein.Workbench } #region 窗体加载方法 - private void Window_Loaded(object sender, RoutedEventArgs e) + private async void Window_Loaded(object sender, RoutedEventArgs e) { var currentPath = System.IO.Directory.GetCurrentDirectory(); // 当前目录 var baseLibraryFilePath = Path.Combine(currentPath, FlowLibraryManagement.SereinBaseLibrary); @@ -260,7 +260,7 @@ namespace Serein.Workbench { try { - _ = Task.Run(() => + await Task.Run(() => { EnvDecorator.LoadProject(new FlowEnvInfo { Project = App.FlowProjectData }, App.FileDataPath); // 加载项目 }); @@ -553,7 +553,7 @@ namespace Serein.Workbench if (toNodeControl is FlipflopNodeControl flipflopControl && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 { - NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 + NodeTreeViewer.RemoveGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 } Connections.Add(connection); fromNodeControl.AddCnnection(connection); @@ -563,7 +563,7 @@ namespace Serein.Workbench } #endregion #region 移除连接 - else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote) // 移除连接 + else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remove) // 移除连接 { // 需要移除连接 var removeConnections = Connections.Where(c => @@ -639,7 +639,7 @@ namespace Serein.Workbench } #endregion #region 移除连接 - else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote) // 移除连接 + else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remove) // 移除连接 { // 需要移除连接 var removeConnections = Connections.Where(c => c.Start.MyNode.Guid.Equals(fromNodeGuid) @@ -674,7 +674,7 @@ namespace Serein.Workbench /// 节点移除事件 /// /// - private void FlowEnvironment_NodeRemoteEvent(NodeRemoveEventArgs eventArgs) + private void FlowEnvironment_NodeRemoveEvent(NodeRemoveEventArgs eventArgs) { var nodeGuid = eventArgs.NodeGuid; if (!TryGetControl(nodeGuid, out var nodeControl)) @@ -696,7 +696,7 @@ namespace Serein.Workbench var node = flipflopControl?.ViewModel?.NodeModel; if (node is not null) { - NodeTreeViewer.RemoteGlobalFlipFlop(node); // 从全局触发器树树视图中移除 + NodeTreeViewer.RemoveGlobalFlipFlop(node); // 从全局触发器树树视图中移除 } } @@ -714,23 +714,24 @@ namespace Serein.Workbench /// private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs) { - if (eventArgs.NodeModel is not NodeModelBase nodeModelBase) + var nodeModel = eventArgs.NodeModel; + if (NodeControls.ContainsKey(nodeModel.Guid)) { - SereinEnv.WriteLine(InfoType.WARN, "OnNodeCreateEvent事件接收到意外的返回值"); + SereinEnv.WriteLine(InfoType.WARN, $"OnNodeCreateEvent 事件接收到意外的返回值:节点Guid重复 - {nodeModel.Guid}"); return; } PositionOfUI position = eventArgs.Position; - if(!NodeMVVMManagement.TryGetType(nodeModelBase.ControlType, out var nodeMVVM)) + if(!NodeMVVMManagement.TryGetType(nodeModel.ControlType, out var nodeMVVM)) { - SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModelBase.ControlType}节点,节点类型尚未注册。"); + SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,节点类型尚未注册。"); return; } if(nodeMVVM.ControlType == null || nodeMVVM.ViewModelType == null) { - SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModelBase.ControlType}节点,UI类型尚未注册(请通过 NodeMVVMManagement.RegisterUI() 方法进行注册)。"); + SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,UI类型尚未注册(请通过 NodeMVVMManagement.RegisterUI() 方法进行注册)。"); return; } @@ -740,7 +741,7 @@ namespace Serein.Workbench { nodeControl = CreateNodeControl(nodeMVVM.ControlType, // 控件UI类型 nodeMVVM.ViewModelType, // 控件VIewModel类型 - nodeModelBase, // 控件数据实体 + nodeModel, // 控件数据实体 nodeCanvas); // 所在画布 } catch (Exception ex) @@ -749,7 +750,7 @@ namespace Serein.Workbench return; } - NodeControls.TryAdd(nodeModelBase.Guid, nodeControl); // 添加到 + NodeControls.TryAdd(nodeModel.Guid, nodeControl); // 添加到 if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器 { // 通知运行环境调用加载节点子项的方法 @@ -764,7 +765,7 @@ namespace Serein.Workbench #region 节点树视图 - if (nodeModelBase.ControlType == NodeControlType.Flipflop) + if (nodeModel.ControlType == NodeControlType.Flipflop) { var node = nodeControl?.ViewModel?.NodeModel; if (node is not null) @@ -779,7 +780,7 @@ namespace Serein.Workbench } /// - /// 节点父子关系发生改变 + /// 放置一个节点 /// /// /// @@ -802,6 +803,10 @@ namespace Serein.Workbench nodeControl.PlaceToContainer(containerControl); // 放置在容器节点中 } + /// + /// 取出一个节点 + /// + /// private void EnvDecorator_OnNodeTakeOutEvent(NodeTakeOutEventArgs eventArgs) { string nodeGuid = eventArgs.NodeGuid; @@ -1170,34 +1175,13 @@ namespace Serein.Workbench })); } - #region 右键菜单功能 - 中断 - - contextMenu.Items.Add(CreateMenuItem("在此中断", async (s, e) => - { - if ((s is MenuItem menuItem) && menuItem is not null) - { - if (nodeControl?.ViewModel?.NodeModel?.DebugSetting?.IsInterrupt == true) - { - await EnvDecorator.SetNodeInterruptAsync(nodeGuid,false); - nodeControl.ViewModel.IsInterrupt = false; - - menuItem.Header = "取消中断"; - } - else - { - nodeControl!.ViewModel!.IsInterrupt = true; - await EnvDecorator.SetNodeInterruptAsync(nodeGuid, true); - menuItem.Header = "在此中断"; - - } - } - })); - - #endregion - + contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => EnvDecorator.SetStartNodeAsync(nodeGuid))); - contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => EnvDecorator.RemoveNodeAsync(nodeGuid))); + contextMenu.Items.Add(CreateMenuItem("删除", async (s, e) => + { + var result = await EnvDecorator.RemoveNodeAsync(nodeGuid); + })); #region 右键菜单功能 - 控件对齐 @@ -1642,7 +1626,7 @@ namespace Serein.Workbench { if (ViewObjectViewer.MonitorObj is null) { - EnvDecorator.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI + // EnvDecorator.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI return; } if (instance is null) @@ -1656,8 +1640,8 @@ namespace Serein.Workbench } else { - EnvDecorator.SetMonitorObjState(ViewObjectViewer.MonitorKey,false); // 取消对旧节点的监视 - EnvDecorator.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI + //EnvDecorator.SetMonitorObjState(ViewObjectViewer.MonitorKey,false); // 取消对旧节点的监视 + //EnvDecorator.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI } } #endregion @@ -2499,7 +2483,7 @@ namespace Serein.Workbench } else { - NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 + NodeTreeViewer.RemoveGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 } } } diff --git a/WorkBench/Themes/NodeTreeViewControl.xaml.cs b/WorkBench/Themes/NodeTreeViewControl.xaml.cs index 416702f..08f8c11 100644 --- a/WorkBench/Themes/NodeTreeViewControl.xaml.cs +++ b/WorkBench/Themes/NodeTreeViewControl.xaml.cs @@ -42,7 +42,7 @@ namespace Serein.Workbench.Themes viewer.RefreshTree(); } } - public void RemoteGlobalFlipFlop(NodeModelBase nodeModel) + public void RemoveGlobalFlipFlop(NodeModelBase nodeModel) { if (globalFlipflopNodes.TryGetValue(nodeModel.Guid, out var viewer)) { diff --git a/WorkBench/Themes/ObjectViewerControl.xaml.cs b/WorkBench/Themes/ObjectViewerControl.xaml.cs index ddb5bc0..d3d68ab 100644 --- a/WorkBench/Themes/ObjectViewerControl.xaml.cs +++ b/WorkBench/Themes/ObjectViewerControl.xaml.cs @@ -132,17 +132,17 @@ namespace Serein.Workbench.Themes /// private async void UpMonitorExpressionButton_Click(object sender, RoutedEventArgs e) { - if (FlowEnvironment is not null && await FlowEnvironment.AddInterruptExpressionAsync(monitorKey, MonitorExpression)) // 对象预览器尝试添加中断表达式 - { - if (string.IsNullOrEmpty(MonitorExpression)) - { - ExpressionTextBox.Text = "表达式已清空"; - } - else - { - UpMonitorExpressionButton.Content = "更新监视表达式"; - } - } + //if (FlowEnvironment is not null && await FlowEnvironment.AddInterruptExpressionAsync(monitorKey, MonitorExpression)) // 对象预览器尝试添加中断表达式 + //{ + // if (string.IsNullOrEmpty(MonitorExpression)) + // { + // ExpressionTextBox.Text = "表达式已清空"; + // } + // else + // { + // UpMonitorExpressionButton.Content = "更新监视表达式"; + // } + //} } private TreeViewItem? LoadTree(object? obj) diff --git a/Workbench/Node/INodeContainerControl.cs b/Workbench/Node/INodeContainerControl.cs index 6ca5208..4c01eff 100644 --- a/Workbench/Node/INodeContainerControl.cs +++ b/Workbench/Node/INodeContainerControl.cs @@ -17,13 +17,13 @@ namespace Serein.Workbench.Node /// 放置一个节点 /// /// - void PlaceNode(NodeControlBase nodeControl); + bool PlaceNode(NodeControlBase nodeControl); /// /// 取出一个节点 /// /// - void TakeOutNode(NodeControlBase nodeControl); + bool TakeOutNode(NodeControlBase nodeControl); /// /// 取出所有节点(用于删除容器) diff --git a/Workbench/Node/NodeControlBase.cs b/Workbench/Node/NodeControlBase.cs index 5655ea7..2487cfb 100644 --- a/Workbench/Node/NodeControlBase.cs +++ b/Workbench/Node/NodeControlBase.cs @@ -53,8 +53,13 @@ namespace Serein.Workbench.Node.View public void PlaceToContainer(INodeContainerControl nodeContainerControl) { this.nodeContainerControl = nodeContainerControl; - NodeCanvas.Children.Remove(this); // 从画布上移除 - nodeContainerControl.PlaceNode(this); + NodeCanvas.Children.Remove(this); // 临时从画布上移除 + var result = nodeContainerControl.PlaceNode(this); + if (!result) // 检查是否放置成功,如果不成功,需要重新添加回来 + { + NodeCanvas.Children.Add(this); // 从画布上移除 + + } } /// @@ -62,8 +67,17 @@ namespace Serein.Workbench.Node.View /// public void TakeOutContainer() { - nodeContainerControl.TakeOutNode(this); - NodeCanvas.Children.Add(this); // 重新添加到画布上 + var result = nodeContainerControl.TakeOutNode(this); // 从控件取出 + if (result) // 移除成功时才添加到画布上 + { + NodeCanvas.Children.Add(this); // 重新添加到画布上 + if (nodeContainerControl is NodeControlBase containerControl) + { + this.ViewModel.NodeModel.Position.X = containerControl.ViewModel.NodeModel.Position.X + containerControl.Width + 10; + this.ViewModel.NodeModel.Position.Y = containerControl.ViewModel.NodeModel.Position.Y; + } + } + } /// diff --git a/Workbench/Node/View/GlobalDataControl.xaml.cs b/Workbench/Node/View/GlobalDataControl.xaml.cs index 4c8f314..f3f4bf3 100644 --- a/Workbench/Node/View/GlobalDataControl.xaml.cs +++ b/Workbench/Node/View/GlobalDataControl.xaml.cs @@ -58,15 +58,24 @@ namespace Serein.Workbench.Node.View JunctionControlBase[] INodeJunction.ArgDataJunction => throw new NotImplementedException(); - public void PlaceNode(NodeControlBase nodeControl) + public bool PlaceNode(NodeControlBase nodeControl) { - //GlobalDataPanel.Children.Clear(); + if (GlobalDataPanel.Children.Contains(nodeControl)) + { + return false; + } GlobalDataPanel.Children.Add(nodeControl); + return true; } - public void TakeOutNode(NodeControlBase nodeControl) + public bool TakeOutNode(NodeControlBase nodeControl) { + if (!GlobalDataPanel.Children.Contains(nodeControl)) + { + return false; + } GlobalDataPanel.Children.Remove(nodeControl); + return true; } public void TakeOutAll()