From f5924aa31ef7c7d3bc347cf12aa09206fa436277 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Fri, 20 Sep 2024 10:50:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=BC=82=E6=AD=A5=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=BA=86=E8=8A=82=E7=82=B9=E6=89=A7=E8=A1=8C=E6=96=B9?= =?UTF-8?q?=E6=B3=95=EF=BC=8C=E5=B0=86=E8=A7=A6=E5=8F=91=E5=99=A8=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E4=B8=8E=E5=85=B6=E4=BB=96=E8=8A=82=E7=82=B9=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E3=80=82=E4=BD=BF=E7=94=A8Channel=E4=BB=A3=E6=9B=BFTc?= =?UTF-8?q?s=E6=9B=B4=E6=94=B9=E4=BA=86=E4=BF=A1=E5=8F=B7=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=EF=BC=8C=E4=BD=BF=E5=85=B6=E7=AC=A6=E5=90=88=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E7=BC=96=E7=A8=8B=E7=9A=84=E4=B9=A0=E6=83=AF=E3=80=82?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E8=8A=82=E7=82=B9=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E5=90=AF=E7=94=A8=E5=8B=BE=E9=80=89=E6=A1=86=E3=80=81=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E9=81=AE=E7=BD=A9=E5=8B=BE=E9=80=89=E6=A1=86=EF=BC=8C?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E5=8F=B3=E9=94=AE=E9=9D=A2=E6=9D=BF=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=B8=AD=E6=96=AD=E5=8A=9F=E8=83=BD=EF=BC=88=E8=AF=95?= =?UTF-8?q?=E9=AA=8C=EF=BC=89=E3=80=82=E5=A2=9E=E5=8A=A0=E4=BA=86=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=90=8E=E8=A2=AB=E9=80=89=E6=8B=A9=E7=9A=84=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E7=9A=84=E8=A7=86=E8=A7=89=E6=95=88=E6=9E=9C=E3=80=82?= =?UTF-8?q?=E6=9B=B4=E6=94=B9=E5=B9=B3=E7=A7=BB=E7=BC=A9=E6=94=BE=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BD=BF=E5=85=B6=E6=9B=B4=E5=8A=A0=E7=AC=A6?= =?UTF-8?q?=E5=90=88=E4=B8=80=E8=88=AC=E7=9A=84=E4=BD=BF=E7=94=A8=E4=B9=A0?= =?UTF-8?q?=E6=83=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library.Core/NodeFlow/FlipflopContext.cs | 7 +- Library.Framework/NodeFlow/FlipflopContext.cs | 9 +- Library/Api/IFlipflopContext.cs | 3 +- Library/Api/IFlowEnvironment.cs | 4 +- Library/Entity/ExplicitData.cs | 9 +- Library/Entity/MethodDetails.cs | 7 + Library/Entity/NodeDebugSetting.cs | 47 ++ Library/Serein.Library.csproj | 1 + Library/Utils/ChannelFlowInterrupt.cs | 122 +++++ Library/Utils/ChannelFlowTrigger.cs | 115 +++++ Library/Utils/TcsSignalFlipflop.cs | 64 --- NodeFlow/Base/NodeModelBaseData.cs | 14 +- NodeFlow/Base/NodeModelBaseFunc.cs | 232 ++++++---- NodeFlow/FlowEnvironment.cs | 70 ++- NodeFlow/FlowStarter.cs | 94 ++-- NodeFlow/Model/CompositeActionNode.cs | 3 +- NodeFlow/Model/CompositeConditionNode.cs | 7 +- NodeFlow/Model/SingleConditionNode.cs | 5 +- NodeFlow/Model/SingleExpOpNode.cs | 7 +- NodeFlow/Model/SingleFlipflopNode.cs | 57 ++- WorkBench/LogWindow.xaml.cs | 8 + WorkBench/MainWindow.xaml | 4 +- WorkBench/MainWindow.xaml.cs | 428 ++++++++++-------- WorkBench/Node/NodeControlViewModelBase.cs | 64 ++- WorkBench/Node/View/ActionNodeControl.xaml | 88 ++-- WorkBench/Node/View/ActionRegionControl.xaml | 4 +- WorkBench/Node/View/ConditionNodeControl.xaml | 2 - WorkBench/Themes/TypeViewerWindow.xaml.cs | 257 +++++++++-- .../InvertableBooleanToVisibilityConverter.cs | 38 ++ WorkBench/tool/LogTextWriter.cs | 67 ++- 30 files changed, 1298 insertions(+), 539 deletions(-) create mode 100644 Library/Entity/NodeDebugSetting.cs create mode 100644 Library/Utils/ChannelFlowInterrupt.cs create mode 100644 Library/Utils/ChannelFlowTrigger.cs delete mode 100644 Library/Utils/TcsSignalFlipflop.cs create mode 100644 WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs diff --git a/Library.Core/NodeFlow/FlipflopContext.cs b/Library.Core/NodeFlow/FlipflopContext.cs index 8dd9884..f59edf6 100644 --- a/Library.Core/NodeFlow/FlipflopContext.cs +++ b/Library.Core/NodeFlow/FlipflopContext.cs @@ -1,5 +1,6 @@ using Serein.Library.Api; using Serein.Library.Enums; +using Serein.Library.NodeFlow.Tool; namespace Serein.Library.Core.NodeFlow { @@ -67,16 +68,16 @@ namespace Serein.Library.Core.NodeFlow { public FlipflopStateType State { get; set; } - public object Data { get; set; } + public TriggerData TriggerData { get; set; } public FlipflopContext(FlipflopStateType ffState) { State = ffState; } - public FlipflopContext(FlipflopStateType ffState, object data) + public FlipflopContext(FlipflopStateType ffState, TriggerData data) { State = ffState; - Data = data; + TriggerData = data; } diff --git a/Library.Framework/NodeFlow/FlipflopContext.cs b/Library.Framework/NodeFlow/FlipflopContext.cs index 5557d63..cf39e0c 100644 --- a/Library.Framework/NodeFlow/FlipflopContext.cs +++ b/Library.Framework/NodeFlow/FlipflopContext.cs @@ -1,5 +1,6 @@ using Serein.Library.Api; using Serein.Library.Enums; +using Serein.Library.NodeFlow.Tool; using System; using System.Threading.Tasks; @@ -59,7 +60,7 @@ namespace Serein.Library.Framework.NodeFlow { public FlipflopStateType State { get; set; } //public TResult? Data { get; set; } - public object Data { get; set; } + public TriggerData TriggerData { get; set; } public FlipflopContext(FlipflopStateType ffState) { State = ffState; @@ -67,7 +68,11 @@ namespace Serein.Library.Framework.NodeFlow public FlipflopContext(FlipflopStateType ffState, object data) { State = ffState; - Data = data; + TriggerData = new TriggerData + { + Type = TriggerType.External, + Value = data + }; } } diff --git a/Library/Api/IFlipflopContext.cs b/Library/Api/IFlipflopContext.cs index 5f6b8a8..88e8947 100644 --- a/Library/Api/IFlipflopContext.cs +++ b/Library/Api/IFlipflopContext.cs @@ -1,10 +1,11 @@ using Serein.Library.Enums; +using Serein.Library.NodeFlow.Tool; namespace Serein.Library.Api { public interface IFlipflopContext { FlipflopStateType State { get; set; } - object Data { get; set; } + TriggerData TriggerData { get; set; } } } diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 57e5bf1..1d83946 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -1,5 +1,6 @@ using Serein.Library.Entity; using Serein.Library.Enums; +using Serein.Library.Utils; using System; using System.Collections.Generic; using System.Reflection; @@ -212,6 +213,8 @@ namespace Serein.Library.Api public interface IFlowEnvironment { + ChannelFlowInterrupt ChannelFlowInterrupt { get; set; } + event FlowRunCompleteHandler OnFlowRunComplete; event ProjectLoadedHandler OnProjectLoaded; @@ -290,7 +293,6 @@ namespace Serein.Library.Api void RemoteNode(string nodeGuid); - } } diff --git a/Library/Entity/ExplicitData.cs b/Library/Entity/ExplicitData.cs index 7067977..f80e68d 100644 --- a/Library/Entity/ExplicitData.cs +++ b/Library/Entity/ExplicitData.cs @@ -16,7 +16,7 @@ namespace Serein.Library.Entity /// public int Index { get; set; } /// - /// 是否为显式参数 + /// 是否为显式参数(固定值/表达式) /// public bool IsExplicitData { get; set; } ///// @@ -45,21 +45,16 @@ namespace Serein.Library.Entity public string DataValue { get; set; } - - public string[] Items { get; set; } - - - public ExplicitData Clone() => new ExplicitData() { Index = Index, IsExplicitData = IsExplicitData, // ExplicitType = ExplicitType, + ExplicitTypeName = ExplicitTypeName, DataType = DataType, ParameterName = ParameterName, - ExplicitTypeName = ExplicitTypeName, DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue, Items = Items.Select(it => it).ToArray(), }; diff --git a/Library/Entity/MethodDetails.cs b/Library/Entity/MethodDetails.cs index 71c3d61..7edafcf 100644 --- a/Library/Entity/MethodDetails.cs +++ b/Library/Entity/MethodDetails.cs @@ -28,10 +28,17 @@ namespace Serein.Library.Entity MethodName = MethodName, MethodLockName = MethodLockName, IsNetFramework = IsNetFramework, + IsProtectionParameter = IsProtectionParameter, ExplicitDatas = ExplicitDatas?.Select(it => it.Clone()).ToArray(), }; } + + /// + /// 是否保护参数 + /// + public bool IsProtectionParameter { get; set; } + /// /// 作用实例的类型 /// diff --git a/Library/Entity/NodeDebugSetting.cs b/Library/Entity/NodeDebugSetting.cs new file mode 100644 index 0000000..c605267 --- /dev/null +++ b/Library/Entity/NodeDebugSetting.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Serein.Library.Entity +{ + public class NodeDebugSetting + { + /// + /// 是否使能(调试中断功能) + /// + public bool IsEnable { get; set; } = true; + + /// + /// 是否中断(调试中断功能) + /// + public bool IsInterrupt { get; set; } = false; + + /// + /// 中断级别,暂时停止继续执行后继分支。 + /// + public InterruptClass InterruptClass { get; set; } = InterruptClass.None; + } + + /// + /// 中断级别,暂时停止继续执行后继分支。 + /// + public enum InterruptClass + { + /// + /// 不中断 + /// + None, + /// + /// 分支中断,当前节点。 + /// + Branch, + /// + /// 分组中断,相同中断分组的节点。 + /// + Group, + /// + /// 全局中断,其它所有节点。 + /// + Global, + } +} diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index bd3f82b..8f12865 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -16,6 +16,7 @@ + diff --git a/Library/Utils/ChannelFlowInterrupt.cs b/Library/Utils/ChannelFlowInterrupt.cs new file mode 100644 index 0000000..1391444 --- /dev/null +++ b/Library/Utils/ChannelFlowInterrupt.cs @@ -0,0 +1,122 @@ +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, + Overtime + } + + // 使用并发字典管理每个信号对应的 Channel + private readonly ConcurrentDictionary> _channels = new ConcurrentDictionary>(); + + /// + /// 创建信号并指定超时时间,到期后自动触发(异步方法) + /// + /// 信号标识符 + /// 超时时间 + /// 等待任务 + public async Task CreateChannelWithTimeoutAsync(string signal, TimeSpan outTime) + { + var channel = GetOrCreateChannel(signal); + var cts = new CancellationTokenSource(); + + // 异步任务:超时后自动触发信号 + _ = Task.Run(async () => + { + try + { + await Task.Delay(outTime, cts.Token); + await channel.Writer.WriteAsync(CancelType.Overtime); + } + catch (OperationCanceledException) + { + // 超时任务被取消 + } + }, cts.Token); + + // 等待信号传入(超时或手动触发) + var result = await channel.Reader.ReadAsync(); + return result; + } + + /// + /// 创建信号并指定超时时间,到期后自动触发(同步阻塞方法) + /// + /// 信号标识符 + /// 超时时间 + public CancelType 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) + { + // 任务被取消 + } + }); + + // 同步阻塞直到信号触发或超时 + var result = channel.Reader.ReadAsync().AsTask().GetAwaiter().GetResult(); + return result; + } + + /// + /// 触发信号 + /// + /// 信号字符串 + /// 是否成功触发 + public bool TriggerSignal(string signal) + { + if (_channels.TryGetValue(signal, out var channel)) + { + // 手动触发信号 + channel.Writer.TryWrite(CancelType.Manual); + return true; + } + return false; + } + + /// + /// 取消所有任务 + /// + public void CancelAllTasks() + { + foreach (var channel in _channels.Values) + { + channel.Writer.Complete(); + } + _channels.Clear(); + } + + /// + /// 获取或创建指定信号的 Channel + /// + /// 信号字符串 + /// 对应的 Channel + private Channel GetOrCreateChannel(string signal) + { + return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded()); + } + } +} + diff --git a/Library/Utils/ChannelFlowTrigger.cs b/Library/Utils/ChannelFlowTrigger.cs new file mode 100644 index 0000000..4ec726d --- /dev/null +++ b/Library/Utils/ChannelFlowTrigger.cs @@ -0,0 +1,115 @@ + +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; + + +namespace Serein.Library.NodeFlow.Tool +{ + /// + /// 触发类型 + /// + public enum TriggerType + { + /// + /// 外部触发 + /// + External, + /// + /// 超时触发 + /// + Overtime + } + public class TriggerData + { + public TriggerType Type { get; set; } + public object Value { get; set; } + } + + + public class ChannelFlowTrigger where TSignal : struct, Enum + { + // 使用并发字典管理每个枚举信号对应的 Channel + private readonly ConcurrentDictionary> _channels = new ConcurrentDictionary>(); + + /// + /// 创建信号并指定超时时间,到期后自动触发(异步方法) + /// + /// 枚举信号标识符 + /// 超时时间 + /// 等待任务 + public async Task CreateChannelWithTimeoutAsync(TSignal signal, TimeSpan outTime, TResult outValue) + { + var channel = GetOrCreateChannel(signal); + var cts = new CancellationTokenSource(); + + // 异步任务:超时后自动触发信号 + _ = Task.Run(async () => + { + try + { + await Task.Delay(outTime, cts.Token); + TriggerData triggerData = new TriggerData() + { + Value = outValue, + Type = TriggerType.Overtime, + }; + await channel.Writer.WriteAsync(triggerData); + } + catch (OperationCanceledException) + { + // 超时任务被取消 + } + }, cts.Token); + + // 等待信号传入(超时或手动触发) + var result = await channel.Reader.ReadAsync(); + return result; + } + + /// + /// 触发信号 + /// + /// 枚举信号标识符 + /// 是否成功触发 + public bool TriggerSignal(TSignal signal, TResult value) + { + if (_channels.TryGetValue(signal, out var channel)) + { + TriggerData triggerData = new TriggerData() + { + Value = value, + Type = TriggerType.External, + }; + // 手动触发信号 + channel.Writer.TryWrite(triggerData); + return true; + } + return false; + } + + /// + /// 取消所有任务 + /// + public void CancelAllTasks() + { + foreach (var channel in _channels.Values) + { + channel.Writer.Complete(); + } + _channels.Clear(); + } + + /// + /// 获取或创建指定信号的 Channel + /// + /// 枚举信号标识符 + /// 对应的 Channel + private Channel GetOrCreateChannel(TSignal signal) + { + return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded()); + } + } +} diff --git a/Library/Utils/TcsSignalFlipflop.cs b/Library/Utils/TcsSignalFlipflop.cs deleted file mode 100644 index fa98d9d..0000000 --- a/Library/Utils/TcsSignalFlipflop.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Serein.Library.Ex; -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; - -namespace Serein.Library.Core.NodeFlow.Tool -{ - //public class TcsSignalException : Exception - //{ - // public FlowStateType FsState { get; set; } - // public TcsSignalException(string? message) : base(message) - // { - // FsState = FlowStateType.Error; - // } - //} - - public class TcsSignalFlipflop where TSignal : struct, Enum - { - public ConcurrentDictionary> TcsEvent { get; } = new ConcurrentDictionary>(); - - public ConcurrentDictionary TcsLock { get; } = new ConcurrentDictionary(); - - /// - /// 触发信号 - /// - /// - /// 信号 - /// 传递的参数 - /// 是否成功触发 - public bool TriggerSignal(TSignal signal, T value) - { - var tcsLock = TcsLock.GetOrAdd(signal, new object()); - lock (tcsLock) - { - if (TcsEvent.TryRemove(signal, out var waitTcs)) - { - waitTcs.SetResult(value); - return true; - } - return false; - } - } - - public TaskCompletionSource CreateTcs(TSignal signal) - { - var tcsLock = TcsLock.GetOrAdd(signal, new object()); - lock (tcsLock) - { - var tcs = TcsEvent.GetOrAdd(signal, new TaskCompletionSource()); - return tcs; - } - - } - - public void CancelTask() - { - foreach (var tcs in TcsEvent.Values) - { - tcs.SetException(new FlipflopException("任务取消")); - } - TcsEvent.Clear(); - } - } -} diff --git a/NodeFlow/Base/NodeModelBaseData.cs b/NodeFlow/Base/NodeModelBaseData.cs index 395d18f..4623382 100644 --- a/NodeFlow/Base/NodeModelBaseData.cs +++ b/NodeFlow/Base/NodeModelBaseData.cs @@ -22,12 +22,14 @@ namespace Serein.NodeFlow.Base PreviousNodes[ctType] = []; SuccessorNodes[ctType] = []; } + DebugSetting = new NodeDebugSetting(); } + /// - /// 是否中断(调试中断功能) + /// 调试功能 /// - public bool IsInterrupt { get; set; } + public NodeDebugSetting DebugSetting { get; set; } /// /// 节点对应的控件类型 @@ -84,13 +86,7 @@ namespace Serein.NodeFlow.Base } - public class DebugInfo - { - /// - /// 是否中断 - /// - public bool IsInterrupt { get;set; } - } + /// diff --git a/NodeFlow/Base/NodeModelBaseFunc.cs b/NodeFlow/Base/NodeModelBaseFunc.cs index 8854ca8..1c3059a 100644 --- a/NodeFlow/Base/NodeModelBaseFunc.cs +++ b/NodeFlow/Base/NodeModelBaseFunc.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.NodeFlow.Base { @@ -20,6 +21,32 @@ namespace Serein.NodeFlow.Base /// public abstract partial class NodeModelBase : IDynamicFlowNode { + + + #region 调试中断 + + public Action? CancelInterruptCallback; + + /// + /// 中断节点 + /// + public void Interrupt() + { + this.DebugSetting.InterruptClass = InterruptClass.Branch; + this.DebugSetting.IsInterrupt = true; + } + /// + /// 不再中断 + /// + public void CancelInterrupt() + { + this.DebugSetting.InterruptClass = InterruptClass.None; + this.DebugSetting.IsInterrupt = false; + CancelInterruptCallback?.Invoke(); + CancelInterruptCallback = null; + } + #endregion + #region 导出/导入项目文件节点信息 internal abstract Parameterdata[] GetParameterdatas(); @@ -84,19 +111,19 @@ namespace Serein.NodeFlow.Base #endregion + #region 节点方法的执行 /// /// 开始执行 /// /// /// - public async Task StartExecution(IDynamicContext context) + public async Task StartExecute(IDynamicContext context) { var cts = context.SereinIoc.GetOrRegisterInstantiate(); Stack stack = new Stack(); stack.Push(this); - while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环 { // 从栈中弹出一个节点作为当前节点进行处理 @@ -108,51 +135,112 @@ namespace Serein.NodeFlow.Base currentNode.MethodDetails.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType); } + //if (TryCreateInterruptTask(context, currentNode, out Task? task)) + //{ + // var cancelType = await task!; + // await Console.Out.WriteLineAsync($"[{currentNode.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支"); + //} + + #region 执行相关 // 首先执行上游分支 var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream]; for (int i = upstreamNodes.Count - 1; i >= 0; i--) { - upstreamNodes[i].PreviousNode = currentNode; - await upstreamNodes[i].StartExecution(context); // 执行上游分支 + if (upstreamNodes[i].DebugSetting.IsEnable) // 排除未启用的上游节点 + { + upstreamNodes[i].PreviousNode = currentNode; + await upstreamNodes[i].StartExecute(context); // 执行流程节点的上游分支 + } } + currentNode.FlowData = currentNode.ExecutingAsync(context); // 流程中正常执行 + // 判断是否为触发器节点,如果是,则开始等待。 - if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == NodeType.Flipflop) - { - - currentNode.FlowData = await currentNode.ExecuteAsync(context); // 流程中遇到了触发器 - } - else - { - currentNode.FlowData = currentNode.Execute(context); // 流程中正常执行 - } + //if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == NodeType.Flipflop) + //{ - if(currentNode.NextOrientation == ConnectionType.None) - { - // 不再执行 - break; - } + // currentNode.FlowData = await currentNode.ExecutingFlipflopAsync(context); // 流程中遇到了触发器 + //} + //else + //{ + + //} + #endregion - // 获取下一分支 + + + + #region 执行完成 + if (currentNode.NextOrientation == ConnectionType.None) break; // 不再执行 + + + // 选择后继分支 var nextNodes = currentNode.SuccessorNodes[currentNode.NextOrientation]; // 将下一个节点集合中的所有节点逆序推入栈中 for (int i = nextNodes.Count - 1; i >= 0; i--) { - nextNodes[i].PreviousNode = currentNode; - stack.Push(nextNodes[i]); - } + // 排除未启用的节点 + if (nextNodes[i].DebugSetting.IsEnable) + { + nextNodes[i].PreviousNode = currentNode; + stack.Push(nextNodes[i]); + } + } + #endregion } } + public static bool TryCreateInterruptTask(IDynamicContext context, NodeModelBase currentNode, out Task? task) + { + if (!currentNode.DebugSetting.IsInterrupt) + { + task = null; + return false; + } + + Task? result = null; + bool haveTask = false; + Console.WriteLine($"[{currentNode.MethodDetails.MethodName}]在当前分支中断"); + if (currentNode.DebugSetting.InterruptClass == InterruptClass.None) + { + haveTask = false; + task = null; + currentNode.DebugSetting.IsInterrupt = false; // 纠正设置 + } + if (currentNode.DebugSetting.InterruptClass == InterruptClass.Branch) // 中断当前分支 + { + currentNode.DebugSetting.IsInterrupt = true; + haveTask = true; + task = context.FlowEnvironment.ChannelFlowInterrupt.CreateChannelWithTimeoutAsync(currentNode.Guid, TimeSpan.FromSeconds(1)); + currentNode.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(currentNode.Guid); + + } + else + { + haveTask = false; + task = null; + } + + return haveTask; + } /// /// 执行节点对应的方法 /// /// 流程上下文 /// 节点传回数据对象 - public virtual object? Execute(IDynamicContext context) + public virtual async Task ExecutingAsync(IDynamicContext context) { + #region 调试中断 + if (TryCreateInterruptTask(context, this, out Task? task)) + { + var cancelType = await task!; + await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支"); + } + + #endregion + MethodDetails md = MethodDetails; var del = md.MethodDelegate; object instance = md.ActingInstance; @@ -186,38 +274,7 @@ namespace Serein.NodeFlow.Base /// /// 节点传回数据对象 /// - public virtual async Task ExecuteAsync(IDynamicContext context) - { - MethodDetails md = MethodDetails; - Delegate del = md.MethodDelegate; - object instance = md.ActingInstance; - var haveParameter = md.ExplicitDatas.Length >= 0; - try - { - // 调用委托并获取结果 - Task flipflopTask = haveParameter switch - { - true => ((Func>)del).Invoke(instance, GetParameters(context, md)), // 执行流程中的触发器方法时获取入参参数 - false => ((Func>)del).Invoke(instance), - }; - - IFlipflopContext flipflopContext = (await flipflopTask) ?? throw new FlipflopException("没有返回上下文"); - NextOrientation = flipflopContext.State.ToContentType(); - return flipflopContext.Data; - } - //catch(FlipflopException ex) - //{ - // NextOrientation = ConnectionType.IsError; - // RuningException = ex; - // return null; - //} - catch (Exception ex) - { - NextOrientation = ConnectionType.IsError; - RuningException = ex; - return null; - } - } + #region 节点转换的委托类型 @@ -248,45 +305,34 @@ namespace Serein.NodeFlow.Base public object?[]? GetParameters(IDynamicContext context, MethodDetails md) { // 用正确的大小初始化参数数组 - var types = md.ExplicitDatas.Select(it => it.DataType).ToArray(); - if (types.Length == 0) + if (md.ExplicitDatas.Length == 0) { - return [md.ActingInstance]; + return [];// md.ActingInstance } - object?[]? parameters = new object[types.Length]; + object?[]? parameters = new object[md.ExplicitDatas.Length]; var flowData = PreviousNode?.FlowData; // 当前传递的数据 var previousDataType = flowData?.GetType(); - for (int i = 0; i < types.Length; i++) + for (int i = 0; i < parameters.Length; i++) { - //if (flowData is null) - //{ - // parameters[i] = md.ExplicitDatas[i].DataType switch - // { - // Type t when t == typeof(IDynamicContext) => context, // 上下文 - // Type t when t == typeof(MethodDetails) => md, // 节点方法描述 - // Type t when t == typeof(NodeModelBase) => this, // 节点实体类 - // _ => null, - // }; - // continue; // 上一节点数据为空,提前跳过 - //} - object? inputParameter; // + + object? inputParameter; // 存放解析的临时参数 var ed = md.ExplicitDatas[i]; // 方法入参描述 - + if (ed.IsExplicitData) { - - if (ed.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase)) + + if (ed.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase)) { // 执行表达式从上一节点获取对象 - inputParameter = SerinExpressionEvaluator.Evaluate(ed.DataValue, flowData, out _); + inputParameter = SerinExpressionEvaluator.Evaluate(ed.DataValue, flowData, out _); } - else + else { // 使用输入的固定值 - inputParameter = ed.DataValue; + inputParameter = ed.DataValue; } } else @@ -303,16 +349,26 @@ namespace Serein.NodeFlow.Base Type t when t == typeof(MethodDetails) => md, // 节点方法描述 Type t when t == typeof(NodeModelBase) => this, // 节点实体类 Type t when t == typeof(Guid) => new Guid(inputParameter?.ToString()), - Type t when t == typeof(decimal) => decimal.Parse(inputParameter?.ToString()), + Type t when t == typeof(DateTime) => DateTime.Parse(inputParameter?.ToString()), Type t when t == typeof(string) => inputParameter?.ToString(), Type t when t == typeof(char) => char.Parse(inputParameter?.ToString()), - Type t when t == typeof(bool) => bool.Parse(inputParameter?.ToString()), - Type t when t == typeof(byte) => byte.Parse(inputParameter?.ToString()), - Type t when t == typeof(int) => int.Parse(inputParameter?.ToString()), - Type t when t == typeof(long) => long.Parse(inputParameter?.ToString()), - Type t when t == typeof(DateTime) => DateTime.Parse(inputParameter?.ToString()), - Type t when t == typeof(float) => float.Parse(inputParameter?.ToString()), - Type t when t == typeof(double) => double.Parse(inputParameter?.ToString()), + Type t when t == typeof(bool) => inputParameter is null ? false : bool.Parse(inputParameter?.ToString()), + Type t when t == typeof(float) => inputParameter is null ? 0F : float.Parse(inputParameter?.ToString()), + Type t when t == typeof(decimal) => inputParameter is null ? 0 : decimal.Parse(inputParameter?.ToString()), + Type t when t == typeof(double) => inputParameter is null ? 0 : double.Parse(inputParameter?.ToString()), + Type t when t == typeof(sbyte) => inputParameter is null ? 0 : sbyte.Parse(inputParameter?.ToString()), + Type t when t == typeof(byte) => inputParameter is null ? 0 : byte.Parse(inputParameter?.ToString()), + Type t when t == typeof(short) => inputParameter is null ? 0 : short.Parse(inputParameter?.ToString()), + Type t when t == typeof(ushort) => inputParameter is null ? 0U : ushort.Parse(inputParameter?.ToString()), + Type t when t == typeof(int) => inputParameter is null ? 0 : int.Parse(inputParameter?.ToString()), + Type t when t == typeof(uint) => inputParameter is null ? 0U : uint.Parse(inputParameter?.ToString()), + Type t when t == typeof(long) => inputParameter is null ? 0L : long.Parse(inputParameter?.ToString()), + Type t when t == typeof(ulong) => inputParameter is null ? 0UL : ulong.Parse(inputParameter?.ToString()), + Type t when t == typeof(nint) => inputParameter is null ? 0 : nint.Parse(inputParameter?.ToString()), + Type t when t == typeof(nuint) => inputParameter is null ? 0 : nuint.Parse(inputParameter?.ToString()), + + + Type t when t.IsEnum => Enum.Parse(ed.DataType, ed.DataValue),// 需要枚举 Type t when t.IsArray => (inputParameter as Array)?.Cast().ToList(), Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter, @@ -329,6 +385,10 @@ namespace Serein.NodeFlow.Base return parameters; } + #endregion + + + /// /// json文本反序列化为对象 /// diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs index 214d327..3fa7bcc 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/FlowEnvironment.cs @@ -7,14 +7,8 @@ using Serein.Library.Utils; using Serein.NodeFlow.Base; using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; -using System.Diagnostics; -using System.Net.Mime; -using System.Numerics; +using System.Collections.Concurrent; using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.CompilerServices; -using System.Text; -using System.Xml.Linq; using static Serein.NodeFlow.FlowStarter; namespace Serein.NodeFlow @@ -37,9 +31,8 @@ namespace Serein.NodeFlow */ - /// - /// 运行环境 - /// + + /// @@ -47,74 +40,99 @@ namespace Serein.NodeFlow /// public class FlowEnvironment : IFlowEnvironment { + public FlowEnvironment() + { + ChannelFlowInterrupt = new ChannelFlowInterrupt(); + LoadedAssemblyPaths = new List(); + LoadedAssemblies = new List(); + MethodDetailss = new List(); + Nodes = new Dictionary(); + FlipflopNodes = new List(); + IsGlobalInterrupt = false; + flowStarter = null; + } + /// /// 节点的命名空间 /// public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; + #region 环境接口事件 /// /// 加载Dll /// public event LoadDLLHandler OnDllLoad; + /// /// 项目加载完成 /// public event ProjectLoadedHandler OnProjectLoaded; + /// /// 节点连接属性改变事件 /// public event NodeConnectChangeHandler OnNodeConnectChange; + /// /// 节点创建事件 /// public event NodeCreateHandler OnNodeCreate; + /// /// 移除节点事件 /// public event NodeRemoteHandler OnNodeRemote; + /// /// 起始节点变化事件 /// public event StartNodeChangeHandler OnStartNodeChange; + /// /// 流程运行完成时间 /// public event FlowRunCompleteHandler OnFlowRunComplete; - private FlowStarter? flowStarter = null; + #endregion + /// - /// 一种轻量的IOC容器 + /// 流程中断器 /// - // public SereinIoc SereinIoc { get; } = new SereinIoc(); + public ChannelFlowInterrupt ChannelFlowInterrupt { get; set; } + + /// + /// 是否全局中断 + /// + public bool IsGlobalInterrupt { get; set; } /// /// 存储加载的程序集路径 /// - public List LoadedAssemblyPaths { get; } = []; + public List LoadedAssemblyPaths { get; } /// /// 存储加载的程序集 /// - public List LoadedAssemblies { get; } = []; + public List LoadedAssemblies { get; } /// /// 存储所有方法信息 /// - public List MethodDetailss { get; } = []; + public List MethodDetailss { get; } - - public Dictionary Nodes { get; } = []; - - public List Regions { get; } = []; + /// + /// 环境加载的节点集合 + /// + public Dictionary Nodes { get; } /// /// 存放触发器节点(运行时全部调用) /// - public List FlipflopNodes { get; } = []; + public List FlipflopNodes { get; } /// - /// 私有属性 + /// 起始节点私有属性 /// private NodeModelBase _startNode; @@ -138,12 +156,19 @@ namespace Serein.NodeFlow } } + + /// + /// 流程启动器(每次运行时都会重新new一个) + /// + private FlowStarter? flowStarter; + /// /// 异步运行 /// /// public async Task StartAsync() { + ChannelFlowInterrupt?.CancelAllTasks(); flowStarter = new FlowStarter(); List flipflopNodes = Nodes.Values.Where(it => it.MethodDetails?.MethodDynamicType == NodeType.Flipflop && it.IsStart == false) .Select(it => (SingleFlipflopNode)it) @@ -171,6 +196,7 @@ namespace Serein.NodeFlow } public void Exit() { + ChannelFlowInterrupt?.CancelAllTasks(); flowStarter?.Exit(); OnFlowRunComplete?.Invoke(new FlowEventArgs()); } diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index fa0d86d..e57d446 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -7,6 +7,7 @@ using Serein.Library.Web; using Serein.NodeFlow.Base; using Serein.NodeFlow.Model; using System.ComponentModel.Design; +using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.NodeFlow { @@ -22,6 +23,7 @@ namespace Serein.NodeFlow { SereinIOC = new SereinIOC(); } + /// /// 流程运行状态 /// @@ -60,13 +62,12 @@ namespace Serein.NodeFlow /// 结束运行时需要执行的方法 /// private Action ExitAction { get; set; } = null; + /// /// 运行的上下文 /// private IDynamicContext Context { get; set; } = null; - - /// /// 开始运行 /// @@ -106,7 +107,7 @@ namespace Serein.NodeFlow #endregion #region 初始化运行环境的Ioc容器 - // 清除节点使用的对象 + // 清除节点使用的对象,筛选出需要初始化的方法描述 var thisRuningMds = new List(); thisRuningMds.AddRange(runNodeMd.Where(md => md is not null)); thisRuningMds.AddRange(initMethods.Where(md => md is not null)); @@ -213,11 +214,11 @@ namespace Serein.NodeFlow // 使用 TaskCompletionSource 创建未启动的触发器任务 var tasks = flipflopNodes.Select(async node => { - await FlipflopExecute(node, env); + await FlipflopExecute(env,node); }).ToArray(); _ = Task.WhenAll(tasks); } - await startNode.StartExecution(Context); // 从起始节点开始运行 + await startNode.StartExecute(Context); // 开始运行时从起始节点开始运行 // 等待结束 if (FlipFlopCts != null) { @@ -240,15 +241,17 @@ namespace Serein.NodeFlow { // 设置对象 singleFlipFlopNode.MethodDetails.ActingInstance = SereinIOC.GetOrRegisterInstantiate(singleFlipFlopNode.MethodDetails.ActingInstanceType); - await FlipflopExecute(singleFlipFlopNode, flowEnvironment); // 启动触发器 + await FlipflopExecute(flowEnvironment,singleFlipFlopNode); // 启动触发器 }); } - /// /// 启动全局触发器 /// - private async Task FlipflopExecute(SingleFlipflopNode singleFlipFlopNode, IFlowEnvironment flowEnvironment) + /// 流程运行全局环境 + /// 需要全局监听信号的触发器 + /// + private async Task FlipflopExecute(IFlowEnvironment flowEnvironment,SingleFlipflopNode singleFlipFlopNode) { var context = new DynamicContext(SereinIOC, flowEnvironment); MethodDetails md = singleFlipFlopNode.MethodDetails; @@ -259,6 +262,7 @@ namespace Serein.NodeFlow { md.ActingInstance ??= context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType); } + object?[]? parameters = singleFlipFlopNode.GetParameters(context, singleFlipFlopNode.MethodDetails); // 启动全局触发器时获取入参参数 // 设置委托对象 var func = md.ExplicitDatas.Length == 0 ? (Func>)del : @@ -267,49 +271,13 @@ namespace Serein.NodeFlow { while (!FlipFlopCts.IsCancellationRequested) { - object?[]? parameters = singleFlipFlopNode.GetParameters(context, singleFlipFlopNode.MethodDetails); // 启动全局触发器时获取入参参数 - IFlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters);// 首先开始等待触发器 - _ = GlobalFlipflopExecute(singleFlipFlopNode, context); + IFlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters);// 开始等待全局触发器的触发 + var connectionType = flipflopContext.State.ToContentType(); + if (connectionType != ConnectionType.None) + { + await GlobalFlipflopExecute(context, singleFlipFlopNode, connectionType); + } } - - - //while (!FlipFlopCts.IsCancellationRequested) - //{ - // if (singleFlipFlopNode.NotExitPreviousNode() == false) - // { - // break; - // } - - // object?[]? parameters = singleFlipFlopNode.GetParameters(context, md); - // if (md.ActingInstance == null) - // { - // md.ActingInstance = context.SereinIoc.GetOrRegisterInstantiate(md.ActingInstanceType); - // } - - // IFlipflopContext flipflopContext = await func.Invoke(md.ActingInstance, parameters); - // ConnectionType connection = flipflopContext.State.ToContentType(); - - // if (connection != ConnectionType.None) - // { - // singleFlipFlopNode.NextOrientation = connection; - // singleFlipFlopNode.FlowData = flipflopContext.Data; - - // var tasks = singleFlipFlopNode.SuccessorNodes.Values - // .SelectMany(nodeList => nodeList) - // .Select(nextNode => - // { - // var nextContext = new DynamicContext(SereinIOC, flowEnvironment); - // nextNode.PreviousNode = singleFlipFlopNode; - // return nextNode.StartExecution(nextContext); // 全局触发器收到信号,开始执行 - // }).ToArray(); - - // await Task.WhenAll(tasks); - // } - // else - // { - // break; - // } - //} } catch (Exception ex) { @@ -317,18 +285,25 @@ namespace Serein.NodeFlow } } - public async Task GlobalFlipflopExecute(SingleFlipflopNode singleFlipFlopNode, IDynamicContext context) + /// + /// 全局触发器开始执行相关分支 + /// + /// 上下文 + /// 被触发的全局触发器 + /// 分支类型 + /// + public async Task GlobalFlipflopExecute(IDynamicContext context, SingleFlipflopNode singleFlipFlopNode, ConnectionType connectionType) { - if (FlipFlopCts.IsCancellationRequested) + if (FlipFlopCts.IsCancellationRequested ) { return; } + bool skip = true; var cts = context.SereinIoc.GetOrRegisterInstantiate(); Stack stack = new Stack(); stack.Push(singleFlipFlopNode); - ConnectionType connectionType = ConnectionType.IsSucceed; while (stack.Count > 0 && !cts.IsCancellationRequested) // 循环中直到栈为空才会退出循环 { @@ -346,7 +321,7 @@ namespace Serein.NodeFlow for (int i = upstreamNodes.Count - 1; i >= 0; i--) { upstreamNodes[i].PreviousNode = currentNode; - await upstreamNodes[i].StartExecution(context); // 执行上游分支 + await upstreamNodes[i].StartExecute(context); // 执行全局触发器的上游分支 } // 当前节点是已经触发了的全局触发器,所以跳过,难道每次都要判断一次? @@ -356,15 +331,8 @@ namespace Serein.NodeFlow } else { - // 判断是否为触发器节点,如果是,则开始等待。 - if (currentNode.MethodDetails != null && currentNode.MethodDetails.MethodDynamicType == NodeType.Flipflop) - { - currentNode.FlowData = await currentNode.ExecuteAsync(context); // 流程中遇到了触发器 - } - else - { - currentNode.FlowData = currentNode.Execute(context); // 流程中正常执行 - } + currentNode.FlowData = await currentNode.ExecutingAsync(context); + if (currentNode.NextOrientation == ConnectionType.None) { break; // 不再执行 diff --git a/NodeFlow/Model/CompositeActionNode.cs b/NodeFlow/Model/CompositeActionNode.cs index e422a44..a6c19fb 100644 --- a/NodeFlow/Model/CompositeActionNode.cs +++ b/NodeFlow/Model/CompositeActionNode.cs @@ -20,7 +20,8 @@ namespace Serein.NodeFlow.Model ActionNodes = actionNodes; } - public override object? Execute(IDynamicContext context) + //public override async Task Executing(IDynamicContext context) + public override Task ExecutingAsync(IDynamicContext context) { throw new NotImplementedException("动作区域暂未实现"); } diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index ee9ba04..2aee3e8 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -24,7 +24,8 @@ namespace Serein.NodeFlow.Model /// /// /// - public override object? Execute(IDynamicContext context) + //public override object? Executing(IDynamicContext context) + public override Task ExecutingAsync(IDynamicContext context) { // 条件区域中遍历每个条件节点 foreach (SingleConditionNode? node in ConditionNodes) @@ -37,7 +38,7 @@ namespace Serein.NodeFlow.Model break; } } - return PreviousNode?.FlowData; + return Task.FromResult( PreviousNode?.FlowData); } @@ -45,7 +46,7 @@ namespace Serein.NodeFlow.Model { try { - node.Execute(context); + node.ExecutingAsync(context); return node.NextOrientation; } catch (Exception ex) diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 8082fa1..8f82ffe 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -28,7 +28,8 @@ namespace Serein.NodeFlow.Model public string Expression { get; set; } - public override object? Execute(IDynamicContext context) + //public override object? Executing(IDynamicContext context) + public override Task ExecutingAsync(IDynamicContext context) { // 接收上一节点参数or自定义参数内容 object? result; @@ -52,7 +53,7 @@ namespace Serein.NodeFlow.Model } Console.WriteLine($"{result} {Expression} -> " + NextOrientation); - return result; + return Task.FromResult(result); } internal override Parameterdata[] GetParameterdatas() diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 0900e5a..3faaaf5 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -18,7 +18,8 @@ namespace Serein.NodeFlow.Model public string Expression { get; set; } - public override object? Execute(IDynamicContext context) + //public override async Task Executing(IDynamicContext context) + public override Task ExecutingAsync(IDynamicContext context) { var data = PreviousNode?.FlowData; @@ -37,13 +38,13 @@ namespace Serein.NodeFlow.Model } NextOrientation = ConnectionType.IsSucceed; - return result; + return Task.FromResult(result); } catch (Exception ex) { NextOrientation = ConnectionType.IsError; RuningException = ex; - return PreviousNode?.FlowData; + return Task.FromResult(PreviousNode?.FlowData); } } diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index 76ad23b..edd0b8f 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -1,18 +1,67 @@ using Serein.Library.Api; using Serein.Library.Entity; +using Serein.Library.Enums; using Serein.Library.Ex; using Serein.NodeFlow.Base; +using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.NodeFlow.Model { public class SingleFlipflopNode : NodeModelBase { - public override object? Execute(IDynamicContext context) + //public override async Task Executing(IDynamicContext context) + //public override Task ExecutingAsync(IDynamicContext context) + //{ + // NextOrientation = Library.Enums.ConnectionType.IsError; + // RuningException = new FlipflopException ("无法以非await/async的形式调用触发器"); + // return null; + //} + + + public override async Task ExecutingAsync(IDynamicContext context) { - NextOrientation = Library.Enums.ConnectionType.IsError; - RuningException = new FlipflopException ("无法以非await/async的形式调用触发器"); - return null; + #region 执行前中断 + if (TryCreateInterruptTask(context, this, out Task? task)) + { + var cancelType = await task!; + await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支"); + } + #endregion + + MethodDetails md = MethodDetails; + Delegate del = md.MethodDelegate; + object instance = md.ActingInstance; + var haveParameter = md.ExplicitDatas.Length >= 0; + try + { + // 调用委托并获取结果 + Task flipflopTask = haveParameter switch + { + true => ((Func>)del).Invoke(instance, GetParameters(context, md)), // 执行流程中的触发器方法时获取入参参数 + false => ((Func>)del).Invoke(instance), + }; + + IFlipflopContext flipflopContext = (await flipflopTask) ?? throw new FlipflopException("没有返回上下文"); + NextOrientation = flipflopContext.State.ToContentType(); + if(flipflopContext.TriggerData.Type == Library.NodeFlow.Tool.TriggerType.Overtime) + { + throw new FlipflopException(""); + } + return flipflopContext.TriggerData.Value; + } + catch (FlipflopException ex) + { + NextOrientation = ConnectionType.None; + RuningException = ex; + throw; + } + catch (Exception ex) + { + NextOrientation = ConnectionType.IsError; + RuningException = ex; + return null; + } } internal override Parameterdata[] GetParameterdatas() diff --git a/WorkBench/LogWindow.xaml.cs b/WorkBench/LogWindow.xaml.cs index 4595c67..85e7e16 100644 --- a/WorkBench/LogWindow.xaml.cs +++ b/WorkBench/LogWindow.xaml.cs @@ -11,6 +11,7 @@ namespace Serein.WorkBench { InitializeComponent(); } + public void AppendText(string text) { Dispatcher.BeginInvoke(() => @@ -19,6 +20,13 @@ namespace Serein.WorkBench LogTextBox.ScrollToEnd(); }); } + public void Clear() + { + Dispatcher.BeginInvoke(() => + { + LogTextBox.Clear(); + }); + } private void ClearLog_Click(object sender, RoutedEventArgs e) { LogTextBox.Clear(); diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index c6cdcac..9f18b27 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -7,6 +7,8 @@ AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver" Loaded="Window_Loaded" ContentRendered="Window_ContentRendered" + PreviewKeyDown="Window_PreviewKeyDown" + PreviewTextInput="Window_PreviewTextInput" Closing="Window_Closing"> @@ -108,7 +110,7 @@ Stroke="Blue" StrokeThickness="2" Fill="LightBlue" - Opacity="0.5" + Opacity="0.2" Panel.ZIndex="999999" Visibility="Collapsed"/> diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index c249a58..6bd27d7 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -92,9 +92,17 @@ namespace Serein.WorkBench private readonly List selectNodeControls = []; /// - /// 记录拖动开始时的鼠标位置 + /// 记录开始拖动节点控件时的鼠标位置 /// - private Point startPoint; + private Point startControlDragPoint; + /// + /// 记录移动画布开始时的鼠标位置 + /// + private Point startCanvasDragPoint; + /// + /// 记录开始选取节点控件时的鼠标位置 + /// + private Point startSelectControolPoint; /// /// 记录开始连接的文本块 @@ -134,8 +142,10 @@ namespace Serein.WorkBench logWindow = new LogWindow(); logWindow.Show(); // 重定向 Console 输出 - var logTextWriter = new LogTextWriter(WriteLog); + var logTextWriter = new LogTextWriter(WriteLog,() => logWindow.Clear());; Console.SetOut(logTextWriter); + + InitUI(); var project = App.FData; @@ -202,7 +212,7 @@ namespace Serein.WorkBench //{ // connection.Refresh(); //} - Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight)); + //Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight)); } /// @@ -517,62 +527,6 @@ namespace Serein.WorkBench #region 节点控件的创建 - private static TControl CreateNodeControl(NodeModelBase model) - where TControl : NodeControlBase - where TViewModel : NodeControlViewModelBase - { - - if (model == null) - { - throw new Exception("无法创建节点控件"); - } - - var viewModel = Activator.CreateInstance(typeof(TViewModel), [model]); - var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]); - if (controlObj is TControl control) - { - return control; - } - else - { - throw new Exception("无法创建节点控件"); - } - } - - private static TControl CreateNodeControl(MethodDetails? methodDetails = null) - where TNode : NodeModelBase - where TControl : NodeControlBase - where TViewModel : NodeControlViewModelBase - { - - var nodeObj = Activator.CreateInstance(typeof(TNode)); - var nodeBase = nodeObj as NodeModelBase; - if (nodeBase == null) - { - throw new Exception("无法创建节点控件"); - } - - - nodeBase.Guid = Guid.NewGuid().ToString(); - - if (methodDetails != null) - { - var md = methodDetails.Clone(); - nodeBase.DisplayName = md.MethodTips; - nodeBase.MethodDetails = md; - } - - var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]); - var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel] ); - if(controlObj is TControl control) - { - return control; - } - else - { - throw new Exception("无法创建节点控件"); - } - } /// /// 创建了节点,添加到画布。配置默认事件 @@ -593,50 +547,6 @@ namespace Serein.WorkBench }); } - /// - /// 配置节点右键菜单 - /// - /// - private void ConfigureContextMenu(NodeControlBase nodeControl) - { - var contextMenu = new ContextMenu(); - - // var nodeModel = nodeControl.ViewModel.Node; - - if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void)) - { - contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) => - { - DisplayReturnTypeTreeViewer(returnType); - })); - } - var nodeGuid = nodeControl?.ViewModel?.Node?.Guid; - contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid))); - contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid))); - - contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed))); - contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail))); - contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError))); - contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream))); - - - nodeControl.ContextMenu = contextMenu; - } - - /// - /// 创建菜单子项 - /// - /// - /// - /// - private static MenuItem CreateMenuItem(string header, RoutedEventHandler handler) - { - var menuItem = new MenuItem { Header = header }; - menuItem.Click += handler; - return menuItem; - } - - /// /// 配置节点事件 /// @@ -682,6 +592,55 @@ namespace Serein.WorkBench #region 右键菜单事件 + /// + /// 配置节点右键菜单 + /// + /// + private void ConfigureContextMenu(NodeControlBase nodeControl) + { + var contextMenu = new ContextMenu(); + + // var nodeModel = nodeControl.ViewModel.Node; + + if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void)) + { + contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) => + { + DisplayReturnTypeTreeViewer(returnType); + })); + } + var nodeGuid = nodeControl?.ViewModel?.Node?.Guid; + + MenuItem? debugMenu = null; + debugMenu = CreateMenuItem("在此中断", (s, e) => + { + if (nodeControl!.ViewModel.DebugSetting.IsInterrupt) + { + nodeControl.ViewModel.IsInterrupt = false; + debugMenu!.Header = "在此中断"; + } + else + { + nodeControl.ViewModel.IsInterrupt = true; + debugMenu!.Header = "取消中断"; + } + }); + contextMenu.Items.Add(debugMenu); + + + contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid))); + contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid))); + + + contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed))); + contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail))); + contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError))); + contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream))); + + + nodeControl.ContextMenu = contextMenu; + } + /// /// 配置连接曲线的右键菜单 /// @@ -693,6 +652,7 @@ namespace Serein.WorkBench connection.ArrowPath.ContextMenu = contextMenu; connection.BezierPath.ContextMenu = contextMenu; } + /// /// 删除该连线 /// @@ -776,24 +736,7 @@ namespace Serein.WorkBench /// private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e) { - if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) // 正在选取节点 - { - // 获取当前鼠标位置 - Point currentPoint = e.GetPosition(FlowChartCanvas); - - // 更新选取矩形的位置和大小 - double x = Math.Min(currentPoint.X, startPoint.X); - double y = Math.Min(currentPoint.Y, startPoint.Y); - double width = Math.Abs(currentPoint.X - startPoint.X); - double height = Math.Abs(currentPoint.Y - startPoint.Y); - - Canvas.SetLeft(SelectionRectangle, x); - Canvas.SetTop(SelectionRectangle, y); - SelectionRectangle.Width = width; - SelectionRectangle.Height = height; - } - - + if (IsConnecting) // 正在连接节点 { Point position = e.GetPosition(FlowChartCanvas); @@ -809,22 +752,41 @@ namespace Serein.WorkBench if (IsCanvasDragging) // 正在移动画布 { Point currentMousePosition = e.GetPosition(this); - double deltaX = currentMousePosition.X - startPoint.X; - double deltaY = currentMousePosition.Y - startPoint.Y; + double deltaX = currentMousePosition.X - startCanvasDragPoint.X; + double deltaY = currentMousePosition.Y - startCanvasDragPoint.Y; translateTransform.X += deltaX; translateTransform.Y += deltaY; - startPoint = currentMousePosition; + startCanvasDragPoint = currentMousePosition; foreach (var line in Connections) { line.Refresh(); } - - e.Handled = true; // 防止事件传播影响其他控件 } + if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) // 正在选取节点 + { + // 获取当前鼠标位置 + Point currentPoint = e.GetPosition(FlowChartCanvas); + + // 更新选取矩形的位置和大小 + double x = Math.Min(currentPoint.X, startSelectControolPoint.X); + double y = Math.Min(currentPoint.Y, startSelectControolPoint.Y); + double width = Math.Abs(currentPoint.X - startSelectControolPoint.X); + double height = Math.Abs(currentPoint.Y - startSelectControolPoint.Y); + /*double x = Math.Min(currentPoint.X, startControlDragPoint.X); + double y = Math.Min(currentPoint.Y, startControlDragPoint.Y); + double width = Math.Abs(currentPoint.X - startControlDragPoint.X); + double height = Math.Abs(currentPoint.Y - startControlDragPoint.Y);*/ + + Canvas.SetLeft(SelectionRectangle, x); + Canvas.SetTop(SelectionRectangle, y); + SelectionRectangle.Width = width; + SelectionRectangle.Height = height; + + } } /// @@ -910,25 +872,6 @@ namespace Serein.WorkBench return false; } - /// - /// 穿透元素获取区域容器 - /// - /// - /// - /// - private static T GetParentOfType(DependencyObject element) where T : DependencyObject - { - while (element != null) - { - if (element is T) - { - return element as T; - } - element = VisualTreeHelper.GetParent(element); - } - return null; - } - /// /// 将节点放在目标区域中 /// @@ -972,12 +915,10 @@ namespace Serein.WorkBench /// private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - if (IsConnecting) - return; - IsControlDragging = true; - startPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置 + startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置 ((UIElement)sender).CaptureMouse(); // 捕获鼠标 + e.Handled = true; // 防止事件传播影响其他控件 } /// @@ -985,7 +926,15 @@ namespace Serein.WorkBench /// private void Block_MouseMove(object sender, MouseEventArgs e) { - if (IsControlDragging) // 如果正在拖动控件 + if (IsConnecting) + return; + if (IsCanvasDragging) + return; + if (IsSelectControl) + return; + + var IsSelect = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); + if (!IsSelect && IsControlDragging) // 如果正在拖动控件 { Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置 // 获取引发事件的控件 @@ -994,8 +943,8 @@ namespace Serein.WorkBench return; } - double deltaX = currentPosition.X - startPoint.X; // 计算X轴方向的偏移量 - double deltaY = currentPosition.Y - startPoint.Y; // 计算Y轴方向的偏移量 + double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量 + double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量 double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距 double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距 @@ -1012,7 +961,7 @@ namespace Serein.WorkBench UpdateConnections(block); - startPoint = currentPosition; // 更新起始点位置 + startControlDragPoint = currentPosition; // 更新起始点位置 } } @@ -1159,13 +1108,14 @@ namespace Serein.WorkBench #region 拖动画布实现缩放平移效果 private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e) { - if (e.MiddleButton == MouseButtonState.Pressed) - { - IsCanvasDragging = true; - startPoint = e.GetPosition(this); - FlowChartCanvas.CaptureMouse(); - e.Handled = true; // 防止事件传播影响其他控件 - } + IsCanvasDragging = true; + startCanvasDragPoint = e.GetPosition(this); + FlowChartCanvas.CaptureMouse(); + e.Handled = true; // 防止事件传播影响其他控件 + //if (e.MiddleButton == MouseButtonState.Pressed) + //{ + + //} } private void FlowChartCanvas_MouseUp(object sender, MouseButtonEventArgs e) @@ -1180,7 +1130,7 @@ namespace Serein.WorkBench // 单纯缩放画布,不改变画布大小 private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e) { - if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) + // if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) { if (e.Delta < 0 && scaleTransform.ScaleX < 0.2) return; if (e.Delta > 0 && scaleTransform.ScaleY > 1.5) return; @@ -1355,26 +1305,29 @@ namespace Serein.WorkBench /// private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) - { - IsSelectControl = true; + IsSelectControl = true; - // 开始选取时,记录鼠标起始点 - startPoint = e.GetPosition(FlowChartCanvas); + // 开始选取时,记录鼠标起始点 + startSelectControolPoint = e.GetPosition(FlowChartCanvas); - // 初始化选取矩形的位置和大小 - Canvas.SetLeft(SelectionRectangle, startPoint.X); - Canvas.SetTop(SelectionRectangle, startPoint.Y); - SelectionRectangle.Width = 0; - SelectionRectangle.Height = 0; + // 初始化选取矩形的位置和大小 + Canvas.SetLeft(SelectionRectangle, startSelectControolPoint.X); + Canvas.SetTop(SelectionRectangle, startSelectControolPoint.Y); + SelectionRectangle.Width = 0; + SelectionRectangle.Height = 0; - // 显示选取矩形 - SelectionRectangle.Visibility = Visibility.Visible; - SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle(); + // 显示选取矩形 + SelectionRectangle.Visibility = Visibility.Visible; + SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle(); - // 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件 - FlowChartCanvas.CaptureMouse(); - } + // 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件 + FlowChartCanvas.CaptureMouse(); + + //if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) + //{ + + //} + e.Handled = true; // 防止事件传播影响其他控件 } private ContextMenu ConfiguerSelectionRectangle() @@ -1448,6 +1401,7 @@ namespace Serein.WorkBench if(selectNodeControls.Count == 0) { Console.WriteLine($"没有选择控件"); + SelectionRectangle.Visibility = Visibility.Collapsed; return; } Console.WriteLine($"一共选取了{selectNodeControls.Count}个控件"); @@ -1455,6 +1409,8 @@ namespace Serein.WorkBench { node.ViewModel.Selected(); node.ViewModel.CancelSelect(); + node.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC700")); + node.BorderThickness = new Thickness(4); } } private void CancelSelectNode() @@ -1462,13 +1418,109 @@ namespace Serein.WorkBench foreach (var node in selectNodeControls) { node.ViewModel.CancelSelect(); + node.BorderBrush = Brushes.Black; + node.BorderThickness = new Thickness(0); } selectNodeControls.Clear(); } #endregion + #region 窗体静态方法 + private static TControl CreateNodeControl(NodeModelBase model) + where TControl : NodeControlBase + where TViewModel : NodeControlViewModelBase + { + + if (model == null) + { + throw new Exception("无法创建节点控件"); + } + + var viewModel = Activator.CreateInstance(typeof(TViewModel), [model]); + var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]); + if (controlObj is TControl control) + { + return control; + } + else + { + throw new Exception("无法创建节点控件"); + } + } + + private static TControl CreateNodeControl(MethodDetails? methodDetails = null) + where TNode : NodeModelBase + where TControl : NodeControlBase + where TViewModel : NodeControlViewModelBase + { + + var nodeObj = Activator.CreateInstance(typeof(TNode)); + var nodeBase = nodeObj as NodeModelBase; + if (nodeBase == null) + { + throw new Exception("无法创建节点控件"); + } + + + nodeBase.Guid = Guid.NewGuid().ToString(); + + if (methodDetails != null) + { + var md = methodDetails.Clone(); + nodeBase.DisplayName = md.MethodTips; + nodeBase.MethodDetails = md; + } + + var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]); + var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]); + if (controlObj is TControl control) + { + return control; + } + else + { + throw new Exception("无法创建节点控件"); + } + } + + + /// + /// 创建菜单子项 + /// + /// + /// + /// + public static MenuItem CreateMenuItem(string header, RoutedEventHandler handler) + { + var menuItem = new MenuItem { Header = header }; + menuItem.Click += handler; + return menuItem; + } + + + + /// + /// 穿透元素获取区域容器 + /// + /// + /// + /// + private static T GetParentOfType(DependencyObject element) where T : DependencyObject + { + while (element != null) + { + if (element is T) + { + return element as T; + } + element = VisualTreeHelper.GetParent(element); + } + return null; + } + + #endregion @@ -1505,7 +1557,11 @@ namespace Serein.WorkBench private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e) { logWindow?.Show(); - await FlowEnvironment.StartAsync(); + + await FlowEnvironment.StartAsync(); // 快 + + //await Task.Run( FlowEnvironment.StartAsync); // 上下文多次切换的场景中吗慢了1/5 + //await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5 } /// @@ -1614,6 +1670,20 @@ namespace Serein.WorkBench Uri relativeUri = baseUri.MakeRelativeUri(fullUri); return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar)); } + + private void Window_PreviewTextInput(object sender, TextCompositionEventArgs e) + { + + } + + private void Window_PreviewKeyDown(object sender, KeyEventArgs e) + { + //if (e.KeyStates == Keyboard.GetKeyStates(Key.D8) && Keyboard.Modifiers == ModifierKeys.Shift) + //if (Keyboard.Modifiers == ModifierKeys.Shift) + //{ + // startSelectControolPoint = e.GetPosition(FlowChartCanvas); + //} + } } #region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线 diff --git a/WorkBench/Node/NodeControlViewModelBase.cs b/WorkBench/Node/NodeControlViewModelBase.cs index 5976375..0e8d1e5 100644 --- a/WorkBench/Node/NodeControlViewModelBase.cs +++ b/WorkBench/Node/NodeControlViewModelBase.cs @@ -23,6 +23,9 @@ namespace Serein.WorkBench.Node.ViewModel /// internal NodeModelBase Node { get; } + + + private bool isSelect; /// /// 表示节点控件是否被选中 @@ -37,29 +40,68 @@ namespace Serein.WorkBench.Node.ViewModel } } - private MethodDetails methodDetails; - - public MethodDetails MethodDetails + public NodeDebugSetting DebugSetting { - get => methodDetails; + get => Node.DebugSetting; set { - methodDetails = value; - OnPropertyChanged(); + if (value != null) + { + Node.DebugSetting = value; + OnPropertyChanged(nameof(DebugSetting)); + } } } + public MethodDetails MethodDetails + { + get => Node.MethodDetails; + set + { + if(value != null) + { + Node.MethodDetails = value; + OnPropertyChanged(nameof(MethodDetails)); + } + } + } + + public bool IsInterrupt + { + get => Node.DebugSetting.IsInterrupt; + set + { + if (value) + { + Node.Interrupt(); + } + else + { + Node.CancelInterrupt(); + } + OnPropertyChanged(nameof(IsInterrupt)); + } + } + + //public bool IsProtectionParameter + //{ + // get => MethodDetails.IsProtectionParameter; + // set + // { + // MethodDetails.IsProtectionParameter = value; + // OnPropertyChanged(nameof(IsInterrupt)); + // } + //} + public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) - { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - /// - /// - /// + + public void Selected() { IsSelect = true; @@ -69,5 +111,7 @@ namespace Serein.WorkBench.Node.ViewModel { IsSelect = false; } + + } } diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml index 6c968fe..b6c1ae5 100644 --- a/WorkBench/Node/View/ActionNodeControl.xaml +++ b/WorkBench/Node/View/ActionNodeControl.xaml @@ -5,43 +5,73 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Serein.WorkBench.Node.View" xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel" + xmlns:Converters="clr-namespace:Serein.WorkBench.Tool.Converters" xmlns:themes="clr-namespace:Serein.WorkBench.Themes" MaxWidth="300"> + + + + - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WorkBench/Node/View/ActionRegionControl.xaml b/WorkBench/Node/View/ActionRegionControl.xaml index 9040307..236d16d 100644 --- a/WorkBench/Node/View/ActionRegionControl.xaml +++ b/WorkBench/Node/View/ActionRegionControl.xaml @@ -6,7 +6,7 @@ xmlns:local="clr-namespace:Serein.WorkBench.Node.View" MaxWidth="300"> - + diff --git a/WorkBench/Node/View/ConditionNodeControl.xaml b/WorkBench/Node/View/ConditionNodeControl.xaml index 2b09c93..92adc6d 100644 --- a/WorkBench/Node/View/ConditionNodeControl.xaml +++ b/WorkBench/Node/View/ConditionNodeControl.xaml @@ -8,9 +8,7 @@ xmlns:themes="clr-namespace:Serein.WorkBench.Themes" MaxWidth="300"> - - diff --git a/WorkBench/Themes/TypeViewerWindow.xaml.cs b/WorkBench/Themes/TypeViewerWindow.xaml.cs index 631d5aa..3354ff3 100644 --- a/WorkBench/Themes/TypeViewerWindow.xaml.cs +++ b/WorkBench/Themes/TypeViewerWindow.xaml.cs @@ -20,9 +20,7 @@ namespace Serein.WorkBench.Themes /// public partial class TypeViewerWindow : Window { - public TypeViewerWindow() - { InitializeComponent(); } @@ -34,55 +32,244 @@ namespace Serein.WorkBench.Themes if (Type == null) return; - var rootNode = new TreeViewItem { Header = Type.Name }; - AddMembersToTreeNode(rootNode, Type); + TypeNodeDetails typeNodeDetails = new TypeNodeDetails + { + Name = Type.Name, + DataType = Type, + }; + var rootNode = new TreeViewItem { Header = Type.Name, Tag = typeNodeDetails }; + AddPlaceholderNode(rootNode); // 添加占位符节点 TypeTreeView.Items.Clear(); TypeTreeView.Items.Add(rootNode); + + rootNode.Expanded += TreeViewItem_Expanded; // 监听节点展开事件 + } + + /// + /// 添加占位符节点 + /// + private void AddPlaceholderNode(TreeViewItem node) + { + node.Items.Add(new TreeViewItem { Header = "Loading..." }); + } + + /// + /// 节点展开事件,延迟加载子节点 + /// + private void TreeViewItem_Expanded(object sender, RoutedEventArgs e) + { + var item = (TreeViewItem)sender; + + // 如果已经加载过子节点,则不再重复加载 + if (item.Items.Count == 1 && item.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...") + { + item.Items.Clear(); + if (item.Tag is TypeNodeDetails typeNodeDetails) + { + AddMembersToTreeNode(item, typeNodeDetails.DataType); + } + + } + + } /// /// 添加属性节点 /// - /// - /// private void AddMembersToTreeNode(TreeViewItem node, Type type) { var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); foreach (var member in members) { - TreeViewItem memberNode; - try + TreeViewItem memberNode = ConfigureTreeViewItem(member); // 生成类型节点的子项 + if (ConfigureTreeItemMenu(memberNode,member, out ContextMenu? contextMenu)) { - memberNode = new TreeViewItem { Header = member.Name }; - } - catch - { - return; - } - - if (member is PropertyInfo property) - { - var propertyType = property.PropertyType; - memberNode.Header = $"{member.Name} : {propertyType.Name}"; - if (!propertyType.IsPrimitive && propertyType != typeof(string)) - { - // 递归显示类型属性的节点 - AddMembersToTreeNode(memberNode, propertyType); - } - } - else if (member is MethodInfo method) - { - var parameters = method.GetParameters(); - var paramStr = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}")); - memberNode.Header = $"{member.Name}({paramStr})"; - } - else if (member is FieldInfo field) - { - memberNode.Header = $"{member.Name} : {field.FieldType.Name}"; + memberNode.ContextMenu = contextMenu; // 设置子项节点的事件 } - node.Items.Add(memberNode); + node.Items.Add(memberNode); // 添加到父节点中 } } + + + /// + /// 生成类型节点的子项 + /// + /// + /// + private TreeViewItem ConfigureTreeViewItem(MemberInfo member) + { + TreeViewItem memberNode = new TreeViewItem { Header = member.Name }; + if (member is PropertyInfo property) + { + TypeNodeDetails typeNodeDetails = new TypeNodeDetails + { + ItemType = TreeItemType.Property, + DataType = property.PropertyType, + Name = property.Name, + DataValue = property, + }; + memberNode.Tag = typeNodeDetails; + + var propertyType = typeNodeDetails.DataType; + memberNode.Header = $"{member.Name} : {propertyType.Name}"; + + if (!propertyType.IsPrimitive && propertyType != typeof(string)) + { + // 延迟加载类型的子属性,添加占位符节点 + AddPlaceholderNode(memberNode); + memberNode.Expanded += TreeViewItem_Expanded; // 监听展开事件 + } + } + else if (member is MethodInfo method) + { + TypeNodeDetails typeNodeDetails = new TypeNodeDetails + { + ItemType = TreeItemType.Method, + DataType = typeof(MethodInfo), + Name = method.Name, + DataValue = null, + }; + memberNode.Tag = typeNodeDetails; + + var parameters = method.GetParameters(); + var paramStr = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}")); + memberNode.Header = $"{member.Name}({paramStr})"; + } + else if (member is FieldInfo field) + { + TypeNodeDetails typeNodeDetails = new TypeNodeDetails + { + ItemType = TreeItemType.Field, + DataType = field.FieldType, + Name = field.Name, + DataValue = field, + }; + memberNode.Tag = typeNodeDetails; + memberNode.Header = $"{member.Name} : {field.FieldType.Name}"; + } + return memberNode; + } + + + /// + /// 设置子项节点的事件 + /// + /// + /// + private bool ConfigureTreeItemMenu(TreeViewItem memberNode, MemberInfo member,out ContextMenu? contextMenu) + { + bool isChange = false; + if (member is PropertyInfo property) + { + //isChange = true; + contextMenu = new ContextMenu(); + } + else if (member is MethodInfo method) + { + //isChange = true; + contextMenu = new ContextMenu(); + } + else if (member is FieldInfo field) + { + isChange = true; + contextMenu = new ContextMenu(); + contextMenu.Items.Add(MainWindow.CreateMenuItem($"取值表达式", (s, e) => + { + string fullPath = GetNodeFullPath(memberNode); + string copyValue = "@Get " + fullPath; + Clipboard.SetDataObject(copyValue); + })); + } + else + { + contextMenu = new ContextMenu(); + } + return isChange; + } + + + + /// + /// 获取当前节点的完整路径,例如 "node1.node2.node3.node4" + /// + /// 目标节点 + /// 节点路径 + private string GetNodeFullPath(TreeViewItem node) + { + if (node == null) + return string.Empty; + + TypeNodeDetails typeNodeDetails = (TypeNodeDetails)node.Tag; + var parent = GetParentTreeViewItem(node); + if (parent != null) + { + // 递归获取父节点的路径,并拼接当前节点的 Header + return $"{GetNodeFullPath(parent)}.{typeNodeDetails.Name}"; + } + else + { + return ""; + + + // 没有父节点,则说明这是根节点,直接返回 Header + // return typeNodeDetails.Name.ToString(); + } + } + + /// + /// 获取指定节点的父级节点 + /// + /// 目标节点 + /// 父节点 + private TreeViewItem GetParentTreeViewItem(TreeViewItem node) + { + DependencyObject parent = VisualTreeHelper.GetParent(node); + while (parent != null && !(parent is TreeViewItem)) + { + parent = VisualTreeHelper.GetParent(parent); + } + return parent as TreeViewItem; + } + + + + public class TypeNodeDetails + { + /// + /// 属性名称 + /// + public string Name { get; set; } + /// + /// 属性类型 + /// + public TreeItemType ItemType { get; set; } + + + /// + /// 数据类型 + /// + public Type DataType { get; set; } + /// + /// 数据(调试用?) + /// + public object DataValue { get; set; } + /// + /// 数据路径 + /// + public string DataPath { get; set; } + } + + public enum TreeItemType + { + Property, + Method, + Field + } + + + } + } diff --git a/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs b/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs new file mode 100644 index 0000000..39b5d18 --- /dev/null +++ b/WorkBench/Tool/Converters/InvertableBooleanToVisibilityConverter.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using System.Windows; + +namespace Serein.WorkBench.Tool.Converters +{ + [ValueConversion(typeof(bool), typeof(Visibility))] + public class InvertableBooleanToVisibilityConverter : IValueConverter + { + enum Parameters + { + Normal, Inverted + } + + public object Convert(object value, Type targetType, + object parameter, CultureInfo culture) + { + var boolValue = (bool)value; + var direction = (Parameters)Enum.Parse(typeof(Parameters), (string)parameter); + + if (direction == Parameters.Inverted) + return !boolValue ? Visibility.Visible : Visibility.Collapsed; + + return boolValue ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, + object parameter, CultureInfo culture) + { + return null; + } + } +} diff --git a/WorkBench/tool/LogTextWriter.cs b/WorkBench/tool/LogTextWriter.cs index 0608e31..13d0575 100644 --- a/WorkBench/tool/LogTextWriter.cs +++ b/WorkBench/tool/LogTextWriter.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Collections.Concurrent; +using System.IO; using System.Text; namespace Serein.WorkBench.tool @@ -6,10 +7,24 @@ namespace Serein.WorkBench.tool /// /// 可以捕获类库输出的打印输出 /// - public class LogTextWriter(Action logAction) : TextWriter + public class LogTextWriter : TextWriter { - private readonly Action logAction = logAction; + private readonly Action logAction; private readonly StringWriter stringWriter = new(); + private readonly BlockingCollection logQueue = new(); + private readonly Task logTask; + + // 用于计数的字段 + private int writeCount = 0; + private const int maxWrites = 500; + private readonly Action clearTextBoxAction; + + public LogTextWriter(Action logAction, Action clearTextBoxAction) + { + this.logAction = logAction; + this.clearTextBoxAction = clearTextBoxAction; + logTask = Task.Run(ProcessLogQueue); // 异步处理日志 + } public override Encoding Encoding => Encoding.UTF8; @@ -18,28 +33,60 @@ namespace Serein.WorkBench.tool stringWriter.Write(value); if (value == '\n') { - logAction(stringWriter.ToString()); - stringWriter.GetStringBuilder().Clear(); + EnqueueLog(); } } public override void Write(string? value) { - if(string.IsNullOrWhiteSpace(value)) { return; } + if (string.IsNullOrWhiteSpace(value)) return; stringWriter.Write(value); if (value.Contains('\n')) { - logAction(stringWriter.ToString()); - stringWriter.GetStringBuilder().Clear(); + EnqueueLog(); } } public override void WriteLine(string? value) { - if (string.IsNullOrWhiteSpace(value)) { return; } + if (string.IsNullOrWhiteSpace(value)) return; stringWriter.WriteLine(value); - logAction(stringWriter.ToString()); + EnqueueLog(); + } + + private void EnqueueLog() + { + logQueue.Add(stringWriter.ToString()); stringWriter.GetStringBuilder().Clear(); } + + private async Task ProcessLogQueue() + { + foreach (var log in logQueue.GetConsumingEnumerable()) + { + // 异步执行日志输出操作 + await Task.Run(() => + { + logAction(log); + + // 计数器增加 + writeCount++; + if (writeCount >= maxWrites) + { + // 计数器达到50,清空文本框 + clearTextBoxAction?.Invoke(); + writeCount = 0; // 重置计数器 + } + }); + } + } + + public new void Dispose() + { + logQueue.CompleteAdding(); + logTask.Wait(); + base.Dispose(); + } } + }