From 74961fa2c4d86a9f5732b5d9ff1020df9d970d99 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Mon, 28 Jul 2025 17:38:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=81=E7=A8=8B=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E8=B0=83=E7=94=A8=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Api/IFlowContext.cs | 195 +++++++++++++++++- Library/Api/IFlowEnvironment.cs | 8 +- Library/Api/INodeContainer.cs | 2 + Library/Extension/FlowModelExtension.cs | 56 ++++- Library/FlowNode/FlowContext.cs | 106 +++++----- .../FlowNode/LightweightFlowEnvironment.cs | 1 - Library/FlowNode/ParameterDetails.cs | 22 +- Library/Utils/BenchmarkHelpers.cs | 34 +-- NodeFlow/Env/FlowControl.cs | 9 +- NodeFlow/Env/FlowEdit.cs | 4 +- NodeFlow/Env/FlowEnvironment.cs | 24 +-- NodeFlow/Model/Node/NodeModelBaseFunc.cs | 2 - NodeFlow/Model/Node/SingleGlobalDataNode.cs | 9 +- .../Operation/ContainerPlaceNodeOperation.cs | 28 ++- .../ContainerTakeOutNodeOperation.cs | 26 ++- NodeFlow/Services/FlowWorkManagement.cs | 65 +++++- Workbench/Node/NodeControlBase.cs | 2 +- Workbench/Node/View/GlobalDataControl.xaml | 2 +- Workbench/Node/View/GlobalDataControl.xaml.cs | 4 + Workbench/Services/FlowNodeService.cs | 6 +- Workbench/Views/BaseNodesView.xaml | 2 +- Workbench/Views/FlowCanvasView.xaml.cs | 17 +- 22 files changed, 480 insertions(+), 144 deletions(-) diff --git a/Library/Api/IFlowContext.cs b/Library/Api/IFlowContext.cs index 116d95d..fe35e20 100644 --- a/Library/Api/IFlowContext.cs +++ b/Library/Api/IFlowContext.cs @@ -2,6 +2,9 @@ using Serein.Library.Utils; using System; using System.Collections.Generic; +using System.Data.Common; +using System.Globalization; +using System.Linq; using System.Threading.Tasks; namespace Serein.Library.Api @@ -11,6 +14,12 @@ namespace Serein.Library.Api /// public interface IFlowContext { + /// + /// 是否记录流程信息 + /// + bool IsRecordInvokeInfo { get; set; } + + /// /// 标识流程 /// @@ -39,6 +48,19 @@ namespace Serein.Library.Api Exception ExceptionOfRuning { get; set; } + /// + /// 获取当前流程上下文的所有节点调用信息,包含每个节点的执行时间、调用类型、执行状态等。 + /// + /// + List GetAllInvokeInfos(); + + + /// + /// 新增当前流程上下文的节点调用信息。 + /// + FlowInvokeInfo NewInvokeInfo(IFlowNode previousNode, IFlowNode theNode, FlowInvokeInfo.InvokeType invokeType); + + /// /// 获取节点的运行时参数数据 /// @@ -107,5 +129,176 @@ namespace Serein.Library.Api /// 用以提前结束当前上下文流程的运行 /// void Exit(); - } + } + + + /// + /// 流程调用信息,记录每个节点的调用信息,包括执行时间、调用类型、执行状态等。 + /// + public sealed class FlowInvokeInfo + { + /// + /// 调用类型枚举,指示节点的调用方式。 + /// + public enum InvokeType + { + /// + /// 初始化。 + /// + None = -1, + /// + /// 上游分支调用,指向上游流程的节点。 + /// + Upstream = 0, + /// + /// 真分支调用,指示节点执行成功后的分支。 + /// + IsSucceed = 1, + /// + /// 假分支调用,指示节点执行失败后的分支。 + /// + IsFail = 2, + /// + /// 异常发生分支调用,指示节点执行过程中发生异常后的分支。 + /// + IsError = 3, + /// + /// 参数来源调用,标明此次调用出自其它节点需要参数时的执行。 + /// + ArgSource = 4, + } + + /// + /// 运行状态枚举,指示节点的执行结果。 + /// + public enum RunState + { + /// + /// 初始化 + /// + None = -1, + /// + /// 正在运行,指示节点正在执行中。 + /// + Running = 0, + /// + /// 执行成功,指示节点执行完成且结果正常。 + /// + Succeed = 1, + /// + /// 执行失败,指示节点执行完成但结果异常或不符合预期。 + /// + Failed = 2, + /// + /// 执行异常,指示节点在执行过程中发生了未处理的异常。 + /// + Error = 3, + } + + /// + /// 流程上下文标识符,唯一标识一个流程上下文 + /// + public string ContextGuid { get; set; } + + /// + /// 节点执行信息标识符,唯一标识一个节点执行信息 + /// + public long Id { get; set; } + + /// + /// 上一节点的唯一标识符,指向流程图中的上一个节点 + /// + public string PreviousNodeGuid { get; set; } + + /// + /// 节点唯一标识符,指向流程图中的节点 + /// + public string NodeGuid { get; set; } + + /// + /// 执行时间,记录节点执行的时间戳 + /// + public DateTime StateTime { get; } = DateTime.Now; + + /// + /// 节点调用类型,指示节点的调用方式 + /// + public InvokeType Type { get; set; } + + + + /// + /// 节点方法名称,指示节点执行的方法 + /// + public string Method { get; set; } + + /// + /// 节点执行状态,指示节点的执行结果 + /// + public RunState State { get; set; } + + /// + /// 耗时,记录节点执行的耗时(单位:毫秒) + /// + public TimeSpan TS { get; private set; } = TimeSpan.Zero; + + /// + /// 节点参数,存储节点执行时的参数信息 + /// + public string[] Parameters { get; private set; } + + /// + /// 节点执行结果,存储节点执行后的结果信息 + /// + public string Result { get; private set; } + + public void UploadState(RunState runState) + { + State = runState; + TS = DateTime.Now - StateTime; + } + public void UploadResultValue(object value = null) + { + if(value is null) + { + Result = string.Empty; + } + else + { + Result = value.ToString(); + } + } + public void UploadParameters(object[] values = null) + { + if (values is null) + { + Parameters = []; + + } + else + { + Parameters = values.Select(v => v.ToString()).ToArray(); + } + } + + public override string ToString() + { + return $"[{State}]{TS.TotalSeconds:0.000}ms : {Result}"; + } + } + + + + + + + + + + + + + } + + diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index fe5e0ba..d8eaa04 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -414,10 +414,11 @@ namespace Serein.Library.Api /// public class NodeTakeOutEventArgs : FlowEventArgs { - public NodeTakeOutEventArgs(string canvasGuid, string nodeGuid) + public NodeTakeOutEventArgs(string canvasGuid, string containerNodeGuid, string nodeGuid) { CanvasGuid = canvasGuid; NodeGuid = nodeGuid; + ContainerNodeGuid = containerNodeGuid; } public string CanvasGuid { get; } @@ -426,6 +427,11 @@ namespace Serein.Library.Api /// 需要取出的节点Guid /// public string NodeGuid { get; private set; } + + /// + /// 容器节点Guid + /// + public string ContainerNodeGuid { get; private set; } } diff --git a/Library/Api/INodeContainer.cs b/Library/Api/INodeContainer.cs index cdd8198..ebdc398 100644 --- a/Library/Api/INodeContainer.cs +++ b/Library/Api/INodeContainer.cs @@ -11,6 +11,8 @@ namespace Serein.Library.Api /// public interface INodeContainer { + + string Guid { get; } /// /// 放置一个节点 /// diff --git a/Library/Extension/FlowModelExtension.cs b/Library/Extension/FlowModelExtension.cs index 3320261..4defd34 100644 --- a/Library/Extension/FlowModelExtension.cs +++ b/Library/Extension/FlowModelExtension.cs @@ -208,7 +208,9 @@ namespace Serein.Library Stack stack = new Stack(); HashSet processedNodes = new HashSet(); // 用于记录已处理上游节点的节点 stack.Push(nodeModel); - +#nullable enable + IFlowNode? previousNode = null; + IFlowNode? currentNode = null; while (true) { if (token.IsCancellationRequested) @@ -218,9 +220,30 @@ namespace Serein.Library #region 执行相关 // 从栈中弹出一个节点作为当前节点进行处理 - var currentNode = stack.Pop(); + previousNode = currentNode; + currentNode = stack.Pop(); + + + + #region 新增调用信息 + FlowInvokeInfo? invokeInfo = null; + var isRecordInvokeInfo = context.IsRecordInvokeInfo; + if (!isRecordInvokeInfo) goto Label_NotRecordInvoke; + + FlowInvokeInfo.InvokeType invokeType = context.NextOrientation switch + { + ConnectionInvokeType.IsSucceed => FlowInvokeInfo.InvokeType.IsSucceed, + ConnectionInvokeType.IsFail => FlowInvokeInfo.InvokeType.IsFail, + ConnectionInvokeType.IsError => FlowInvokeInfo.InvokeType.IsError, + ConnectionInvokeType.Upstream => FlowInvokeInfo.InvokeType.Upstream, + _ => FlowInvokeInfo.InvokeType.None + }; + invokeInfo = context.NewInvokeInfo(previousNode, currentNode, invokeType); + #endregion + +Label_NotRecordInvoke: context.NextOrientation = ConnectionInvokeType.None; // 重置上下文状态 - FlowResult flowResult = null; + FlowResult flowResult = null; try { flowResult = await currentNode.ExecutingAsync(context, token); @@ -239,6 +262,22 @@ namespace Serein.Library } #endregion + #region 更新调用信息 + var state = context.NextOrientation switch + { + ConnectionInvokeType.IsFail => FlowInvokeInfo.RunState.Failed, + ConnectionInvokeType.IsError => FlowInvokeInfo.RunState.Error, + _ => FlowInvokeInfo.RunState.Succeed + }; + if (isRecordInvokeInfo) + { + invokeInfo.UploadState(state); + invokeInfo.UploadResultValue(flowResult.Value); + } + + + #endregion + #region 执行完成时更新栈 context.AddOrUpdateFlowData(currentNode.Guid, flowResult); // 上下文中更新数据 @@ -289,10 +328,18 @@ namespace Serein.Library #endregion #if DEBUG - await Task.Delay(1); + //await Task.Delay(1); #endif } } + + /// + /// 获取所有参数 + /// + /// + /// + /// + /// public static async Task GetParametersAsync(this IFlowNode nodeModel, IFlowContext context, CancellationToken token) { var md = nodeModel.MethodDetails; @@ -437,6 +484,7 @@ namespace Serein.Library } var context = new FlowContext(flowCallNode.Env); + for (int index = 0; index < pds.Length; index++) { ParameterDetails pd = pds[index]; diff --git a/Library/FlowNode/FlowContext.cs b/Library/FlowNode/FlowContext.cs index 2ed011e..51f63c4 100644 --- a/Library/FlowNode/FlowContext.cs +++ b/Library/FlowNode/FlowContext.cs @@ -1,8 +1,13 @@ -using Serein.Library.Api; +using Microsoft.VisualBasic; +using Serein.Library.Api; using Serein.Library.Utils; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; +using System.Reactive.Concurrency; +using System.Security.Cryptography.X509Certificates; +using System.Threading; namespace Serein.Library { @@ -24,6 +29,11 @@ namespace Serein.Library } private string _guid = global::System.Guid.NewGuid().ToString(); + + /// + /// 是否记录流程调用信息 + /// + public bool IsRecordInvokeInfo { get; set; } = true; string IFlowContext.Guid => _guid; /// @@ -36,7 +46,6 @@ namespace Serein.Library /// public RunState RunState { get; set; } = RunState.NoStart; - /// /// 当前节点执行完成后,设置该属性,让运行环境判断接下来要执行哪个分支的节点。 /// @@ -66,27 +75,43 @@ namespace Serein.Library /// 记录节点的运行时参数数据 /// private readonly ConcurrentDictionary> dictNodeParams = new ConcurrentDictionary>(); - /* - /// - /// 每个流程上下文分别存放节点的当前数据 - /// - private readonly ConcurrentDictionary dictNodeFlowData = new ConcurrentDictionary(); + + + /// - /// 每个流程上下文存储运行时节点的调用关系 + /// 记录流程调用信息 /// - private readonly ConcurrentDictionary dictPreviousNodes = new ConcurrentDictionary(); + //private Dictionary flowInvokeNodes = new Dictionary(); + private Dictionary flowInvokeInfos = new Dictionary(); + private static long _idCounter = 0; /// - /// 记录忽略处理的流程 + /// 在执行方法之前,获取新的调用信息 /// - private readonly ConcurrentDictionary dictIgnoreNodeFlow = new ConcurrentDictionary(); + /// 上一个节点 + /// 执行节点 + public FlowInvokeInfo NewInvokeInfo(IFlowNode previousNode, IFlowNode theNode, FlowInvokeInfo.InvokeType invokeType) + { + //Interlocked + var id = Interlocked.Increment(ref _idCounter); - /// - /// 记录节点的运行时参数数据 - /// - private readonly ConcurrentDictionary> dictNodeParams = new ConcurrentDictionary>();*/ + FlowInvokeInfo flowInvokeInfo = new FlowInvokeInfo + { + ContextGuid = this._guid, + Id = id, + PreviousNodeGuid = previousNode?.Guid, + Method = theNode.MethodDetails?.MethodName, + NodeGuid = theNode.Guid, + Type = invokeType, + State = FlowInvokeInfo.RunState.None, + }; + flowInvokeInfos.Add(id, flowInvokeInfo); + return flowInvokeInfo; + } + public List GetAllInvokeInfos() => [.. flowInvokeInfos.Values]; + /// /// 设置节点的运行时参数数据 /// @@ -138,8 +163,6 @@ namespace Serein.Library } } - - /// /// 设置运行时上一节点 /// @@ -178,7 +201,6 @@ namespace Serein.Library dictIgnoreNodeFlow.AddOrUpdate(node, (o) => false, (o, n) => false); } - /// /// 获取当前节点的运行时上一节点 /// @@ -252,67 +274,38 @@ namespace Serein.Library throw new InvalidOperationException($"透传{nodeModel}节点数据时发生异常:上一节点不存在数据"); } + /// + /// 开始 + /// + /// /// 重置 /// public void Reset() { - //foreach (var nodeObj in dictNodeFlowData.Values) - //{ - // if (nodeObj is null) - // { - - // } - // else - // { - // if (typeof(IDisposable).IsAssignableFrom(nodeObj?.GetType()) && nodeObj is IDisposable disposable) - // { - // disposable?.Dispose(); - // } - // } - //} - //if (Tag != null && typeof(IDisposable).IsAssignableFrom(Tag?.GetType()) && Tag is IDisposable tagDisposable) - //{ - // tagDisposable?.Dispose(); - //} this.dictNodeFlowData?.Clear(); ExceptionOfRuning = null; NextOrientation = ConnectionInvokeType.None; RunState = RunState.Running; + flowInvokeInfos.Clear(); _guid = global::System.Guid.NewGuid().ToString(); } - /// /// 结束当前流程上下文 /// public void Exit() { - //foreach (var nodeObj in dictNodeFlowData.Values) - //{ - // if (nodeObj is null) - // { - - // } - // else - // { - // if (typeof(IDisposable).IsAssignableFrom(nodeObj?.GetType()) && nodeObj is IDisposable disposable) - // { - // disposable?.Dispose(); - // } - // } - //} - //if (Tag != null && typeof(IDisposable).IsAssignableFrom(Tag?.GetType()) && Tag is IDisposable tagDisposable) - //{ - // tagDisposable?.Dispose(); - //} this.dictNodeFlowData?.Clear(); ExceptionOfRuning = null; NextOrientation = ConnectionInvokeType.None; RunState = RunState.Completion; - _guid = global::System.Guid.NewGuid().ToString(); } + /// + /// 释放当前上下文中的所有资源 + /// + /// private void Dispose(ref IDictionary keyValuePairs) { foreach (var nodeObj in keyValuePairs.Values) @@ -370,7 +363,6 @@ namespace Serein.Library list.Clear(); } - private void Dispose(ref IList list) { foreach (var nodeObj in list) diff --git a/Library/FlowNode/LightweightFlowEnvironment.cs b/Library/FlowNode/LightweightFlowEnvironment.cs index f1c8e1d..495c783 100644 --- a/Library/FlowNode/LightweightFlowEnvironment.cs +++ b/Library/FlowNode/LightweightFlowEnvironment.cs @@ -398,7 +398,6 @@ namespace Serein.Library CancellationTokenSource cts = new CancellationTokenSource(); FlowResult flowResult; #if DEBUG - flowResult = await BenchmarkHelpers.BenchmarkAsync(async () => { var node = flowCallTree.Get(startNodeGuid); diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 648b065..7ce5491 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -249,11 +249,31 @@ namespace Serein.Library sourceNode = sourceNodeTemp; if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) + { + inputParameter = context.GetFlowData(sourceNode.Guid)?.Value; + } else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) // 立刻执行目标节点获取参数 - inputParameter = (await sourceNode.ExecutingAsync(context, CancellationToken.None)).Value; + { + if (context.IsRecordInvokeInfo) + { + var invokeInfo = context.NewInvokeInfo(NodeModel, sourceNode, FlowInvokeInfo.InvokeType.ArgSource); + var result = await sourceNode.ExecutingAsync(context, CancellationToken.None); + inputParameter = result.Value; + invokeInfo.UploadResultValue(result.Value); + invokeInfo.UploadState(FlowInvokeInfo.RunState.Succeed); + } + else + { + var result = await sourceNode.ExecutingAsync(context, CancellationToken.None); + inputParameter = result.Value; + } + } else + { + throw new Exception("无效的 ArgDataSourceType"); + } } // 5. 表达式处理 diff --git a/Library/Utils/BenchmarkHelpers.cs b/Library/Utils/BenchmarkHelpers.cs index 4c61d1d..19d3441 100644 --- a/Library/Utils/BenchmarkHelpers.cs +++ b/Library/Utils/BenchmarkHelpers.cs @@ -37,11 +37,11 @@ namespace Serein.Library.Utils double avg = total / count; - Console.WriteLine($"运行 {count} 次:"); - Console.WriteLine($"总耗时 :{total} 毫秒:"); - Console.WriteLine($"最大耗时:{max} 毫秒"); - Console.WriteLine($"最小耗时:{min} 毫秒"); - Console.WriteLine($"平均耗时:{avg} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"运行 {count} 次:"); + SereinEnv.WriteLine(InfoType.INFO, $"总耗时 :{total} 毫秒:"); + SereinEnv.WriteLine(InfoType.INFO, $"最大耗时:{max} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒"); } /// @@ -68,11 +68,11 @@ namespace Serein.Library.Utils } double avg = total / count; - Console.WriteLine($"运行 {count} 次:"); - Console.WriteLine($"总耗时 :{total} 毫秒:"); - Console.WriteLine($"最大耗时:{max} 毫秒"); - Console.WriteLine($"最小耗时:{min} 毫秒"); - Console.WriteLine($"平均耗时:{avg} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"运行 {count} 次:"); + SereinEnv.WriteLine(InfoType.INFO, $"总耗时 :{total} 毫秒:"); + SereinEnv.WriteLine(InfoType.INFO, $"最大耗时:{max} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒"); } /// @@ -100,11 +100,11 @@ namespace Serein.Library.Utils } double avg = total / count; - Console.WriteLine($"运行 {count} 次:"); - Console.WriteLine($"总耗时 :{total} 毫秒:"); - Console.WriteLine($"最大耗时:{max} 毫秒"); - Console.WriteLine($"最小耗时:{min} 毫秒"); - Console.WriteLine($"平均耗时:{avg} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"运行 {count} 次:"); + SereinEnv.WriteLine(InfoType.INFO, $"总耗时 :{total} 毫秒:"); + SereinEnv.WriteLine(InfoType.INFO, $"最大耗时:{max} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒"); + SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒"); return result; } @@ -127,8 +127,8 @@ namespace Serein.Library.Utils if (ms < min) min = ms; total += ms; - Console.WriteLine($"运行1次耗时 :{total} 毫秒:"); - Debug.WriteLine($"运行1次耗时 :{total} 毫秒:"); + var tips = $"运行耗时 :{total} 毫秒:"; + SereinEnv.WriteLine(InfoType.INFO, tips); return result; } } diff --git a/NodeFlow/Env/FlowControl.cs b/NodeFlow/Env/FlowControl.cs index f808600..922ae99 100644 --- a/NodeFlow/Env/FlowControl.cs +++ b/NodeFlow/Env/FlowControl.cs @@ -185,12 +185,11 @@ namespace Serein.NodeFlow.Env #if DEBUG FlowResult flowResult = await BenchmarkHelpers.BenchmarkAsync(async () => - { - var flowResult = await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel); - return flowResult; - }); + await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel)); #else - FlowResult flowResult = await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel); + //FlowResult flowResult = await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel); + FlowResult flowResult = await BenchmarkHelpers.BenchmarkAsync(async () => await flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel)); + #endif diff --git a/NodeFlow/Env/FlowEdit.cs b/NodeFlow/Env/FlowEdit.cs index 8dfd6d8..6c57779 100644 --- a/NodeFlow/Env/FlowEdit.cs +++ b/NodeFlow/Env/FlowEdit.cs @@ -412,9 +412,7 @@ namespace Serein.NodeFlow.Env { try { - var nodes = canvasModel.Nodes.ToList(); - nodes.Add(nodeModel); - canvasModel.Nodes = nodes; + canvasModel.Nodes = [.. canvasModel.Nodes, nodeModel]; } catch (Exception ex) { diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 6e51104..a5fdd7e 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -146,11 +146,11 @@ namespace Serein.NodeFlow.Env /// public RunState FlowState { get => currentFlowEnvironment.FlowState; set => currentFlowEnvironment.FlowState = value; } - /// + /* /// public void ActivateFlipflopNode(string nodeGuid) { currentFlowEnvironment.FlowControl.ActivateFlipflopNode(nodeGuid); - } + }*/ /* /// public async Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token) @@ -167,10 +167,10 @@ namespace Serein.NodeFlow.Env }*/ /// - public async Task ExitFlowAsync() + /* public async Task ExitFlowAsync() { return await currentFlowEnvironment.FlowControl.ExitFlowAsync(); - } + }*/ /* /// public void ExitRemoteEnv() @@ -221,11 +221,11 @@ namespace Serein.NodeFlow.Env SetProjectLoadingFlag(true); } - /// + /* /// public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType) { currentFlowEnvironment.FlowControl.MonitorObjectNotification(nodeGuid, monitorData, sourceType); - } + }*/ /// public bool TryUnloadLibrary(string assemblyName) @@ -274,7 +274,7 @@ namespace Serein.NodeFlow.Env #endif #endregion - +/* /// public async Task StartFlowAsync(string[] canvasGuids) @@ -286,7 +286,7 @@ namespace Serein.NodeFlow.Env public async Task StartFlowAsync(string startNodeGuid) { return await currentFlowEnvironment.FlowControl.StartFlowAsync(startNodeGuid); - } + }*/ /* /// public async Task StartRemoteServerAsync(int port = 7525) @@ -299,7 +299,7 @@ namespace Serein.NodeFlow.Env { currentFlowEnvironment.StopRemoteServer(); }*/ - +/* /// public void TerminateFlipflopNode(string nodeGuid) { @@ -310,7 +310,7 @@ namespace Serein.NodeFlow.Env public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type) { currentFlowEnvironment.FlowControl.TriggerInterrupt(nodeGuid, expression, type); - } + }*/ /// public void SetUIContextOperation(UIContextOperation uiContextOperation) @@ -318,12 +318,12 @@ namespace Serein.NodeFlow.Env currentFlowEnvironment.SetUIContextOperation(uiContextOperation); } - +/* /// public void UseExternalIOC(ISereinIOC ioc) { currentFlowEnvironment.FlowControl.UseExternalIOC(ioc); - } + }*/ /// public bool TryGetNodeModel(string nodeGuid, out IFlowNode nodeModel) diff --git a/NodeFlow/Model/Node/NodeModelBaseFunc.cs b/NodeFlow/Model/Node/NodeModelBaseFunc.cs index e945d8a..9a4ace4 100644 --- a/NodeFlow/Model/Node/NodeModelBaseFunc.cs +++ b/NodeFlow/Model/Node/NodeModelBaseFunc.cs @@ -40,7 +40,6 @@ namespace Serein.NodeFlow.Model return; } - /// /// 执行节点对应的方法 /// @@ -50,7 +49,6 @@ namespace Serein.NodeFlow.Model /// 节点传回数据对象 public virtual async Task ExecutingAsync(IFlowContext context, CancellationToken token) { - // 执行触发检查是否需要中断 if (DebugSetting.IsInterrupt) { diff --git a/NodeFlow/Model/Node/SingleGlobalDataNode.cs b/NodeFlow/Model/Node/SingleGlobalDataNode.cs index 1fd7ac7..174e81a 100644 --- a/NodeFlow/Model/Node/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/Node/SingleGlobalDataNode.cs @@ -25,6 +25,8 @@ namespace Serein.NodeFlow.Model /// public partial class SingleGlobalDataNode : NodeModelBase, INodeContainer { + string INodeContainer.Guid => this.Guid; + /// /// 全局数据节点是基础节点 /// @@ -58,12 +60,13 @@ namespace Serein.NodeFlow.Model DataNode = nodeModel; return true; } - else + else if (DataNode.Guid != nodeModel.Guid) { - // 全局数据节点只有一个子控件 + Env.FlowEdit.TakeOutNodeToContainer(DataNode.CanvasDetails.Guid, DataNode.Guid); + Env.FlowEdit.PlaceNodeToContainer(this.CanvasDetails.Guid, nodeModel.Guid, this.Guid); return false; } - + return false; } diff --git a/NodeFlow/Model/Operation/ContainerPlaceNodeOperation.cs b/NodeFlow/Model/Operation/ContainerPlaceNodeOperation.cs index 9dc6b4e..be4c65e 100644 --- a/NodeFlow/Model/Operation/ContainerPlaceNodeOperation.cs +++ b/NodeFlow/Model/Operation/ContainerPlaceNodeOperation.cs @@ -78,21 +78,29 @@ namespace Serein.NodeFlow.Model.Operation public override async Task ExecuteAsync() { if (!ValidationParameter()) return false; - - ContainerNode.PlaceNode(Node); - - await TriggerEvent(() => + var isSuccess = ContainerNode.PlaceNode(Node); + if(isSuccess is true) { - flowEnvironmentEvent.OnNodePlace(new NodePlaceEventArgs(CanvasGuid, NodeGuid, ContainerNodeGuid)); // 通知UI更改节点放置位置 - }); - return true; + await TriggerEvent(() => + { + flowEnvironmentEvent.OnNodePlace(new NodePlaceEventArgs(CanvasGuid, NodeGuid, ContainerNodeGuid)); // 通知UI更改节点放置位置 + }); + } + return isSuccess; } public override bool Undo() { - ContainerNode.TakeOutNode(Node); - flowEnvironmentEvent.OnNodeTakeOut(new NodeTakeOutEventArgs(CanvasGuid, NodeGuid)); // 重新放置在画布上 - return true; + var isSuccess = ContainerNode.TakeOutNode(Node); + if (isSuccess is true) + { + _ = TriggerEvent(() => + { + // 取出节点,重新放置在画布上 + flowEnvironmentEvent.OnNodeTakeOut(new NodeTakeOutEventArgs(CanvasGuid, ContainerNode.Guid, NodeGuid)); + }); + } + return isSuccess; } diff --git a/NodeFlow/Model/Operation/ContainerTakeOutNodeOperation.cs b/NodeFlow/Model/Operation/ContainerTakeOutNodeOperation.cs index f6ced50..2b206f4 100644 --- a/NodeFlow/Model/Operation/ContainerTakeOutNodeOperation.cs +++ b/NodeFlow/Model/Operation/ContainerTakeOutNodeOperation.cs @@ -65,23 +65,29 @@ namespace Serein.NodeFlow.Model.Operation { if (!ValidationParameter()) return false; - ContainerNode.TakeOutNode(Node); - await TriggerEvent(() => + var isSuccess = ContainerNode.TakeOutNode(Node); + if (isSuccess is true) { - flowEnvironmentEvent.OnNodeTakeOut(new NodeTakeOutEventArgs(CanvasGuid, NodeGuid)); // 重新放置在画布上 - }); - return true; + await TriggerEvent(() => + { + // 取出节点,重新放置在画布上 + flowEnvironmentEvent.OnNodeTakeOut(new NodeTakeOutEventArgs(CanvasGuid, ContainerNode.Guid, NodeGuid)); + }); + } + return isSuccess; } public override bool Undo() { - ContainerNode.PlaceNode(Node); - if (ContainerNode is IFlowNode containerFlowNode) + var isSuccess = ContainerNode.PlaceNode(Node); + if (isSuccess is true) { - flowEnvironmentEvent.OnNodePlace(new NodePlaceEventArgs(CanvasGuid, NodeGuid, containerFlowNode.Guid)); // 通知UI更改节点放置位置 - + if (ContainerNode is IFlowNode containerFlowNode) + { + flowEnvironmentEvent.OnNodePlace(new NodePlaceEventArgs(CanvasGuid, NodeGuid, containerFlowNode.Guid)); // 通知UI更改节点放置位置 + } } - return true; + return isSuccess; } diff --git a/NodeFlow/Services/FlowWorkManagement.cs b/NodeFlow/Services/FlowWorkManagement.cs index 7821229..abe2f7b 100644 --- a/NodeFlow/Services/FlowWorkManagement.cs +++ b/NodeFlow/Services/FlowWorkManagement.cs @@ -6,6 +6,9 @@ using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Threading.Tasks.Dataflow; using System.Xml.Linq; @@ -21,7 +24,6 @@ namespace Serein.NodeFlow.Services /// private ConcurrentDictionary dictGlobalFlipflop = []; - /// /// 结束运行时需要执行的方法 /// @@ -271,13 +273,65 @@ namespace Serein.NodeFlow.Services /// public async Task StartFlowInSelectNodeAsync(IFlowNode startNode) { - var pool = WorkOptions.FlowContextPool; - var context = pool.Allocate(); - var token = WorkOptions.CancellationTokenSource.Token; - var result = await startNode.StartFlowAsync(context, token); // 开始运行时从选定节点开始运行 + var sw = Stopwatch.StartNew(); + var checkpoints = new Dictionary(); + var pool = WorkOptions.FlowContextPool; + var token = WorkOptions.CancellationTokenSource.Token; + var context = pool.Allocate(); + checkpoints["准备Context"] = sw.Elapsed; + + var result = await startNode.StartFlowAsync(context, token); // 开始运行时从选定节点开始运行 + checkpoints["执行流程"] = sw.Elapsed; + + + if (context.IsRecordInvokeInfo) + { + var invokeInfos = context.GetAllInvokeInfos(); + _ = Task.Delay(100).ContinueWith(async (task) => + { + await task; + if(invokeInfos.Count < 255) + { + foreach (var info in invokeInfos) + { + SereinEnv.WriteLine(InfoType.INFO, info.ToString()); + } + } + else + { + double total = 0; + for (int i = 0; i < invokeInfos.Count; i++) + { + total += invokeInfos[i].TS.TotalSeconds; + } + SereinEnv.WriteLine(InfoType.INFO, $"运行次数:{invokeInfos.Count}"); + SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{total / invokeInfos.Count}"); + SereinEnv.WriteLine(InfoType.INFO, $"总耗时:{total}"); + } + + + }); + + + } + context.Reset(); + checkpoints["重置流程"] = sw.Elapsed; + pool.Free(context); + checkpoints["释放Context"] = sw.Elapsed; + + _ = Task.Run(() => + { + var last = TimeSpan.Zero; + foreach (var kv in checkpoints) + { + SereinEnv.WriteLine(InfoType.INFO, $"{kv.Key} 耗时: {(kv.Value - last).TotalMilliseconds} ms"); + last = kv.Value; + } + }); + return result; } @@ -295,7 +349,6 @@ namespace Serein.NodeFlow.Services } } - /// /// 尝试移除全局触发器 /// diff --git a/Workbench/Node/NodeControlBase.cs b/Workbench/Node/NodeControlBase.cs index f02f7e0..01919a8 100644 --- a/Workbench/Node/NodeControlBase.cs +++ b/Workbench/Node/NodeControlBase.cs @@ -58,7 +58,7 @@ namespace Serein.Workbench.Node.View var result = nodeContainerControl.PlaceNode(this); if (!result) // 检查是否放置成功,如果不成功,需要重新添加回来 { - FlowCanvas.Add(this); // 从画布上移除 + FlowCanvas.Add(this); } } diff --git a/Workbench/Node/View/GlobalDataControl.xaml b/Workbench/Node/View/GlobalDataControl.xaml index 2bbd8b1..f46995b 100644 --- a/Workbench/Node/View/GlobalDataControl.xaml +++ b/Workbench/Node/View/GlobalDataControl.xaml @@ -57,7 +57,7 @@ - + diff --git a/Workbench/Node/View/GlobalDataControl.xaml.cs b/Workbench/Node/View/GlobalDataControl.xaml.cs index 75c8fee..ab292a3 100644 --- a/Workbench/Node/View/GlobalDataControl.xaml.cs +++ b/Workbench/Node/View/GlobalDataControl.xaml.cs @@ -10,6 +10,8 @@ namespace Serein.Workbench.Node.View /// public partial class GlobalDataControl : NodeControlBase, INodeJunction, INodeContainerControl { + private readonly GlobalDataNodeControlViewModel viewModel; + public GlobalDataControl() : base() { // 窗体初始化需要 @@ -26,6 +28,7 @@ namespace Serein.Workbench.Node.View DataContext = viewModel; viewModel.NodeModel.DisplayName = "[全局数据]"; InitializeComponent(); + this.viewModel = viewModel; } @@ -56,6 +59,7 @@ namespace Serein.Workbench.Node.View { return false; } + //viewModel.NodeModel is SingleGlobalDataNode GlobalDataPanel.Children.Add(nodeControl); return true; } diff --git a/Workbench/Services/FlowNodeService.cs b/Workbench/Services/FlowNodeService.cs index 6fd2d6a..73949a5 100644 --- a/Workbench/Services/FlowNodeService.cs +++ b/Workbench/Services/FlowNodeService.cs @@ -261,11 +261,15 @@ namespace Serein.Workbench.Services private void FlowEEForwardingService_OnNodeTakeOut(NodeTakeOutEventArgs eventArgs) { string nodeGuid = eventArgs.NodeGuid; - if (!TryGetControl(nodeGuid, out var nodeControl)) + string containerNodeGuid = eventArgs.ContainerNodeGuid; + if (!TryGetControl(containerNodeGuid, out var containerNodeControl) || !TryGetControl(nodeGuid, out var nodeControl)) { return; } nodeControl.TakeOutContainer(); // 从容器节点中取出 + (double x, double y) = (Canvas.GetLeft(containerNodeControl), Canvas.GetRight(containerNodeControl)); + Canvas.SetLeft(nodeControl, x + 400); + Canvas.SetRight(nodeControl, y + 200); } private void FlowEEForwardingService_OnNodePlace(NodePlaceEventArgs eventArgs) diff --git a/Workbench/Views/BaseNodesView.xaml b/Workbench/Views/BaseNodesView.xaml index 6ce395d..3c0006a 100644 --- a/Workbench/Views/BaseNodesView.xaml +++ b/Workbench/Views/BaseNodesView.xaml @@ -14,7 +14,7 @@ - + diff --git a/Workbench/Views/FlowCanvasView.xaml.cs b/Workbench/Views/FlowCanvasView.xaml.cs index 5a8d95a..d81ae86 100644 --- a/Workbench/Views/FlowCanvasView.xaml.cs +++ b/Workbench/Views/FlowCanvasView.xaml.cs @@ -122,7 +122,7 @@ namespace Serein.Workbench.Views private readonly TranslateTransform translateTransform; #endregion - #region 初始化 + #region 初始化画布与相关事件 public FlowCanvasView(FlowCanvasDetails model) { @@ -173,11 +173,9 @@ namespace Serein.Workbench.Views } - private void InitEvent() { keyEventService.OnKeyDown += KeyEventService_OnKeyDown; - //flowNodeService.OnRemoveConnectionLine += FlowNodeService_OnRemoveConnectionLine; flowEEForwardingService.NodeLocated += FlowEEForwardingService_OnNodeLocated; } @@ -357,8 +355,7 @@ namespace Serein.Workbench.Views } #endregion - - #region 接口实现 + #region 画布节点操作接口实现 private IFlowCanvas Api => this; /// @@ -568,14 +565,20 @@ namespace Serein.Workbench.Views if (keyEventService.GetKeyState(Key.LeftCtrl) || keyEventService.GetKeyState(Key.RightCtrl)) { // Ctrl + F5 调试当前流程 - _ = flowEnvironment.FlowControl.StartFlowAsync([flowNodeService.CurrentSelectCanvas.Guid]); + Task.Run(() => + { + flowEnvironment.FlowControl.StartFlowAsync([flowNodeService.CurrentSelectCanvas.Guid]); + }); } else if (selectNodeControls.Count == 1 ) { // F5 调试当前选定节点 var nodeModel = selectNodeControls[0].ViewModel.NodeModel; SereinEnv.WriteLine(InfoType.INFO, $"调试运行当前节点:{nodeModel.Guid}"); - _ = flowEnvironment.FlowControl.StartFlowAsync(nodeModel.Guid); + Task.Run(() => + { + flowEnvironment.FlowControl.StartFlowAsync(nodeModel.Guid); + }); //_ = nodeModel.StartFlowAsync(new DynamicContext(flowEnvironment), new CancellationToken()); }