diff --git a/.gitignore b/.gitignore index 56f5651..e768df6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ obj/ # 排除发布文件夹 .Output/ /.git1 +WorkBench.ControlLibrary.Core +WorkBench.Remote \ No newline at end of file diff --git a/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs b/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs index cde6264..2a385b1 100644 --- a/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs +++ b/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs @@ -67,7 +67,7 @@ namespace SereinFlowRemoteManagement await Console.Out.WriteLineAsync("启动远程管理模块"); await socketServer.StartAsync($"http://*:{ServerPort}/"); }); - SereinProjectData projectData = environment.SaveProject(); + SereinProjectData projectData = environment.GetProjectInfo(); } #endregion @@ -83,32 +83,10 @@ namespace SereinFlowRemoteManagement { await Send("尝试获取"); - Dictionary> LibraryMds = []; - - foreach (var mdskv in environment.MethodDetailss) - { - var library = mdskv.Key; - var mds = mdskv.Value; - foreach (var md in mds) - { - if (!LibraryMds.TryGetValue(library, out var t_mds)) - { - t_mds = new List(); - LibraryMds[library] = t_mds; - } - var mdInfo = md.ToInfo(); - mdInfo.LibraryName = library.Assembly.GetName().FullName; - t_mds.Add(mdInfo); - } - } try { - var project = await GetProjectInfo(); - return new - { - project = project, - envNode = LibraryMds.Values, - }; + var envInfo = this.environment.GetEnvInfo(); + return envInfo; } catch (Exception ex) { @@ -128,9 +106,9 @@ namespace SereinFlowRemoteManagement throw new InvalidOperationException("类型错误"); } - if (this.environment.TryGetMethodDetails(methodName,out var md)) + if (this.environment.TryGetMethodDetailsInfo(methodName,out var mdInfo)) { - this.environment.CreateNode(connectionType, new Position(x, y), md); ; + this.environment.CreateNode(connectionType, new Position(x, y), mdInfo); ; } @@ -191,7 +169,7 @@ namespace SereinFlowRemoteManagement public async Task GetProjectInfo() { await Task.Delay(0); - return environment.SaveProject(); + return environment.GetProjectInfo(); } diff --git a/FlowEdit/FlowEdit可视化流程编辑器beta.zip b/FlowEdit/FlowEdit可视化流程编辑器beta.zip new file mode 100644 index 0000000..6db3d40 Binary files /dev/null and b/FlowEdit/FlowEdit可视化流程编辑器beta.zip differ diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 3128408..c5d5f9c 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -2,6 +2,7 @@ using Serein.Library.Enums; using Serein.Library.Utils; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; @@ -35,17 +36,13 @@ namespace Serein.Library.Api /// /// 运行环境节点连接发生了改变 /// - /// - /// - /// + /// public delegate void NodeConnectChangeHandler(NodeConnectChangeEventArgs eventArgs); /// /// 环境中加载了一个节点 /// - /// - /// - /// + /// public delegate void NodeCreateHandler(NodeCreateEventArgs eventArgs); /// @@ -72,6 +69,16 @@ namespace Serein.Library.Api /// public delegate void ExpInterruptTriggerHandler(InterruptTriggerEventArgs eventArgs); + /// + /// IOC容器发生变化 + /// + public delegate void IOCMembersChangedHandler(IOCMembersChangedEventArgs eventArgs); + + /// + /// 节点需要定位 + /// + /// + public delegate void NodeLocatedHandler(NodeLocatedEventArgs eventArgs); #endregion @@ -118,7 +125,7 @@ namespace Serein.Library.Api public class LoadDllEventArgs : FlowEventArgs { - public LoadDllEventArgs(NodeLibrary nodeLibrary, List MethodDetailss) + public LoadDllEventArgs(NodeLibrary nodeLibrary, List MethodDetailss) { this.NodeLibrary = nodeLibrary; this.MethodDetailss = MethodDetailss; @@ -130,7 +137,7 @@ namespace Serein.Library.Api /// /// dll文件中有效的流程方法描述 /// - public List MethodDetailss { get; protected set; } + public List MethodDetailss { get; protected set; } } public class RemoteDllEventArgs : FlowEventArgs @@ -251,11 +258,27 @@ namespace Serein.Library.Api /// public class MonitorObjectEventArgs : FlowEventArgs { + /// + /// 变化的数据类别 + /// public enum ObjSourceType { + /// + /// 流程节点的数据 + /// NodeFlowData, + + /// + /// IOC容器对象 + /// IOCObj, } + /// + /// 在某个节点运行时,监听的数据发生了改变 + /// + /// + /// + /// public MonitorObjectEventArgs(string nodeGuid, object monitorData, ObjSourceType objSourceType) { NodeGuid = nodeGuid; @@ -267,6 +290,10 @@ namespace Serein.Library.Api /// 中断的节点Guid /// public string NodeGuid { get; protected set; } + + /// + /// 监听对象类别 + /// public ObjSourceType ObjSource { get; protected set; } /// /// 新的数据 @@ -326,14 +353,8 @@ namespace Serein.Library.Api public string Expression { get; protected set; } public InterruptTriggerType Type { get; protected set; } } - #endregion - /// - /// IOC容器发生变化 - /// - public delegate void IOCMembersChangedHandler(IOCMembersChangedEventArgs eventArgs); - /// /// 流程事件签名基类 @@ -360,11 +381,6 @@ namespace Serein.Library.Api public object Instance { get; private set; } } - /// - /// 节点需要定位 - /// - /// - public delegate void NodeLocatedHandler(NodeLocatedEventArgs eventArgs); public class NodeLocatedEventArgs : FlowEventArgs { @@ -375,6 +391,14 @@ namespace Serein.Library.Api public string NodeGuid { get; private set; } } + #endregion + + + + + /// + /// 运行环境 + /// public interface IFlowEnvironment { #region 属性 @@ -397,7 +421,7 @@ namespace Serein.Library.Api /// /// DLL中NodeAction特性的方法描述的所有原始副本 /// - Dictionary> MethodDetailss { get; } + // ConcurrentDictionary MethodDetailss { get; } /// @@ -478,14 +502,19 @@ namespace Serein.Library.Api /// - /// 获取方法描述 + /// 获取方法描述信息 /// - /// - /// + /// 方法描述 + /// 方法信息 /// - bool TryGetMethodDetails(string methodName, out MethodDetails md); - + bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo); + /// + /// 获取指定方法的Emit委托 + /// + /// + /// + /// bool TryGetDelegateDetails(string methodName, out DelegateDetails del); //bool TryGetNodeData(string methodName, out NodeData node); @@ -496,13 +525,22 @@ namespace Serein.Library.Api /// 保存当前项目 /// /// - SereinProjectData SaveProject(); + SereinProjectData GetProjectInfo(); /// /// 加载项目文件 /// /// /// void LoadProject(SereinProjectData projectFile, string filePath); + + /// + /// 加载远程项目 + /// + /// 远程项目地址 + /// 远程项目端口 + /// 密码 + void LoadRemoteProject(string addres,int port, string token); + /// /// 从文件中加载Dll /// @@ -511,7 +549,7 @@ namespace Serein.Library.Api /// /// 移除DLL /// - /// + /// 程序集的名称 bool RemoteDll(string assemblyFullName); /// @@ -552,8 +590,10 @@ namespace Serein.Library.Api /// 创建节点/区域/基础控件 /// /// 节点/区域/基础控件 - /// 节点绑定的方法说明( - void CreateNode(NodeControlType nodeBase, Position position, MethodDetails methodDetails = null); + /// 节点在画布上的位置( + /// 节点绑定的方法说明( + void CreateNode(NodeControlType nodeBase, Position position, MethodDetailsInfo methodDetailsInfo = null); + /// /// 移除两个节点之间的连接关系 /// @@ -590,15 +630,15 @@ namespace Serein.Library.Api /// /// 添加作用于某个对象的中断表达式 /// - /// + /// /// /// bool AddInterruptExpression(string key, string expression); - + /// /// 监视指定对象 /// - /// 需要监视的对象 + /// 需要监视的对象 /// 是否启用监视 void SetMonitorObjState(string key,bool isMonitor); @@ -620,6 +660,14 @@ namespace Serein.Library.Api Task GetOrCreateGlobalInterruptAsync(); + #region 远程相关 + /// + /// (适用于远程连接后获取环境的运行状态)获取当前环境的信息 + /// + /// + object GetEnvInfo(); + #endregion + #endregion #region 启动器调用 @@ -628,7 +676,8 @@ namespace Serein.Library.Api /// 流程启动器调用,监视数据更新通知 /// /// 更新了数据的节点Guid - /// 更新的数据 + /// 更新的数据 + /// 更新的数据 void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType); /// diff --git a/Library/Entity/Base/NodeModelBaseData.cs b/Library/Entity/Base/NodeModelBaseData.cs new file mode 100644 index 0000000..b85c0dc --- /dev/null +++ b/Library/Entity/Base/NodeModelBaseData.cs @@ -0,0 +1,225 @@ +using Serein.Library.Api; +using Serein.Library.Entity; +using Serein.Library.Enums; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Serein.NodeFlow.Base +{ + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + public abstract partial class NodeModelBase : IDynamicFlowNode + { + + public NodeModelBase() + { + PreviousNodes = []; + SuccessorNodes = []; + foreach (ConnectionType ctType in NodeStaticConfig.ConnectionTypes) + { + PreviousNodes[ctType] = new List(); + SuccessorNodes[ctType] = new List(); + } + DebugSetting = new NodeDebugSetting(); + } + + + /// + /// 调试功能 + /// + public NodeDebugSetting DebugSetting { get; set; } + + /// + /// 节点对应的控件类型 + /// + public NodeControlType ControlType { get; set; } + + /// + /// 方法描述,对应DLL的方法 + /// + public MethodDetails MethodDetails { get; set; } + + /// + /// 节点guid + /// + public string Guid { get; set; } + + /// + /// 显示名称 + /// + public string DisplayName { get; set; } = string.Empty; + + /// + /// 是否为起点控件 + /// + public bool IsStart { get; set; } + + /// + /// 运行时的上一节点 + /// + public NodeModelBase PreviousNode { get; set; } + + /// + /// 不同分支的父节点 + /// + public Dictionary> PreviousNodes { get; } + + /// + /// 不同分支的子节点 + /// + public Dictionary> SuccessorNodes { get; } + + /// + /// 当前节点执行完毕后需要执行的下一个分支的类别 + /// + public ConnectionType NextOrientation { get; set; } = ConnectionType.None; + + /// + /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) + /// + public Exception RuningException { get; set; } = null; + + + /// + /// 控制FlowData在同一时间只会被同一个线程更改。 + /// + private readonly ReaderWriterLockSlim _flowDataLock = new ReaderWriterLockSlim(); + private object _flowData; + /// + /// 当前传递数据(执行了节点对应的方法,才会存在值)。 + /// + protected object FlowData + { + get + { + _flowDataLock.EnterReadLock(); + try + { + return _flowData; + } + finally + { + _flowDataLock.ExitReadLock(); + } + } + set + { + _flowDataLock.EnterWriteLock(); + try + { + _flowData = value; + } + finally + { + _flowDataLock.ExitWriteLock(); + } + } + } + + } + + + + + + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + //public class NodeModelBaseBuilder + //{ + // public NodeModelBaseBuilder(NodeModelBase builder) + // { + // this.ControlType = builder.ControlType; + // this.MethodDetails = builder.MethodDetails; + // this.Guid = builder.Guid; + // this.DisplayName = builder.DisplayName; + // this.IsStart = builder.IsStart; + // this.PreviousNode = builder.PreviousNode; + // this.PreviousNodes = builder.PreviousNodes; + // this.SucceedBranch = builder.SucceedBranch; + // this.FailBranch = builder.FailBranch; + // this.ErrorBranch = builder.ErrorBranch; + // this.UpstreamBranch = builder.UpstreamBranch; + // this.FlowState = builder.FlowState; + // this.RuningException = builder.RuningException; + // this.FlowData = builder.FlowData; + // } + + + + // /// + // /// 节点对应的控件类型 + // /// + // public NodeControlType ControlType { get; } + + // /// + // /// 方法描述,对应DLL的方法 + // /// + // public MethodDetails MethodDetails { get; } + + // /// + // /// 节点guid + // /// + // public string Guid { get; } + + // /// + // /// 显示名称 + // /// + // public string DisplayName { get;} + + // /// + // /// 是否为起点控件 + // /// + // public bool IsStart { get; } + + // /// + // /// 运行时的上一节点 + // /// + // public NodeModelBase? PreviousNode { get; } + + // /// + // /// 上一节点集合 + // /// + // public List PreviousNodes { get; } = []; + + // /// + // /// 下一节点集合(真分支) + // /// + // public List SucceedBranch { get; } = []; + + // /// + // /// 下一节点集合(假分支) + // /// + // public List FailBranch { get; } = []; + + // /// + // /// 异常分支 + // /// + // public List ErrorBranch { get; } = []; + + // /// + // /// 上游分支 + // /// + // public List UpstreamBranch { get; } = []; + + // /// + // /// 当前执行状态(进入真分支还是假分支,异常分支在异常中确定) + // /// + // public FlowStateType FlowState { get; set; } = FlowStateType.None; + + // /// + // /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) + // /// + // public Exception RuningException { get; set; } = null; + + // /// + // /// 当前传递数据(执行了节点对应的方法,才会存在值) + // /// + // public object? FlowData { get; set; } = null; + //} + + +} + diff --git a/Library/Entity/Base/NodeModelBaseFunc.cs b/Library/Entity/Base/NodeModelBaseFunc.cs new file mode 100644 index 0000000..610b7fd --- /dev/null +++ b/Library/Entity/Base/NodeModelBaseFunc.cs @@ -0,0 +1,457 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Serein.Library.Api; +using Serein.Library.Attributes; +using Serein.Library.Entity; +using Serein.Library.Enums; +using Serein.Library.Ex; +using Serein.Library.Utils; +using Serein.NodeFlow.Tool; +using Serein.NodeFlow.Tool.SereinExpression; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using static Serein.Library.Utils.ChannelFlowInterrupt; + +namespace Serein.NodeFlow.Base +{ + + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + public abstract partial class NodeModelBase : IDynamicFlowNode + { + + + #region 调试中断 + + + /// + /// 不再中断 + /// + public void CancelInterrupt() + { + this.DebugSetting.InterruptClass = InterruptClass.None; + DebugSetting.CancelInterruptCallback?.Invoke(); + } + + #endregion + + #region 导出/导入项目文件节点信息 + + internal abstract Parameterdata[] GetParameterdatas(); + public virtual NodeInfo ToInfo() + { + // if (MethodDetails == null) return null; + + var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 + var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 + var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支 + var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 上游分支 + + // 生成参数列表 + Parameterdata[] parameterData = GetParameterdatas(); + + return new NodeInfo + { + Guid = Guid, + MethodName = MethodDetails?.MethodName, + Label = DisplayName ?? "", + Type = this.GetType().ToString(), + TrueNodes = trueNodes.ToArray(), + FalseNodes = falseNodes.ToArray(), + UpstreamNodes = upstreamNodes.ToArray(), + ParameterData = parameterData.ToArray(), + ErrorNodes = errorNodes.ToArray(), + + }; + } + + public virtual NodeModelBase LoadInfo(NodeInfo nodeInfo) + { + this.Guid = nodeInfo.Guid; + if (this.MethodDetails is not null) + { + for (int i = 0; i < nodeInfo.ParameterData.Length; i++) + { + Parameterdata? pd = nodeInfo.ParameterData[i]; + this.MethodDetails.ParameterDetailss[i].IsExplicitData = pd.State; + this.MethodDetails.ParameterDetailss[i].DataValue = pd.Value; + } + } + + return this; + } + + #endregion + + #region 节点方法的执行 + + /// + /// 是否应该退出执行 + /// + /// + /// + /// + public static bool IsBradk(IDynamicContext context, CancellationTokenSource? flowCts) + { + // 上下文不再执行 + if(context.RunState == RunState.Completion) + { + return true; + } + + // 不存在全局触发器时,流程运行状态被设置为完成,退出执行,用于打断无限循环分支。 + if (flowCts is null && context.Env.FlowState == RunState.Completion) + { + return true; + } + // 如果存在全局触发器,且触发器的执行任务已经被取消时,退出执行。 + if (flowCts is not null) + { + if (flowCts.IsCancellationRequested) + return true; + } + return false; + } + + + + /// + /// 开始执行 + /// + /// + /// + public async Task StartFlowAsync(IDynamicContext context) + { + Stack stack = new Stack(); + stack.Push(this); + var flowCts = context.Env.IOC.Get(FlowStarter.FlipFlopCtsName); + bool hasFlipflow = flowCts != null; + while (stack.Count > 0) // 循环中直到栈为空才会退出循环 + { + await Task.Delay(0); + // 从栈中弹出一个节点作为当前节点进行处理 + var currentNode = stack.Pop(); + + #region 执行相关 + + // 筛选出上游分支 + var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream].ToArray(); + for (int index = 0; index < upstreamNodes.Length; index++) + { + NodeModelBase? upstreamNode = upstreamNodes[index]; + if (upstreamNode is not null && upstreamNode.DebugSetting.IsEnable) + { + if (upstreamNode.DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前 + { + var cancelType = await upstreamNode.DebugSetting.GetInterruptTask(); + await Console.Out.WriteLineAsync($"[{upstreamNode.MethodDetails?.MethodName}]中断已{cancelType},开始执行后继分支"); + } + upstreamNode.PreviousNode = currentNode; + await upstreamNode.StartFlowAsync(context); // 执行流程节点的上游分支 + if (upstreamNode.NextOrientation == ConnectionType.IsError) + { + // 如果上游分支执行失败,不再继续执行 + // 使上游节点(仅上游节点本身,不包含上游节点的后继节点) + // 具备通过抛出异常中断流程的能力 + break; + } + } + } + if (IsBradk(context, flowCts)) break; // 退出执行 + // 上游分支执行完成,才执行当前节点 + object? newFlowData = await currentNode.ExecutingAsync(context); + if (IsBradk(context, flowCts)) break; // 退出执行 + + await RefreshFlowDataAndExpInterrupt(context, currentNode, newFlowData); // 执行当前节点后刷新数据 + #endregion + + + #region 执行完成 + + // 选择后继分支 + var nextNodes = currentNode.SuccessorNodes[currentNode.NextOrientation]; + + // 将下一个节点集合中的所有节点逆序推入栈中 + for (int i = nextNodes.Count - 1; i >= 0; i--) + { + // 筛选出启用的节点的节点 + if (nextNodes[i].DebugSetting.IsEnable) + { + nextNodes[i].PreviousNode = currentNode; + stack.Push(nextNodes[i]); + } + } + + #endregion + + } + } + + + /// + /// 执行节点对应的方法 + /// + /// 流程上下文 + /// 节点传回数据对象 + public virtual async Task ExecutingAsync(IDynamicContext context) + { + #region 调试中断 + + if (DebugSetting.InterruptClass != InterruptClass.None) // 执行触发检查是否需要中断 + { + var cancelType = await this.DebugSetting.GetInterruptTask(); // 等待中断结束 + await Console.Out.WriteLineAsync($"[{this.MethodDetails?.MethodName}]中断已{cancelType},开始执行后继分支"); + } + + #endregion + + MethodDetails? md = MethodDetails; + //var del = md.MethodDelegate.Clone(); + if (md is null) + { + throw new Exception($"节点{this.Guid}不存在方法信息,请检查是否需要重写节点的ExecutingAsync"); + } + if (!context.Env.TryGetDelegateDetails(md.MethodName, out var dd)) + { + throw new Exception($"节点{this.Guid}不存在对应委托"); + } + md.ActingInstance ??= context.Env.IOC.Get(md.ActingInstanceType); + object instance = md.ActingInstance; + + + object? result = null; + + try + { + object?[]? args = GetParameters(context, this, md); + result = await dd.InvokeAsync(md.ActingInstance, args); + NextOrientation = ConnectionType.IsSucceed; + return result; + } + catch (Exception ex) + { + await Console.Out.WriteLineAsync($"节点[{this.MethodDetails?.MethodName}]异常:" + ex); + NextOrientation = ConnectionType.IsError; + RuningException = ex; + return null; + } + } + + + + /// + /// 获取对应的参数数组 + /// + public static object?[]? GetParameters(IDynamicContext context, NodeModelBase nodeModel, MethodDetails md) + { + // 用正确的大小初始化参数数组 + if (md.ParameterDetailss.Length == 0) + { + return null;// md.ActingInstance + } + + object?[]? parameters = new object[md.ParameterDetailss.Length]; + var flowData = nodeModel.PreviousNode?.FlowData; // 当前传递的数据 + var previousDataType = flowData?.GetType(); + + for (int i = 0; i < parameters.Length; i++) + { + + object? inputParameter; // 存放解析的临时参数 + var ed = md.ParameterDetailss[i]; // 方法入参描述 + + + if (ed.IsExplicitData) // 判断是否使用显示的输入参数 + { + if (ed.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase) && flowData is not null) + { + // 执行表达式从上一节点获取对象 + inputParameter = SerinExpressionEvaluator.Evaluate(ed.DataValue, flowData, out _); + } + else + { + // 使用输入的固定值 + inputParameter = ed.DataValue; + } + } + else + { + inputParameter = flowData; // 使用上一节点的对象 + } + + // 入参存在取值转换器 + if (ed.ExplicitType.IsEnum && ed.Convertor is not null) + { + if (Enum.TryParse(ed.ExplicitType, ed.DataValue, out var resultEnum)) + { + var value = ed.Convertor(resultEnum); + if (value is not null) + { + parameters[i] = value; + continue; + } + else + { + throw new InvalidOperationException("转换器调用失败"); + } + } + } + + // 入参存在类型转换器,获取枚举转换器中记录的枚举 + if (ed.ExplicitType.IsEnum && ed.DataType != ed.ExplicitType) + { + if (Enum.TryParse(ed.ExplicitType, ed.DataValue, out var resultEnum)) // 获取对应的枚举项 + { + // 获取绑定的类型 + var type = EnumHelper.GetBoundValue(ed.ExplicitType, resultEnum, attr => attr.Value); + if (type is Type enumBindType && enumBindType is not null) + { + var value = context.Env.IOC.Instantiate(enumBindType); + if (value is not null) + { + parameters[i] = value; + continue; + } + } + } + } + + + + + + + + + if (ed.DataType.IsValueType) + { + var valueStr = inputParameter?.ToString(); + parameters[i] = valueStr.ToValueData(ed.DataType); + } + else + { + var valueStr = inputParameter?.ToString(); + parameters[i] = ed.DataType switch + { + Type t when t == typeof(string) => valueStr, + Type t when t == typeof(IDynamicContext) => context, // 上下文 + Type t when t == typeof(DateTime) => string.IsNullOrEmpty(valueStr) ? 0 : DateTime.Parse(valueStr), + + Type t when t == typeof(MethodDetails) => md, // 节点方法描述 + Type t when t == typeof(NodeModelBase) => nodeModel, // 节点实体类 + + Type t when t.IsArray => (inputParameter as Array)?.Cast().ToList(), + Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter, + _ => inputParameter, + }; + } + + + + } + return parameters; + } + + /// + /// 更新节点数据,并检查监视表达式是否生效 + /// + /// 上下文 + /// 节点Moel + /// 新的数据 + /// + public static async Task RefreshFlowDataAndExpInterrupt(IDynamicContext context, NodeModelBase nodeModel, object? newData = null) + { + string guid = nodeModel.Guid; + if (newData is not null) + { + await MonitorObjExpInterrupt(context, nodeModel, newData, 0); // 首先监视对象 + await MonitorObjExpInterrupt(context, nodeModel, newData, 1); // 然后监视节点 + nodeModel.FlowData = newData; // 替换数据 + context.AddOrUpdate(guid, nodeModel); // 上下文中更新数据 + } + } + + private static async Task MonitorObjExpInterrupt(IDynamicContext context, NodeModelBase nodeModel, object? data, int monitorType) + { + MonitorObjectEventArgs.ObjSourceType sourceType; + string? key; + if (monitorType == 0) + { + key = data?.GetType()?.FullName; + sourceType = MonitorObjectEventArgs.ObjSourceType.IOCObj; + } + else + { + key = nodeModel.Guid; + sourceType = MonitorObjectEventArgs.ObjSourceType.IOCObj; + } + if (string.IsNullOrEmpty(key)) + { + return; + } + + if (context.Env.CheckObjMonitorState(key, out List exps)) // 如果新的数据处于查看状态,通知UI进行更新?交给运行环境判断? + { + context.Env.MonitorObjectNotification(nodeModel.Guid, data, sourceType); // 对象处于监视状态,通知UI更新数据显示 + if (exps.Count > 0) + { + // 表达式环境下判断是否需要执行中断 + bool isExpInterrupt = false; + string? exp = ""; + // 判断执行监视表达式,直到为 true 时退出 + for (int i = 0; i < exps.Count && !isExpInterrupt; i++) + { + exp = exps[i]; + if (string.IsNullOrEmpty(exp)) continue; + isExpInterrupt = SereinConditionParser.To(data, exp); + } + + if (isExpInterrupt) // 触发中断 + { + InterruptClass interruptClass = InterruptClass.Branch; // 分支中断 + if (context.Env.SetNodeInterrupt(nodeModel.Guid, interruptClass)) + { + context.Env.TriggerInterrupt(nodeModel.Guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Exp); + var cancelType = await nodeModel.DebugSetting.GetInterruptTask(); + await Console.Out.WriteLineAsync($"[{data}]中断已{cancelType},开始执行后继分支"); + } + } + } + + } + } + + + /// + /// 释放对象 + /// + public void ReleaseFlowData() + { + if (typeof(IDisposable).IsAssignableFrom(FlowData?.GetType()) && FlowData is IDisposable disposable) + { + disposable?.Dispose(); + } + this.FlowData = null; + } + + /// + /// 获取节点数据 + /// + /// + public object? GetFlowData() + { + return this.FlowData; + } + #endregion + + } +} diff --git a/Library/Entity/DelegateDetails.cs b/Library/Entity/DelegateDetails.cs index dc4412b..62deda4 100644 --- a/Library/Entity/DelegateDetails.cs +++ b/Library/Entity/DelegateDetails.cs @@ -14,11 +14,21 @@ namespace Serein.Library.Entity /// public class DelegateDetails { + /// + /// 记录Emit委托 + /// + /// + /// public DelegateDetails(EmitMethodType EmitMethodType, Delegate EmitDelegate) { this._emitMethodType = EmitMethodType; this._emitDelegate = EmitDelegate; } + /// + /// 更新委托方法 + /// + /// + /// public void Upload(EmitMethodType EmitMethodType, Delegate EmitDelegate) { _emitMethodType = EmitMethodType; @@ -26,18 +36,30 @@ namespace Serein.Library.Entity } private Delegate _emitDelegate; private EmitMethodType _emitMethodType; + + /// + /// 普通方法:Func<object,object[],object> + /// 异步方法:Func<object,object[],Task> + /// 异步有返回值方法:Func<object,object[],Task<object>> + /// public Delegate EmitDelegate { get => _emitDelegate; } + /// + /// 表示Emit构造的委托类型 + /// public EmitMethodType EmitMethodType { get => _emitMethodType; } /// - /// 异步等待Emit创建的委托。 - /// 需要注意的是,传入的实例必须包含创建委托的方法信息,传入的参数也必须符合方法入参信息。 - /// + /// 使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。 + /// /// 实例 /// 入参 - /// 返回值 - public async Task Invoke(object instance, object[] args) + /// void方法自动返回null + public async Task InvokeAsync(object instance, object[] args) { + if(args is null) + { + args = new object[0]; + } object result = null; try { diff --git a/Library/Entity/MethodDetails.cs b/Library/Entity/MethodDetails.cs index 00efadc..41c19ba 100644 --- a/Library/Entity/MethodDetails.cs +++ b/Library/Entity/MethodDetails.cs @@ -23,7 +23,7 @@ namespace Serein.Library.Entity /// /// 节点类型 /// - public string NodeType { get; set; } + public NodeType NodeType { get; set; } /// /// 方法说明 @@ -39,7 +39,6 @@ namespace Serein.Library.Entity /// /// 出参类型 /// - public string ReturnTypeFullName { get; set; } } @@ -60,7 +59,7 @@ namespace Serein.Library.Entity { MethodName = MethodName, MethodTips = MethodTips, - NodeType = MethodDynamicType.ToString(), + NodeType = MethodDynamicType, ParameterDetailsInfos = this.ParameterDetailss.Select(p => p.ToInfo()).ToArray(), ReturnTypeFullName = ReturnType.FullName, diff --git a/Library/Entity/NodeLibrary.cs b/Library/Entity/NodeLibrary.cs index 0e042f4..f0d53be 100644 --- a/Library/Entity/NodeLibrary.cs +++ b/Library/Entity/NodeLibrary.cs @@ -15,6 +15,7 @@ namespace Serein.Library.Entity /// public string Path { get; set; } + public string Name{ get; set; } public Assembly Assembly { get; set; } } diff --git a/Library/Entity/NodeModelBaseData.cs b/Library/Entity/NodeModelBaseData.cs new file mode 100644 index 0000000..18e8acb --- /dev/null +++ b/Library/Entity/NodeModelBaseData.cs @@ -0,0 +1,225 @@ +using Serein.Library.Api; +using Serein.Library.Entity; +using Serein.Library.Enums; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Serein.NodeFlow.Base +{ + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + public abstract partial class NodeModelBase : IDynamicFlowNode + { + + public NodeModelBase() + { + PreviousNodes = new Dictionary>(); + SuccessorNodes = new Dictionary>(); + foreach (ConnectionType ctType in NodeStaticConfig.ConnectionTypes) + { + PreviousNodes[ctType] = new List(); + SuccessorNodes[ctType] = new List(); + } + DebugSetting = new NodeDebugSetting(); + } + + + /// + /// 调试功能 + /// + public NodeDebugSetting DebugSetting { get; set; } + + /// + /// 节点对应的控件类型 + /// + public NodeControlType ControlType { get; set; } + + /// + /// 方法描述,对应DLL的方法 + /// + public MethodDetails MethodDetails { get; set; } + + /// + /// 节点guid + /// + public string Guid { get; set; } + + /// + /// 显示名称 + /// + public string DisplayName { get; set; } = string.Empty; + + /// + /// 是否为起点控件 + /// + public bool IsStart { get; set; } + + /// + /// 运行时的上一节点 + /// + public NodeModelBase PreviousNode { get; set; } + + /// + /// 不同分支的父节点 + /// + public Dictionary> PreviousNodes { get; } + + /// + /// 不同分支的子节点 + /// + public Dictionary> SuccessorNodes { get; } + + /// + /// 当前节点执行完毕后需要执行的下一个分支的类别 + /// + public ConnectionType NextOrientation { get; set; } = ConnectionType.None; + + /// + /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) + /// + public Exception RuningException { get; set; } = null; + + + /// + /// 控制FlowData在同一时间只会被同一个线程更改。 + /// + private readonly ReaderWriterLockSlim _flowDataLock = new ReaderWriterLockSlim(); + private object _flowData; + /// + /// 当前传递数据(执行了节点对应的方法,才会存在值)。 + /// + protected object FlowData + { + get + { + _flowDataLock.EnterReadLock(); + try + { + return _flowData; + } + finally + { + _flowDataLock.ExitReadLock(); + } + } + set + { + _flowDataLock.EnterWriteLock(); + try + { + _flowData = value; + } + finally + { + _flowDataLock.ExitWriteLock(); + } + } + } + + } + + + + + + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + //public class NodeModelBaseBuilder + //{ + // public NodeModelBaseBuilder(NodeModelBase builder) + // { + // this.ControlType = builder.ControlType; + // this.MethodDetails = builder.MethodDetails; + // this.Guid = builder.Guid; + // this.DisplayName = builder.DisplayName; + // this.IsStart = builder.IsStart; + // this.PreviousNode = builder.PreviousNode; + // this.PreviousNodes = builder.PreviousNodes; + // this.SucceedBranch = builder.SucceedBranch; + // this.FailBranch = builder.FailBranch; + // this.ErrorBranch = builder.ErrorBranch; + // this.UpstreamBranch = builder.UpstreamBranch; + // this.FlowState = builder.FlowState; + // this.RuningException = builder.RuningException; + // this.FlowData = builder.FlowData; + // } + + + + // /// + // /// 节点对应的控件类型 + // /// + // public NodeControlType ControlType { get; } + + // /// + // /// 方法描述,对应DLL的方法 + // /// + // public MethodDetails MethodDetails { get; } + + // /// + // /// 节点guid + // /// + // public string Guid { get; } + + // /// + // /// 显示名称 + // /// + // public string DisplayName { get;} + + // /// + // /// 是否为起点控件 + // /// + // public bool IsStart { get; } + + // /// + // /// 运行时的上一节点 + // /// + // public NodeModelBase? PreviousNode { get; } + + // /// + // /// 上一节点集合 + // /// + // public List PreviousNodes { get; } = []; + + // /// + // /// 下一节点集合(真分支) + // /// + // public List SucceedBranch { get; } = []; + + // /// + // /// 下一节点集合(假分支) + // /// + // public List FailBranch { get; } = []; + + // /// + // /// 异常分支 + // /// + // public List ErrorBranch { get; } = []; + + // /// + // /// 上游分支 + // /// + // public List UpstreamBranch { get; } = []; + + // /// + // /// 当前执行状态(进入真分支还是假分支,异常分支在异常中确定) + // /// + // public FlowStateType FlowState { get; set; } = FlowStateType.None; + + // /// + // /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) + // /// + // public Exception RuningException { get; set; } = null; + + // /// + // /// 当前传递数据(执行了节点对应的方法,才会存在值) + // /// + // public object? FlowData { get; set; } = null; + //} + + +} + diff --git a/Library/Entity/NodeModelBaseFunc.cs b/Library/Entity/NodeModelBaseFunc.cs new file mode 100644 index 0000000..6a90a82 --- /dev/null +++ b/Library/Entity/NodeModelBaseFunc.cs @@ -0,0 +1,487 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Serein.Library.Api; +using Serein.Library.Attributes; +using Serein.Library.Entity; +using Serein.Library.Enums; +using Serein.Library.Ex; +using Serein.Library.Utils; +using Serein.NodeFlow.Tool.SereinExpression; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using static Serein.Library.Utils.ChannelFlowInterrupt; + +namespace Serein.NodeFlow.Base +{ + + /// + /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 + /// + public abstract partial class NodeModelBase : IDynamicFlowNode + { + + + #region 调试中断 + + + /// + /// 不再中断 + /// + public void CancelInterrupt() + { + this.DebugSetting.InterruptClass = InterruptClass.None; + DebugSetting.CancelInterruptCallback?.Invoke(); + } + + #endregion + + #region 导出/导入项目文件节点信息 + + public abstract Parameterdata[] GetParameterdatas(); + public virtual NodeInfo ToInfo() + { + // if (MethodDetails == null) return null; + + var trueNodes = SuccessorNodes[ConnectionType.IsSucceed].Select(item => item.Guid); // 真分支 + var falseNodes = SuccessorNodes[ConnectionType.IsFail].Select(item => item.Guid);// 假分支 + var errorNodes = SuccessorNodes[ConnectionType.IsError].Select(item => item.Guid);// 异常分支 + var upstreamNodes = SuccessorNodes[ConnectionType.Upstream].Select(item => item.Guid);// 上游分支 + + // 生成参数列表 + Parameterdata[] parameterData = GetParameterdatas(); + + return new NodeInfo + { + Guid = Guid, + MethodName = MethodDetails?.MethodName, + Label = DisplayName ?? "", + Type = this.GetType().ToString(), + TrueNodes = trueNodes.ToArray(), + FalseNodes = falseNodes.ToArray(), + UpstreamNodes = upstreamNodes.ToArray(), + ParameterData = parameterData.ToArray(), + ErrorNodes = errorNodes.ToArray(), + + }; + } + + public virtual NodeModelBase LoadInfo(NodeInfo nodeInfo) + { + this.Guid = nodeInfo.Guid; + if (this.MethodDetails != null) + { + for (int i = 0; i < nodeInfo.ParameterData.Length; i++) + { + Parameterdata pd = nodeInfo.ParameterData[i]; + this.MethodDetails.ParameterDetailss[i].IsExplicitData = pd.State; + this.MethodDetails.ParameterDetailss[i].DataValue = pd.Value; + } + } + + return this; + } + + #endregion + + #region 节点方法的执行 + + /// + /// 是否应该退出执行 + /// + /// + /// + /// + public static bool IsBradk(IDynamicContext context, CancellationTokenSource flowCts) + { + // 上下文不再执行 + if(context.RunState == RunState.Completion) + { + return true; + } + + // 不存在全局触发器时,流程运行状态被设置为完成,退出执行,用于打断无限循环分支。 + if (flowCts is null && context.Env.FlowState == RunState.Completion) + { + return true; + } + // 如果存在全局触发器,且触发器的执行任务已经被取消时,退出执行。 + if (flowCts != null) + { + if (flowCts.IsCancellationRequested) + return true; + } + return false; + } + + + + /// + /// 开始执行 + /// + /// + /// + public async Task StartFlowAsync(IDynamicContext context) + { + Stack stack = new Stack(); + stack.Push(this); + var flowCts = context.Env.IOC.Get(NodeStaticConfig.FlipFlopCtsName); + bool hasFlipflow = flowCts != null; + while (stack.Count > 0) // 循环中直到栈为空才会退出循环 + { + await Task.Delay(0); + // 从栈中弹出一个节点作为当前节点进行处理 + var currentNode = stack.Pop(); + + #region 执行相关 + + // 筛选出上游分支 + var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream].ToArray(); + for (int index = 0; index < upstreamNodes.Length; index++) + { + NodeModelBase upstreamNode = upstreamNodes[index]; + if (!(upstreamNode is null) && upstreamNode.DebugSetting.IsEnable) + { + if (upstreamNode.DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前 + { + var cancelType = await upstreamNode.DebugSetting.GetInterruptTask(); + await Console.Out.WriteLineAsync($"[{upstreamNode.MethodDetails?.MethodName}]中断已{cancelType},开始执行后继分支"); + } + upstreamNode.PreviousNode = currentNode; + await upstreamNode.StartFlowAsync(context); // 执行流程节点的上游分支 + if (upstreamNode.NextOrientation == ConnectionType.IsError) + { + // 如果上游分支执行失败,不再继续执行 + // 使上游节点(仅上游节点本身,不包含上游节点的后继节点) + // 具备通过抛出异常中断流程的能力 + break; + } + } + } + if (IsBradk(context, flowCts)) break; // 退出执行 + // 上游分支执行完成,才执行当前节点 + object newFlowData = await currentNode.ExecutingAsync(context); + if (IsBradk(context, flowCts)) break; // 退出执行 + + await RefreshFlowDataAndExpInterrupt(context, currentNode, newFlowData); // 执行当前节点后刷新数据 + #endregion + + + #region 执行完成 + + // 选择后继分支 + var nextNodes = currentNode.SuccessorNodes[currentNode.NextOrientation]; + + // 将下一个节点集合中的所有节点逆序推入栈中 + for (int i = nextNodes.Count - 1; i >= 0; i--) + { + // 筛选出启用的节点的节点 + if (nextNodes[i].DebugSetting.IsEnable) + { + nextNodes[i].PreviousNode = currentNode; + stack.Push(nextNodes[i]); + } + } + + #endregion + + } + } + + + /// + /// 执行节点对应的方法 + /// + /// 流程上下文 + /// 节点传回数据对象 + public virtual async Task ExecutingAsync(IDynamicContext context) + { + #region 调试中断 + + if (DebugSetting.InterruptClass != InterruptClass.None) // 执行触发检查是否需要中断 + { + var cancelType = await this.DebugSetting.GetInterruptTask(); // 等待中断结束 + await Console.Out.WriteLineAsync($"[{this.MethodDetails?.MethodName}]中断已{cancelType},开始执行后继分支"); + } + + #endregion + + MethodDetails md = MethodDetails; + //var del = md.MethodDelegate.Clone(); + if (md is null) + { + throw new Exception($"节点{this.Guid}不存在方法信息,请检查是否需要重写节点的ExecutingAsync"); + } + if (!context.Env.TryGetDelegateDetails(md.MethodName, out var dd)) + { + throw new Exception($"节点{this.Guid}不存在对应委托"); + } + if(md.ActingInstance is null) + { + md.ActingInstance = context.Env.IOC.Get(md.ActingInstanceType); + } + // md.ActingInstance ??= context.Env.IOC.Get(md.ActingInstanceType); + object instance = md.ActingInstance; + + + object result = null; + + try + { + object[] args = GetParameters(context, this, md); + result = await dd.InvokeAsync(md.ActingInstance, args); + NextOrientation = ConnectionType.IsSucceed; + return result; + } + catch (Exception ex) + { + await Console.Out.WriteLineAsync($"节点[{this.MethodDetails?.MethodName}]异常:" + ex); + NextOrientation = ConnectionType.IsError; + RuningException = ex; + return null; + } + } + + + + /// + /// 获取对应的参数数组 + /// + public static object[] GetParameters(IDynamicContext context, NodeModelBase nodeModel, MethodDetails md) + { + // 用正确的大小初始化参数数组 + if (md.ParameterDetailss.Length == 0) + { + return null;// md.ActingInstance + } + + object[] parameters = new object[md.ParameterDetailss.Length]; + var flowData = nodeModel.PreviousNode?.FlowData; // 当前传递的数据 + var previousDataType = flowData?.GetType(); + + for (int i = 0; i < parameters.Length; i++) + { + + object inputParameter; // 存放解析的临时参数 + var ed = md.ParameterDetailss[i]; // 方法入参描述 + + + if (ed.IsExplicitData) // 判断是否使用显示的输入参数 + { + if (ed.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase) && !(flowData is null)) + { + // 执行表达式从上一节点获取对象 + inputParameter = SerinExpressionEvaluator.Evaluate(ed.DataValue, flowData, out _); + } + else + { + // 使用输入的固定值 + inputParameter = ed.DataValue; + } + } + else + { + inputParameter = flowData; // 使用上一节点的对象 + } + + // 入参存在取值转换器 + if (ed.ExplicitType.IsEnum && !(ed.Convertor is null)) + { + //var resultEnum = Enum.ToObject(ed.ExplicitType, ed.DataValue); + var resultEnum = Enum.Parse(ed.ExplicitType, ed.DataValue); + var value = ed.Convertor(resultEnum); + if (value is null) + { + throw new InvalidOperationException("转换器调用失败"); + + } + else + { + parameters[i] = value; + continue; + } + //if (Enum.TryParse(ed.ExplicitType, ed.DataValue, out var resultEnum)) + //{ + + //} + } + + // 入参存在类型转换器,获取枚举转换器中记录的枚举 + if (ed.ExplicitType.IsEnum && ed.DataType != ed.ExplicitType) + { + var resultEnum = Enum.Parse(ed.ExplicitType, ed.DataValue); + // 获取绑定的类型 + var type = EnumHelper.GetBoundValue(ed.ExplicitType, resultEnum, attr => attr.Value); + if (type is Type enumBindType && !(enumBindType is null)) + { + var value = context.Env.IOC.Instantiate(enumBindType); + if (value is null) + { + + } + else + { + parameters[i] = value; + continue; + } + + } + } + + + + if (ed.DataType.IsValueType) + { + var valueStr = inputParameter?.ToString(); + parameters[i] = valueStr.ToValueData(ed.DataType); + } + else + { + var valueStr = inputParameter?.ToString(); + if(ed.DataType == typeof(string)) + { + parameters[i] = valueStr; + } + else if(ed.DataType == typeof(IDynamicContext)) + { + parameters[i] = context; + } + else if(ed.DataType == typeof(MethodDetails)) + { + parameters[i] = md; + } + else if(ed.DataType == typeof(NodeModelBase)) + { + parameters[i] = nodeModel; + } + else + { + parameters[i] = inputParameter; + } + + //parameters[i] = ed.DataType switch + //{ + // Type t when t == typeof(string) => valueStr, + // Type t when t == typeof(IDynamicContext) => context, // 上下文 + // Type t when t == typeof(DateTime) => string.IsNullOrEmpty(valueStr) ? null : DateTime.Parse(valueStr), + + // Type t when t == typeof(MethodDetails) => md, // 节点方法描述 + // Type t when t == typeof(NodeModelBase) => nodeModel, // 节点实体类 + + // Type t when t.IsArray => (inputParameter as Array)?.Cast().ToList(), + // Type t when t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>) => inputParameter, + // _ => inputParameter, + //}; + } + + + + } + return parameters; + } + + /// + /// 更新节点数据,并检查监视表达式是否生效 + /// + /// 上下文 + /// 节点Moel + /// 新的数据 + /// + public static async Task RefreshFlowDataAndExpInterrupt(IDynamicContext context, NodeModelBase nodeModel, object newData = null) + { + string guid = nodeModel.Guid; + if (newData is null) + { + } + else + { + await MonitorObjExpInterrupt(context, nodeModel, newData, 0); // 首先监视对象 + await MonitorObjExpInterrupt(context, nodeModel, newData, 1); // 然后监视节点 + nodeModel.FlowData = newData; // 替换数据 + context.AddOrUpdate(guid, nodeModel); // 上下文中更新数据 + } + } + + private static async Task MonitorObjExpInterrupt(IDynamicContext context, NodeModelBase nodeModel, object data, int monitorType) + { + MonitorObjectEventArgs.ObjSourceType sourceType; + string key; + if (monitorType == 0) + { + key = data?.GetType()?.FullName; + sourceType = MonitorObjectEventArgs.ObjSourceType.IOCObj; + } + else + { + key = nodeModel.Guid; + sourceType = MonitorObjectEventArgs.ObjSourceType.IOCObj; + } + if (string.IsNullOrEmpty(key)) + { + return; + } + + if (context.Env.CheckObjMonitorState(key, out List exps)) // 如果新的数据处于查看状态,通知UI进行更新?交给运行环境判断? + { + context.Env.MonitorObjectNotification(nodeModel.Guid, data, sourceType); // 对象处于监视状态,通知UI更新数据显示 + if (exps.Count > 0) + { + // 表达式环境下判断是否需要执行中断 + bool isExpInterrupt = false; + string exp = ""; + // 判断执行监视表达式,直到为 true 时退出 + for (int i = 0; i < exps.Count && !isExpInterrupt; i++) + { + exp = exps[i]; + if (string.IsNullOrEmpty(exp)) continue; + // isExpInterrupt = SereinConditionParser.To(data, exp); + } + + if (isExpInterrupt) // 触发中断 + { + InterruptClass interruptClass = InterruptClass.Branch; // 分支中断 + if (context.Env.SetNodeInterrupt(nodeModel.Guid, interruptClass)) + { + context.Env.TriggerInterrupt(nodeModel.Guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Exp); + var cancelType = await nodeModel.DebugSetting.GetInterruptTask(); + await Console.Out.WriteLineAsync($"[{data}]中断已{cancelType},开始执行后继分支"); + } + } + } + + } + } + + + /// + /// 释放对象 + /// + public void ReleaseFlowData() + { + if (typeof(IDisposable).IsAssignableFrom(FlowData?.GetType()) && FlowData is IDisposable disposable) + { + disposable?.Dispose(); + } + this.FlowData = null; + } + + /// + /// 获取节点数据 + /// + /// + public object GetFlowData() + { + return this.FlowData; + } + #endregion + + } +} diff --git a/Library/Network/WebSocket/Handle/WebSocketHandleConfig.cs b/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs similarity index 95% rename from Library/Network/WebSocket/Handle/WebSocketHandleConfig.cs rename to Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs index ae87755..4206f56 100644 --- a/Library/Network/WebSocket/Handle/WebSocketHandleConfig.cs +++ b/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs @@ -15,14 +15,17 @@ using System.Threading.Tasks; namespace Serein.Library.Network.WebSocketCommunication.Handle { - public class WebSocketHandleConfig + /// + /// + /// + public class JsonMsgHandleConfig { private readonly Delegate EmitDelegate; private readonly EmitHelper.EmitMethodType EmitMethodType; private Action> OnExceptionTracking; - internal WebSocketHandleConfig(SocketHandleModel model,ISocketHandleModule instance, MethodInfo methodInfo, Action> onExceptionTracking) + internal JsonMsgHandleConfig(SocketHandleModel model,ISocketHandleModule instance, MethodInfo methodInfo, Action> onExceptionTracking) { EmitMethodType = EmitHelper.CreateDynamicMethod(methodInfo,out EmitDelegate); this.Model = model; diff --git a/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs b/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs index 4904ead..6410c8d 100644 --- a/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs +++ b/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs @@ -8,30 +8,51 @@ using System.Threading.Tasks; namespace Serein.Library.Network.WebSocketCommunication.Handle { + /// + /// Json消息处理模块 + /// public class WebSocketHandleModule { + /// + /// Json消息处理模块 + /// public WebSocketHandleModule(string ThemeJsonKey, string DataJsonKey) { this.ThemeJsonKey = ThemeJsonKey; this.DataJsonKey = DataJsonKey; } + + /// + /// 指示处理模块该使用json中的哪个key作为业务区别字段 + /// public string ThemeJsonKey { get; } + + /// + /// 指示处理模块该使用json中的哪个key作为业务数据字段 + /// public string DataJsonKey { get; } + /// + /// 存储处理数据的配置 + /// + public ConcurrentDictionary MyHandleConfigs = new ConcurrentDictionary(); - - - public ConcurrentDictionary MyHandleConfigs = new ConcurrentDictionary(); internal void AddHandleConfigs(SocketHandleModel model, ISocketHandleModule instance, MethodInfo methodInfo , Action> onExceptionTracking) { if (!MyHandleConfigs.ContainsKey(model.ThemeValue)) { - var myHandleConfig = new WebSocketHandleConfig(model,instance, methodInfo, onExceptionTracking); - MyHandleConfigs[model.ThemeValue] = myHandleConfig; + var jsonMsgHandleConfig = new JsonMsgHandleConfig(model,instance, methodInfo, onExceptionTracking); + MyHandleConfigs[model.ThemeValue] = jsonMsgHandleConfig; } } - public bool ResetConfig(ISocketHandleModule socketControlBase) + + /// + /// 移除某个处理模块 + /// + /// + /// + public bool RemoveConfig(ISocketHandleModule socketControlBase) { foreach (var kv in MyHandleConfigs.ToArray()) { @@ -44,7 +65,10 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle return MyHandleConfigs.Count == 0; } - public void ResetConfig() + /// + /// 卸载当前模块的所有配置 + /// + public void UnloadConfig() { var temp = MyHandleConfigs.Values; MyHandleConfigs.Clear(); @@ -54,7 +78,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle } } - + /// + /// 处理JSON数据 + /// + /// + /// public void HandleSocketMsg(Func RecoverAsync, JObject jsonObject) { // 获取到消息 diff --git a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs b/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs index a0e06c0..c3293ed 100644 --- a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs +++ b/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs @@ -19,7 +19,9 @@ using System.Security.Cryptography; namespace Serein.Library.Network.WebSocketCommunication.Handle { - + /// + /// 适用于Json数据格式的WebSocket消息处理类 + /// public class WebSocketMsgHandleHelper { /// @@ -68,7 +70,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle var key = (themeKeyName, dataKeyName); if (MyHandleModuleDict.TryGetValue(key, out var myHandleModules)) { - var isRemote = myHandleModules.ResetConfig(socketControlBase); + var isRemote = myHandleModules.RemoveConfig(socketControlBase); if (isRemote) MyHandleModuleDict.TryGetValue(key, out _); } @@ -132,6 +134,12 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle } + /// + /// 异步处理消息 + /// + /// + /// + /// public async Task HandleMsgAsync(Func RecoverAsync, string message) { JObject json = JObject.Parse(message); diff --git a/Library/Network/WebSocket/WebSocketServer.cs b/Library/Network/WebSocket/WebSocketServer.cs index 5ae87ed..b550172 100644 --- a/Library/Network/WebSocket/WebSocketServer.cs +++ b/Library/Network/WebSocket/WebSocketServer.cs @@ -14,12 +14,25 @@ using System.Threading.Tasks; namespace Serein.Library.Network.WebSocketCommunication { + + /// + /// WebSocket服务类 + /// [AutoRegister] public class WebSocketServer { + /// + /// 消息处理 + /// public WebSocketMsgHandleHelper MsgHandleHelper { get; } = new WebSocketMsgHandleHelper(); private HttpListener listener; + + /// + /// 进行监听服务 + /// + /// + /// public async Task StartAsync(string url) { listener = new HttpListener(); @@ -58,6 +71,9 @@ namespace Serein.Library.Network.WebSocketCommunication } } + /// + /// 停止监听服务 + /// public void Stop() { listener?.Stop(); @@ -96,6 +112,12 @@ namespace Serein.Library.Network.WebSocketCommunication } } + /// + /// 发送消息 + /// + /// + /// + /// public static async Task SendAsync(WebSocket webSocket, string message) { var buffer = Encoding.UTF8.GetBytes(message); diff --git a/Library/NodeStaticConfig.cs b/Library/NodeStaticConfig.cs new file mode 100644 index 0000000..304ab89 --- /dev/null +++ b/Library/NodeStaticConfig.cs @@ -0,0 +1,41 @@ +using Serein.Library.Api; +using Serein.Library.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow +{ + public static class NodeStaticConfig + { + /// + /// 全局触发器CTS + /// + public const string FlipFlopCtsName = "<>.FlowFlipFlopCts"; + /// + /// 流程运行CTS + /// + public const string FlowRungCtsName = "<>.FlowRungCtsName"; + + + /// + /// 节点的命名空间 + /// + //public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; + public const string NodeSpaceName = "Serein.NodeFlow.Model"; + + + /// + /// 节点连接关系种类 + /// + public static readonly ConnectionType[] ConnectionTypes = new ConnectionType[] + { + ConnectionType.Upstream, + ConnectionType.IsSucceed, + ConnectionType.IsFail, + ConnectionType.IsError, + }; + } +} diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index eb7822c..cdfaa25 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -14,10 +14,13 @@ + + + diff --git a/Library/Utils/EnumHelper.cs b/Library/Utils/EnumHelper.cs index e7bb71b..ed0a477 100644 --- a/Library/Utils/EnumHelper.cs +++ b/Library/Utils/EnumHelper.cs @@ -29,6 +29,8 @@ namespace Serein.Library.Utils result = default; return false; } + + /// /// 从枚举值的 BindValueAttribute 特性中 获取绑定的参数(用于绑定了某些内容的枚举值) diff --git a/Library/Utils/SereinExpression/Resolver/BoolConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/BoolConditionResolver.cs new file mode 100644 index 0000000..f5fe3ed --- /dev/null +++ b/Library/Utils/SereinExpression/Resolver/BoolConditionResolver.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +{ + public class BoolConditionResolver : SereinConditionResolver + { + public enum Operator + { + /// + /// 是 + /// + Is + } + + public Operator Op { get; set; } + public bool Value { get; set; } + + public override bool Evaluate(object obj) + { + + if (obj is bool boolObj) + { + return boolObj == Value; + /*switch (Op) + { + case Operator.Is: + return boolObj == Value; + }*/ + } + return false; + } + } + +} diff --git a/Library/Utils/SereinExpression/Resolver/MemberConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/MemberConditionResolver.cs new file mode 100644 index 0000000..0153a97 --- /dev/null +++ b/Library/Utils/SereinExpression/Resolver/MemberConditionResolver.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +{ + public class MemberConditionResolver : SereinConditionResolver where T : struct, IComparable + { + //public string MemberPath { get; set; } + public ValueTypeConditionResolver.Operator Op { get; set; } + public object TargetObj { get; set; } + public T Value { get; set; } + + public string ArithmeticExpression { get; set; } + public T RangeEnd { get; internal set; } + public T RangeStart { get; internal set; } + + public override bool Evaluate(object obj) + { + //object? memberValue = GetMemberValue(obj, MemberPath); + + + if (TargetObj is T typedObj) + { + return new ValueTypeConditionResolver + { + RangeStart = RangeStart, + RangeEnd = RangeEnd, + Op = Op, + Value = Value, + ArithmeticExpression = ArithmeticExpression, + }.Evaluate(typedObj); + } + return false; + } + + //private object? GetMemberValue(object? obj, string memberPath) + //{ + // string[] members = memberPath[1..].Split('.'); + // foreach (var member in members) + // { + // if (obj == null) return null; + // Type type = obj.GetType(); + // PropertyInfo? propertyInfo = type.GetProperty(member); + // FieldInfo? fieldInfo = type.GetField(member); + // if (propertyInfo != null) + // obj = propertyInfo.GetValue(obj); + // else if (fieldInfo != null) + // obj = fieldInfo.GetValue(obj); + // else + // throw new ArgumentException($"Member {member} not found in type {type.FullName}"); + // } + // return obj; + //} + } + +} diff --git a/Library/Utils/SereinExpression/Resolver/MemberStringConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/MemberStringConditionResolver.cs new file mode 100644 index 0000000..60e89e9 --- /dev/null +++ b/Library/Utils/SereinExpression/Resolver/MemberStringConditionResolver.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +{ + public class MemberStringConditionResolver : SereinConditionResolver + { + public string MemberPath { get; set; } + + public StringConditionResolver.Operator Op { get; set; } + + public string Value { get; set; } + + + public override bool Evaluate(object obj) + { + object memberValue; + if (!string.IsNullOrWhiteSpace(MemberPath)) + { + memberValue = GetMemberValue(obj, MemberPath); + } + else + { + memberValue = obj; + } + + if (memberValue is string strObj) + { + return new StringConditionResolver + { + Op = Op, + Value = Value + }.Evaluate(strObj); + } + return false; + } + + private object GetMemberValue(object obj, string memberPath) + { + //string[] members = memberPath[1..].Split('.'); + string[] members = memberPath.Substring(1).Split('.'); + foreach (var member in members) + { + + if (obj is null) return null; + + Type type = obj.GetType(); + PropertyInfo propertyInfo = type.GetProperty(member); + FieldInfo fieldInfo = type.GetField(member); + if (propertyInfo != null) + obj = propertyInfo.GetValue(obj); + else if (fieldInfo != null) + obj = fieldInfo.GetValue(obj); + else + throw new ArgumentException($"Member {member} not found in type {type.FullName}"); + } + + return obj; + + } + + + private static string GetArithmeticExpression(string part) + { + int startIndex = part.IndexOf('['); + int endIndex = part.IndexOf(']'); + if (startIndex >= 0 && endIndex > startIndex) + { + return part.Substring(startIndex + 1, endIndex - startIndex - 1); + } + + return null; + + } + + + + + + } + +} diff --git a/Library/Utils/SereinExpression/Resolver/PassConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/PassConditionResolver.cs new file mode 100644 index 0000000..5a30e7e --- /dev/null +++ b/Library/Utils/SereinExpression/Resolver/PassConditionResolver.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +{ + public class PassConditionResolver : SereinConditionResolver + { + public Operator Op { get; set; } + public override bool Evaluate(object obj) + { + /*return Op switch + { + Operator.Pass => true, + Operator.NotPass => false, + _ => throw new NotSupportedException("不支持的条件类型") + };*/ + switch (Op) + { + case Operator.Pass: + return true; + case Operator.NotPass: + return false; + default: + throw new NotSupportedException("不支持的条件类型"); + + } + } + + public enum Operator + { + Pass, + NotPass, + } + } +} diff --git a/Library/Utils/SereinExpression/Resolver/StringConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/StringConditionResolver.cs new file mode 100644 index 0000000..7e2d178 --- /dev/null +++ b/Library/Utils/SereinExpression/Resolver/StringConditionResolver.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +{ + public class StringConditionResolver : SereinConditionResolver + { + public enum Operator + { + /// + /// 出现过 + /// + Contains, + /// + /// 没有出现过 + /// + DoesNotContain, + /// + /// 相等 + /// + Equal, + /// + /// 不相等 + /// + NotEqual, + /// + /// 起始字符串等于 + /// + StartsWith, + /// + /// 结束字符串等于 + /// + EndsWith + } + + public Operator Op { get; set; } + + public string Value { get; set; } + + + public override bool Evaluate(object obj) + { + if (obj is string strObj) + { + /*return Op switch + { + Operator.Contains => strObj.Contains(Value), + Operator.DoesNotContain => !strObj.Contains(Value), + Operator.Equal => strObj == Value, + Operator.NotEqual => strObj != Value, + Operator.StartsWith => strObj.StartsWith(Value), + Operator.EndsWith => strObj.EndsWith(Value), + _ => throw new NotSupportedException("不支持的条件类型"), + };*/ + + switch (Op) + { + case Operator.Contains: + return strObj.Contains(Value); + case Operator.DoesNotContain: + return !strObj.Contains(Value); + case Operator.Equal: + return strObj == Value; + case Operator.NotEqual: + return strObj != Value; + case Operator.StartsWith: + return strObj.StartsWith(Value); + case Operator.EndsWith: + return strObj.EndsWith(Value); + default: + throw new NotSupportedException("不支持的条件类型"); + } + } + return false; + } + } +} diff --git a/Library/Utils/SereinExpression/Resolver/ValueTypeConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/ValueTypeConditionResolver.cs new file mode 100644 index 0000000..f1cf7e1 --- /dev/null +++ b/Library/Utils/SereinExpression/Resolver/ValueTypeConditionResolver.cs @@ -0,0 +1,128 @@ +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +{ + public class ValueTypeConditionResolver : SereinConditionResolver where T : struct, IComparable + { + public enum Operator + { + /// + /// 不进行任何操作 + /// + Node, + /// + /// 大于 + /// + GreaterThan, + /// + /// 小于 + /// + LessThan, + /// + /// 等于 + /// + Equal, + /// + /// 大于或等于 + /// + GreaterThanOrEqual, + /// + /// 小于或等于 + /// + LessThanOrEqual, + /// + /// 在两者之间 + /// + InRange, + /// + /// 不在两者之间 + /// + OutOfRange + } + + public Operator Op { get; set; } + public T Value { get; set; } + public T RangeStart { get; set; } + public T RangeEnd { get; set; } + + public string ArithmeticExpression { get; set; } + + + public override bool Evaluate(object obj) + { + + var evaluatedValue = obj.ToConvert(); + if (!string.IsNullOrEmpty(ArithmeticExpression)) + { + evaluatedValue = SerinArithmeticExpressionEvaluator.Evaluate(ArithmeticExpression, evaluatedValue); + } + + switch (Op) + { + case Operator.GreaterThan: + return evaluatedValue.CompareTo(Value) > 0; + case Operator.LessThan: + return evaluatedValue.CompareTo(Value) < 0; + case Operator.Equal: + return evaluatedValue.CompareTo(Value) == 0; + case Operator.GreaterThanOrEqual: + return evaluatedValue.CompareTo(Value) >= 0; + case Operator.LessThanOrEqual: + return evaluatedValue.CompareTo(Value) <= 0; + case Operator.InRange: + return evaluatedValue.CompareTo(RangeStart) >= 0 && evaluatedValue.CompareTo(RangeEnd) <= 0; + case Operator.OutOfRange: + return evaluatedValue.CompareTo(RangeStart) < 0 || evaluatedValue.CompareTo(RangeEnd) > 0; + } + return false; + + //if (obj is T typedObj) + //{ + // numericValue = Convert.ToDouble(typedObj); + // numericValue = Convert.ToDouble(obj); + // if (!string.IsNullOrEmpty(ArithmeticExpression)) + // { + // numericValue = SerinArithmeticExpressionEvaluator.Evaluate(ArithmeticExpression, numericValue); + // } + + // T evaluatedValue = (T)Convert.ChangeType(numericValue, typeof(T)); + + // /*return Op switch + // { + // Operator.GreaterThan => evaluatedValue.CompareTo(Value) > 0, + // Operator.LessThan => evaluatedValue.CompareTo(Value) < 0, + // Operator.Equal => evaluatedValue.CompareTo(Value) == 0, + // Operator.GreaterThanOrEqual => evaluatedValue.CompareTo(Value) >= 0, + // Operator.LessThanOrEqual => evaluatedValue.CompareTo(Value) <= 0, + // Operator.InRange => evaluatedValue.CompareTo(RangeStart) >= 0 && evaluatedValue.CompareTo(RangeEnd) <= 0, + // Operator.OutOfRange => evaluatedValue.CompareTo(RangeStart) < 0 || evaluatedValue.CompareTo(RangeEnd) > 0, + // _ => throw new NotSupportedException("不支持的条件类型") + // };*/ + // switch (Op) + // { + // case Operator.GreaterThan: + // return evaluatedValue.CompareTo(Value) > 0; + // case Operator.LessThan: + // return evaluatedValue.CompareTo(Value) < 0; + // case Operator.Equal: + // return evaluatedValue.CompareTo(Value) == 0; + // case Operator.GreaterThanOrEqual: + // return evaluatedValue.CompareTo(Value) >= 0; + // case Operator.LessThanOrEqual: + // return evaluatedValue.CompareTo(Value) <= 0; + // case Operator.InRange: + // return evaluatedValue.CompareTo(RangeStart) >= 0 && evaluatedValue.CompareTo(RangeEnd) <= 0; + // case Operator.OutOfRange: + // return evaluatedValue.CompareTo(RangeStart) < 0 || evaluatedValue.CompareTo(RangeEnd) > 0; + // } + //} + //return false; + } + } + +} diff --git a/Library/Utils/SereinExpression/SereinConditionParser.cs b/Library/Utils/SereinExpression/SereinConditionParser.cs new file mode 100644 index 0000000..e73b189 --- /dev/null +++ b/Library/Utils/SereinExpression/SereinConditionParser.cs @@ -0,0 +1,775 @@ +using Newtonsoft.Json.Linq; +using Serein.Library.Utils; +using Serein.NodeFlow.Tool.SereinExpression.Resolver; +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Globalization; +using System.Linq; +using System.Reflection; + +namespace Serein.NodeFlow.Tool.SereinExpression +{ + /// + /// 字符串工具类 + /// + public static class StringHelper + { + /// + /// Net低版本无法引入Skip函数,所以使用扩展方法代替 + /// + /// + /// + /// + /// + /// + public static IEnumerable MySkip(this IEnumerable source, int count) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + int skipped = 0; + foreach (var item in source) + { + if (skipped++ >= count) + yield return item; + } + } + + public static string JoinStrings(string[] parts, int startIndex, int count, char separator) + { + if (parts == null) + throw new ArgumentNullException(nameof(parts)); + + if (startIndex < 0 || startIndex >= parts.Length || count < 0 || startIndex + count > parts.Length) + throw new ArgumentOutOfRangeException(); + + // 复制需要的部分到新的数组 + string[] subArray = new string[count]; + Array.Copy(parts, startIndex, subArray, 0, count); + + // 使用 string.Join 连接 + return string.Join(separator.ToString(), subArray); + } + + + + } + + + public class SereinConditionParser + { + public static bool To(T data, string expression) + { + try + { + if (string.IsNullOrEmpty(expression)) + { + return false; + } + var parse = ConditionParse(data, expression); + var result = parse.Evaluate(data); + return result; + + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + + public static SereinConditionResolver ConditionParse(object data, string expression) + { + if (expression.StartsWith(".")) // 表达式前缀属于从上一个节点数据对象获取成员值 + { + return ParseObjectExpression(data, expression); + } + else + { + return ParseSimpleExpression(data, expression); + } + + + //bool ContainsArithmeticOperators(string expression) + //{ + // return expression.Contains('+') || expression.Contains('-') || expression.Contains('*') || expression.Contains('/'); + //} + + } + + /// + /// 获取计算表达式的部分 + /// + /// + /// + private static string GetArithmeticExpression(string part) + { + int startIndex = part.IndexOf('['); + int endIndex = part.IndexOf(']'); + if (startIndex >= 0 && endIndex > startIndex) + { + return part.Substring(startIndex + 1, endIndex - startIndex - 1); + } + + return null; + + } + /// + /// 获取对象指定名称的成员 + /// + private static object GetMemberValue(object obj, string memberPath) + { + //string[] members = memberPath[1..].Split('.'); + string[] members = memberPath.Substring(1).Split('.'); + + foreach (var member in members) + { + if (obj is null) return null; + Type type = obj.GetType(); + PropertyInfo propertyInfo = type.GetProperty(member); + FieldInfo fieldInfo = type.GetField(member); + if (propertyInfo != null) + obj = propertyInfo.GetValue(obj); + else if (fieldInfo != null) + obj = fieldInfo.GetValue(obj); + else + throw new ArgumentException($"Member {member} not found in type {type.FullName}"); + } + return obj; + + + } + + + /// + /// 解析对象表达式 + /// + private static SereinConditionResolver ParseObjectExpression(object data, string expression) + { + var parts = expression.Split(' '); + string operatorStr = parts[0]; // 获取操作类型 + string valueStr; //= string.Join(' ', parts, 1, parts.Length - 1); + string memberPath; + Type type; + object targetObj; + + // 尝试获取指定类型 + int typeStartIndex = expression.IndexOf('<'); + int typeEndIndex = expression.IndexOf('>'); + if (typeStartIndex + typeStartIndex == -2) + { + // 如果不需要转为指定类型 + memberPath = operatorStr; + targetObj = SerinExpressionEvaluator.Evaluate("@get " + operatorStr, data, out _); + //targetObj = GetMemberValue(data, operatorStr); + type = targetObj.GetType(); + operatorStr = parts[1].ToLower(); // + valueStr = string.Join(" ", parts.Skip(2)); + } + else + { + // 类型语法不正确 + if (typeStartIndex >= typeEndIndex) + { + throw new ArgumentException("无效的表达式格式"); + } + memberPath = expression.Substring(0, typeStartIndex).Trim(); + string typeStr = expression.Substring(typeStartIndex + 1, typeEndIndex - typeStartIndex - 1) + .Trim().ToLower(); // 手动置顶的类型 + + // 对象取值表达式 + parts = expression.Substring(typeEndIndex + 1).Trim().Split(' '); + if (parts.Length == 3) + { + operatorStr = parts[1].ToLower(); // 操作类型 + valueStr = string.Join(" ", parts.Skip(2)); // 表达式值 + } + else + { + operatorStr = parts[0].ToLower(); // 操作类型 + valueStr = string.Join(" ", parts.Skip(1)); // 表达式值 + } + Type tempType; + + switch (typeStr) + { + case "bool": + tempType = typeof(bool); + break; + case "float": + tempType = typeof(float); + break; + case "decimal": + tempType = typeof(decimal); + break; + case "double": + tempType = typeof(double); + break; + case "sbyte": + tempType = typeof(sbyte); + break; + case "byte": + tempType = typeof(byte); + break; + case "short": + tempType = typeof(short); + break; + case "ushort": + tempType = typeof(ushort); + break; + case "int": + tempType = typeof(int); + break; + case "uint": + tempType = typeof(uint); + break; + case "long": + tempType = typeof(long); + break; + case "ulong": + tempType = typeof(ulong); + break; + // 如果需要支持 nint 和 nuint + // case "nint": + // tempType = typeof(nint); + // break; + // case "nuint": + // tempType = typeof(nuint); + // break; + case "string": + tempType = typeof(string); + break; + default: + tempType = Type.GetType(typeStr); + break; + } + type = tempType ?? throw new ArgumentException("对象表达式无效的类型声明"); + if (string.IsNullOrWhiteSpace(memberPath)) + { + targetObj = Convert.ChangeType(data, type); + } + else + { + targetObj = GetMemberValue(data, memberPath);// 获取对象成员,作为表达式的目标对象 + } + + } + + #region 解析类型 int + if (type.IsValueType) + { + //return GetValueResolver(type, valueStr, operatorStr, parts); + } + if (type == typeof(int)) + { + var op = ParseValueTypeOperator(operatorStr); + if (op == ValueTypeConditionResolver.Operator.InRange || op == ValueTypeConditionResolver.Operator.OutOfRange) + { + var temp = valueStr.Split('-'); + if (temp.Length < 2) + throw new ArgumentException($"范围无效:{valueStr}。"); + int rangeStart = int.Parse(temp[0], CultureInfo.InvariantCulture); + int rangeEnd = int.Parse(temp[1], CultureInfo.InvariantCulture); + return new MemberConditionResolver + { + Op = op, + RangeStart = rangeStart, + RangeEnd = rangeEnd, + TargetObj = targetObj, + ArithmeticExpression = GetArithmeticExpression(parts[0]), + }; + } + else + { + + int value = int.Parse(valueStr, CultureInfo.InvariantCulture); + + + return new MemberConditionResolver + { + TargetObj = targetObj, + //MemberPath = memberPath, + Op = ParseValueTypeOperator(operatorStr), + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + + + } + } + #endregion + #region 解析类型 double + else if (type == typeof(double)) + { + double value = double.Parse(valueStr, CultureInfo.InvariantCulture); + return new MemberConditionResolver + { + //MemberPath = memberPath, + TargetObj = targetObj, + Op = ParseValueTypeOperator(operatorStr), + Value = value, + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + + } + #endregion + #region 解析类型 bool + else if (type == typeof(bool)) + { + return new MemberConditionResolver + { + //MemberPath = memberPath, + TargetObj = targetObj, + Op = (ValueTypeConditionResolver.Operator)ParseBoolOperator(operatorStr) + }; + } + #endregion + #region 解析类型 string + else if (type == typeof(string)) + { + return new MemberStringConditionResolver + { + MemberPath = memberPath, + Op = ParseStringOperator(operatorStr), + Value = valueStr + }; + } + #endregion + + throw new NotSupportedException($"Type {type} is not supported."); + } + + + /// + /// 条件表达式解析 + /// + /// + /// + /// + /// + /// + private static SereinConditionResolver ParseSimpleExpression(object data, string expression) + { + if ("pass".Equals(expression.ToLower())) + { + return new PassConditionResolver + { + Op = PassConditionResolver.Operator.Pass, + }; + } + else + { + if ("not pass".Equals(expression.ToLower())) + { + return new PassConditionResolver + { + Op = PassConditionResolver.Operator.NotPass, + }; + } + if ("!pass".Equals(expression.ToLower())) + { + return new PassConditionResolver + { + Op = PassConditionResolver.Operator.NotPass, + }; + } + } + + + var parts = expression.Split(' '); + + if (parts.Length < 2) + throw new ArgumentException("无效的表达式格式。"); + + string operatorStr; + string valueStr; + Type type = null; + // 尝试获取指定类型 + int typeStartIndex = expression.IndexOf('<'); + int typeEndIndex = expression.IndexOf('>'); + if (typeStartIndex + typeStartIndex == -2) + { + // 如果不需要转为指定类型 + operatorStr = parts[0]; +#if NET8_0_OR_GREATER + valueStr = string.Join(' ', parts, 1, parts.Length - 1); + +#elif NET462_OR_GREATER + + valueStr = StringHelper.JoinStrings(parts, 1, parts.Length - 1, ' '); +#endif + + type = data.GetType(); + } + else + {//string typeStr = parts[0]; + string typeStr = expression.Substring(typeStartIndex + 1, typeEndIndex - typeStartIndex - 1) + .Trim().ToLower(); // 手动置顶的类型 + parts = expression.Substring(typeEndIndex + 1).Trim().Split(' '); + operatorStr = parts[0].ToLower(); // 操作类型 + valueStr = string.Join(" ", parts.Skip(1)); // 表达式值 + + + Type tempType; + + switch (typeStr) + { + case "bool": + tempType = typeof(bool); + break; + case "float": + tempType = typeof(float); + break; + case "decimal": + tempType = typeof(decimal); + break; + case "double": + tempType = typeof(double); + break; + case "sbyte": + tempType = typeof(sbyte); + break; + case "byte": + tempType = typeof(byte); + break; + case "short": + tempType = typeof(short); + break; + case "ushort": + tempType = typeof(ushort); + break; + case "int": + tempType = typeof(int); + break; + case "uint": + tempType = typeof(uint); + break; + case "long": + tempType = typeof(long); + break; + case "ulong": + tempType = typeof(ulong); + break; + // 如果需要支持 nint 和 nuint + // case "nint": + // tempType = typeof(nint); + // break; + // case "nuint": + // tempType = typeof(nuint); + // break; + case "string": + tempType = typeof(string); + break; + default: + tempType = Type.GetType(typeStr); + break; + } + } + + + if (type == typeof(bool)) + { + bool value = bool.Parse(valueStr); + return new BoolConditionResolver + { + Op = ParseBoolOperator(operatorStr), + Value = value, + }; + } + else if (type.IsValueType) + { + return GetValueResolver(type, valueStr, operatorStr, parts); + } + else if (type == typeof(string)) + { + return new StringConditionResolver + { + Op = ParseStringOperator(operatorStr), + Value = valueStr + }; + } + + throw new NotSupportedException($"Type {type} is not supported."); + } + + public static SereinConditionResolver GetValueResolver(Type valueType, string valueStr, string operatorStr, string[] parts)// where T : struct, IComparable + { + SereinConditionResolver resolver; + + if (valueType == typeof(float)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(decimal)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(double)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(sbyte)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(byte)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(short)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(ushort)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(int)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(uint)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(long)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(ulong)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } +#if NET8_0_OR_GREATER + else if (valueType == typeof(nint)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } + else if (valueType == typeof(nuint)) + { + resolver = GetValueResolver(valueStr, operatorStr, parts); + } +#endif + else + { + throw new ArgumentException("非预期值类型"); + } + + return resolver; + + } + + + private static ValueTypeConditionResolver GetValueResolver(string valueStr, string operatorStr, string[] parts) + where T :struct, IComparable + { + var op = ParseValueTypeOperator(operatorStr); + if (op == ValueTypeConditionResolver.Operator.InRange || op == ValueTypeConditionResolver.Operator.OutOfRange) + { + var temp = valueStr.Split('-'); + var leftNum = string.Empty; + var rightNum = string.Empty; + if (temp.Length < 2 || temp.Length > 4) + { + throw new ArgumentException($"范围无效:{valueStr}。"); + } + else if (temp.Length == 2) + { + leftNum = temp[0]; + rightNum = temp[1]; + } + else if (temp.Length == 3) + { + if (string.IsNullOrEmpty(temp[0]) + && !string.IsNullOrEmpty(temp[1]) + && !string.IsNullOrEmpty(temp[2])) + { + leftNum = "-" + temp[1]; + rightNum = temp[2]; + } + else + { + throw new ArgumentException($"范围无效:{valueStr}。"); + } + } + else if (temp.Length == 4) + { + if (string.IsNullOrEmpty(temp[0]) + && !string.IsNullOrEmpty(temp[1]) + && string.IsNullOrEmpty(temp[2]) + && !string.IsNullOrEmpty(temp[3])) + { + leftNum = "-" + temp[1]; + rightNum = temp[3]; + } + else + { + throw new ArgumentException($"范围无效:{valueStr}。"); + } + } + + + + return new ValueTypeConditionResolver + { + Op = op, + RangeStart = leftNum.ToValueData(), + RangeEnd = rightNum.ToValueData(), + ArithmeticExpression = GetArithmeticExpression(parts[0]), + }; + } + else + { + return new ValueTypeConditionResolver + { + Op = op, + Value = valueStr.ToValueData(), + ArithmeticExpression = GetArithmeticExpression(parts[0]) + }; + + } + } + //public static T ValueParse(object value) where T : struct, IComparable + //{ + // return (T)ValueParse(typeof(T), value); + //} + + //public static object ValueParse(Type type, object value) + //{ + + // string? valueStr = value.ToString(); + // if (string.IsNullOrEmpty(valueStr)) + // { + // throw new ArgumentException("value is null"); + // } + // object result = type switch + // { + // Type t when t.IsEnum => Enum.Parse(type, valueStr), + // Type t when t == typeof(bool) => bool.Parse(valueStr), + // Type t when t == typeof(float) => float.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(decimal) => decimal.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(double) => double.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(sbyte) => sbyte.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(byte) => byte.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(short) => short.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(ushort) => ushort.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(int) => int.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(uint) => uint.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(long) => long.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(ulong) => ulong.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(nint) => nint.Parse(valueStr, CultureInfo.InvariantCulture), + // Type t when t == typeof(nuint) => nuint.Parse(valueStr, CultureInfo.InvariantCulture), + // _ => throw new ArgumentException("非预期值类型") + // }; + // return result; + //} + + + + /// + /// 数值操作类型 + /// + /// + /// + /// + /// + private static ValueTypeConditionResolver.Operator ParseValueTypeOperator(string operatorStr) where T : struct, IComparable + { + if (operatorStr == ">") + { + return ValueTypeConditionResolver.Operator.GreaterThan; + } + else if (operatorStr == "<") + { + return ValueTypeConditionResolver.Operator.LessThan; + } + else if (operatorStr == "==") + { + return ValueTypeConditionResolver.Operator.Equal; + } + else if (operatorStr == ">=" || operatorStr == "≥") + { + return ValueTypeConditionResolver.Operator.GreaterThanOrEqual; + } + else if (operatorStr == "<=" || operatorStr == "≤") + { + return ValueTypeConditionResolver.Operator.LessThanOrEqual; + } + else if (operatorStr == "in") + { + return ValueTypeConditionResolver.Operator.InRange; + } + else if (operatorStr == "!in") + { + return ValueTypeConditionResolver.Operator.OutOfRange; + } + else + { + throw new ArgumentException($"Invalid operator {operatorStr} for value type."); + } + + } + + /// + /// 布尔操作类型 + /// + /// + /// + /// + private static BoolConditionResolver.Operator ParseBoolOperator(string operatorStr) + { + if (operatorStr == "is" || operatorStr == "==" || operatorStr == "equals") + { + return BoolConditionResolver.Operator.Is; + } + else + { + throw new ArgumentException($"Invalid operator {operatorStr} for bool type."); + } + + } + + /// + /// 字符串操作类型 + /// + /// + /// + /// + private static StringConditionResolver.Operator ParseStringOperator(string operatorStr) + { + operatorStr = operatorStr.ToLower(); + + if (operatorStr == "c" || operatorStr == "contains") + { + return StringConditionResolver.Operator.Contains; + } + else if (operatorStr == "nc" || operatorStr == "doesnotcontain") + { + return StringConditionResolver.Operator.DoesNotContain; + } + else if (operatorStr == "sw" || operatorStr == "startswith") + { + return StringConditionResolver.Operator.StartsWith; + } + else if (operatorStr == "ew" || operatorStr == "endswith") + { + return StringConditionResolver.Operator.EndsWith; + } + else if (operatorStr == "==" || operatorStr == "equals") + { + return StringConditionResolver.Operator.Equal; + } + else if (operatorStr == "!=" || operatorStr == "notequals") + { + return StringConditionResolver.Operator.NotEqual; + } + else + { + throw new ArgumentException($"Invalid operator {operatorStr} for string type."); + } + + } + + } +} diff --git a/Library/Utils/SereinExpression/SereinConditionResolver.cs b/Library/Utils/SereinExpression/SereinConditionResolver.cs new file mode 100644 index 0000000..e0ac6be --- /dev/null +++ b/Library/Utils/SereinExpression/SereinConditionResolver.cs @@ -0,0 +1,12 @@ +using System.Reflection; + +namespace Serein.NodeFlow.Tool.SereinExpression +{ + /// + /// 条件解析抽象类 + /// + public abstract class SereinConditionResolver + { + public abstract bool Evaluate(object obj); + } +} diff --git a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs new file mode 100644 index 0000000..ada8fbf --- /dev/null +++ b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs @@ -0,0 +1,378 @@ +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; + +namespace Serein.NodeFlow.Tool.SereinExpression +{ + /// + /// 使用表达式操作/获取 对象的值 + /// 获取值 @get .xx.xxx + /// 设置值 @set .xx.xxx = [data] + /// + /// 操作的对象 + /// + public class SerinArithmeticExpressionEvaluator where T : struct, IComparable + { + private static readonly DataTable table = new DataTable(); + + public static T Evaluate(string expression, T inputValue) + { + + // 替换占位符@为输入值 + expression = expression.Replace("@", inputValue.ToString()); + try + { + // 使用 DataTable.Compute 方法计算表达式 + var result = table.Compute(expression, string.Empty); + return (T)result; + } + catch + { + throw new ArgumentException("Invalid arithmetic expression."); + } + } + } + + public class SerinExpressionEvaluator + { + /// + /// + /// + /// 表达式 + /// 操作对象 + /// 是否改变了对象(Set语法) + /// + /// + /// + public static object Evaluate(string expression, object targetObJ, out bool isChange) + { + //var parts = expression.Split([' '], 2); + + var parts = expression.Split(new[] { ' ' }, 2, StringSplitOptions.None); + if (parts.Length != 2) + { + throw new ArgumentException("Invalid expression format."); + } + + var operation = parts[0].ToLower(); + var operand = parts[1][0] == '.' ? parts[1].Substring(1) : parts[1]; + object result; + if (operation == "@num") + { + result = ComputedNumber(targetObJ, operand); + } + else if (operation == "@call") + { + result = InvokeMethod(targetObJ, operand); + } + else if (operation == "@get") + { + result = GetMember(targetObJ, operand); + } + else if (operation == "@set") + { + result = SetMember(targetObJ, operand); + } + else + { + throw new NotSupportedException($"Operation {operation} is not supported."); + } + + if(operation == "@set") + { + isChange = true; + } + else + { + isChange = false; + } + + return result; + } + + + private static readonly char[] separator = new char[] { '(', ')' }; + private static readonly char[] separatorArray = new char[] { ',' }; + + /// + /// 调用目标方法 + /// + /// 目标实例 + /// 方法名称 + /// + /// + private static object InvokeMethod(object target, string methodCall) + { + if (target is null) return null; + var methodParts = methodCall.Split(separator, StringSplitOptions.RemoveEmptyEntries); + if (methodParts.Length != 2) + { + throw new ArgumentException("Invalid method call format."); + } + + var methodName = methodParts[0]; + var parameterList = methodParts[1]; + var parameters = parameterList.Split(separatorArray, StringSplitOptions.RemoveEmptyEntries) + .Select(p => p.Trim()) + .ToArray(); + + var method = target.GetType().GetMethod(methodName) ?? throw new ArgumentException($"Method {methodName} not found on target."); + var parameterValues = method.GetParameters() + .Select((p, index) => Convert.ChangeType(parameters[index], p.ParameterType)) + .ToArray(); + + + return method.Invoke(target, parameterValues); + + } + /// + /// 获取值 + /// + /// 目标实例 + /// 属性路径 + /// + /// + private static object GetMember(object target, string memberPath) + { + if (target is null) return null; + // 分割成员路径,按 '.' 处理多级访问 + var members = memberPath.Split('.'); + + foreach (var member in members) + { + // 检查成员是否包含数组索引,例如 "cars[0]" + var arrayIndexStart = member.IndexOf('['); + if (arrayIndexStart != -1) + { + // 解析数组/集合名与索引部分 + var arrayName = member.Substring(0, arrayIndexStart); + var arrayIndexEnd = member.IndexOf(']'); + if (arrayIndexEnd == -1 || arrayIndexEnd <= arrayIndexStart + 1) + { + throw new ArgumentException($"Invalid array syntax for member {member}"); + } + + // 提取数组索引 + var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1); + if (!int.TryParse(indexStr, out int index)) + { + throw new ArgumentException($"Invalid array index '{indexStr}' for member {member}"); + } + + // 获取数组或集合对象 + var arrayProperty = target?.GetType().GetProperty(arrayName); + if (arrayProperty is null) + { + var arrayField = target?.GetType().GetField(arrayName); + if (arrayField is null) + { + throw new ArgumentException($"Member {arrayName} not found on target."); + } + else + { + target = arrayField.GetValue(target); + } + } + else + { + target = arrayProperty.GetValue(target); + } + + // 访问数组或集合中的指定索引 + if (target is Array array) + { + if (index < 0 || index >= array.Length) + { + throw new ArgumentException($"Index {index} out of bounds for array {arrayName}"); + } + target = array.GetValue(index); + } + else if (target is IList list) + { + if (index < 0 || index >= list.Count) + { + throw new ArgumentException($"Index {index} out of bounds for list {arrayName}"); + } + target = list[index]; + } + else + { + throw new ArgumentException($"Member {arrayName} is not an array or list."); + } + } + else + { + // 处理非数组情况的属性或字段 + var property = target?.GetType().GetProperty(member); + if (property is null) + { + var field = target?.GetType().GetField(member); + if (field is null) + { + throw new ArgumentException($"Member {member} not found on target."); + } + else + { + target = field.GetValue(target); + } + } + else + { + target = property.GetValue(target); + } + } + } + return target; + } + + /// + /// 设置目标的值 + /// + /// 目标实例 + /// 属性路径 + /// + /// + private static object SetMember(object target, string assignment) + { + var parts = assignment.Split(new[] { '=' }, 2); + if (parts.Length != 2) + { + throw new ArgumentException("Invalid assignment format."); + } + + var memberPath = parts[0].Trim(); + var value = parts[1].Trim(); + + var members = memberPath.Split('.'); + for (int i = 0; i < members.Length - 1; i++) + { + var member = members[i]; + + // 检查是否包含数组索引 + var arrayIndexStart = member.IndexOf('['); + if (arrayIndexStart != -1) + { + // 解析数组名和索引 + var arrayName = member.Substring(0, arrayIndexStart); + var arrayIndexEnd = member.IndexOf(']'); + if (arrayIndexEnd == -1 || arrayIndexEnd <= arrayIndexStart + 1) + { + throw new ArgumentException($"Invalid array syntax for member {member}"); + } + + var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1); + if (!int.TryParse(indexStr, out int index)) + { + throw new ArgumentException($"Invalid array index '{indexStr}' for member {member}"); + } + + // 获取数组或集合 + var arrayProperty = target?.GetType().GetProperty(arrayName); + if (arrayProperty is null) + { + var arrayField = target?.GetType().GetField(arrayName); + if (arrayField is null) + { + throw new ArgumentException($"Member {arrayName} not found on target."); + } + else + { + target = arrayField.GetValue(target); + } + + } + else + { + target = arrayProperty.GetValue(target); + } + + // 获取目标数组或集合中的指定元素 + if (target is Array array) + { + if (index < 0 || index >= array.Length) + { + throw new ArgumentException($"Index {index} out of bounds for array {arrayName}"); + } + target = array.GetValue(index); + } + else if (target is IList list) + { + if (index < 0 || index >= list.Count) + { + throw new ArgumentException($"Index {index} out of bounds for list {arrayName}"); + } + target = list[index]; + } + else + { + throw new ArgumentException($"Member {arrayName} is not an array or list."); + } + } + else + { + // 处理非数组情况的属性或字段 + var property = target?.GetType().GetProperty(member); + if (property is null) + { + var field = target?.GetType().GetField(member); + if (field is null) + { + throw new ArgumentException($"Member {member} not found on target."); + } + else + { + target = field.GetValue(target); + } + } + else + { + target = property.GetValue(target); + } + } + } + + // 设置值 + var lastMember = members.Last(); + + var lastProperty = target?.GetType().GetProperty(lastMember); + if (lastProperty is null) + { + var lastField = target?.GetType().GetField(lastMember); + if (lastField is null) + { + throw new ArgumentException($"Member {lastMember} not found on target."); + } + else + { + var convertedValue = Convert.ChangeType(value, lastField.FieldType); + lastField.SetValue(target, convertedValue); + } + } + else + { + var convertedValue = Convert.ChangeType(value, lastProperty.PropertyType); + lastProperty.SetValue(target, convertedValue); + } + + return target; + } + /// + /// 计算数学简单表达式 + /// + /// + /// + /// + private static decimal ComputedNumber(object value, string expression) + { + return ComputedNumber(value, expression); + } + + private static T ComputedNumber(object value, string expression) where T : struct, IComparable + { + T result = value.ToConvert(); + return SerinArithmeticExpressionEvaluator.Evaluate(expression, result); + } + } +} diff --git a/Library/Utils/SereinIoc.cs b/Library/Utils/SereinIoc.cs index d777ea5..6f5323f 100644 --- a/Library/Utils/SereinIoc.cs +++ b/Library/Utils/SereinIoc.cs @@ -87,8 +87,7 @@ namespace Serein.Library.Utils /// /// 用于临时实例的创建,不登记到IOC容器中,依赖项注入失败时也不记录。 /// - /// - /// + /// /// public object Instantiate(Type type) { diff --git a/NodeFlow/Base/NodeModelBaseFunc.cs b/NodeFlow/Base/NodeModelBaseFunc.cs index 3c84bd3..610b7fd 100644 --- a/NodeFlow/Base/NodeModelBaseFunc.cs +++ b/NodeFlow/Base/NodeModelBaseFunc.cs @@ -233,7 +233,7 @@ namespace Serein.NodeFlow.Base try { object?[]? args = GetParameters(context, this, md); - result = await dd.Invoke(md.ActingInstance, args); + result = await dd.InvokeAsync(md.ActingInstance, args); NextOrientation = ConnectionType.IsSucceed; return result; } diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs index f105b67..d47d667 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/FlowEnvironment.cs @@ -8,9 +8,13 @@ using Serein.Library.Utils; using Serein.NodeFlow.Base; using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; +using System; using System.Collections; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net.Sockets; using System.Reflection; +using System.Security.Cryptography; using System.Xml.Linq; using static Serein.Library.Utils.ChannelFlowInterrupt; using static Serein.NodeFlow.FlowStarter; @@ -180,11 +184,20 @@ namespace Serein.NodeFlow /// public ISereinIOC IOC { get => this; } + /// + /// Library 与 MethodDetailss的依赖关系 + /// + public ConcurrentDictionary> MethodDetailsOfLibrarys { get; } = []; /// - /// 描述所有DLL中NodeAction特性的方法的原始副本 + /// 存储已加载的程序集 /// - public Dictionary> MethodDetailss { get; } = []; + public ConcurrentDictionary Librarys { get; } = []; + + /// + /// 存储已加载的方法信息。描述所有DLL中NodeAction特性的方法的原始副本 + /// + public ConcurrentDictionary MethodDetailss { get; } = []; #endregion @@ -194,17 +207,6 @@ namespace Serein.NodeFlow /// public readonly SereinIOC sereinIOC; - /// - /// 存储加载的程序集路径 - /// - - /// - /// 存储加载的程序集 - /// - public List NodeLibrarys { get; } = []; - - - /// /// 环境加载的节点集合 /// Node Guid - Node Model @@ -266,7 +268,9 @@ namespace Serein.NodeFlow #endregion - #region 对外暴露的接口 + #region 基础接口 + + /// /// 异步运行 @@ -281,15 +285,22 @@ namespace Serein.NodeFlow List initMethods = []; List loadMethods = []; List exitMethods = []; - foreach(var mds in MethodDetailss.Values) - { - var initMds = mds.Where(it => it.MethodDynamicType == NodeType.Init); - var loadMds = mds.Where(it => it.MethodDynamicType == NodeType.Loading); - var exitMds = mds.Where(it => it.MethodDynamicType == NodeType.Exit); - initMethods.AddRange(initMds); - loadMethods.AddRange(loadMds); - exitMethods.AddRange(exitMds); - } + //foreach(var mds in MethodDetailss.Values) + //{ + // var initMds = mds.Where(it => it.MethodDynamicType == NodeType.Init); + // var loadMds = mds.Where(it => it.MethodDynamicType == NodeType.Loading); + // var exitMds = mds.Where(it => it.MethodDynamicType == NodeType.Exit); + // initMethods.AddRange(initMds); + // loadMethods.AddRange(loadMds); + // exitMethods.AddRange(exitMds); + //} + var initMds = MethodDetailss.Values.Where(it => it.MethodDynamicType == NodeType.Init); + var loadMds = MethodDetailss.Values.Where(it => it.MethodDynamicType == NodeType.Loading); + var exitMds = MethodDetailss.Values.Where(it => it.MethodDynamicType == NodeType.Exit); + initMethods.AddRange(initMds); + loadMethods.AddRange(loadMds); + exitMethods.AddRange(exitMds); + this.IOC.Reset(); // 开始运行时清空ioc中注册的实例 this.IOC.CustomRegisterInstance(typeof(IFlowEnvironment).FullName,this); @@ -354,8 +365,8 @@ namespace Serein.NodeFlow public void ClearAll() { //LoadedAssemblyPaths.Clear(); - NodeLibrarys.Clear(); - MethodDetailss.Clear(); + //NodeLibrarys.Clear(); + //MethodDetailss.Clear(); } @@ -396,8 +407,10 @@ namespace Serein.NodeFlow } else { - TryGetMethodDetails(nodeInfo.MethodName, out MethodDetails? methodDetails); // 加载项目时尝试获取方法信息 - if(controlType == NodeControlType.ExpOp || controlType == NodeControlType.ExpOp) + //TryGetMethodDetails(nodeInfo.MethodName, out MethodDetailsInfo? methodDetailsInfo); + + MethodDetailss.TryGetValue(nodeInfo.MethodName, out var methodDetails);// 加载项目时尝试获取方法信息 + if (controlType == NodeControlType.ExpOp || controlType == NodeControlType.ExpOp) { methodDetails ??= new MethodDetails(); } @@ -495,22 +508,53 @@ namespace Serein.NodeFlow SetStartNode(project.StartNode); OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs()); } - + /// - /// 保存项目为项目文件 + /// 加载远程项目 + /// + /// 远程项目地址 + /// 远程项目端口 + /// 密码 + public void LoadRemoteProject(string addres, int port, string token) + { + // -- 第1种,直接从远程环境复制所有dll信息,项目信息,在本地打开?(安全问题) + // 第2种,WebSocket连接到远程环境,实时接收远程环境的响应? + Console.WriteLine($"准备连接:{addres}:{port},{token}"); + + // bool success = false; + //try + //{ + // TcpClient tcpClient = new TcpClient(); + // var result = tcpClient.BeginConnect(addres, port, null, null); + // success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(3)); + //} + //catch (Exception ex) + //{ + + //} + //if (!success) + //{ + // Console.WriteLine($"无法连接远程:{addres}:{port}"); + //} + } + + + /// + /// 序列化当前项目的依赖信息、节点信息 /// /// - public SereinProjectData SaveProject() + public SereinProjectData GetProjectInfo() { var projectData = new SereinProjectData() { - Librarys = NodeLibrarys.Select(assemblies => assemblies.Assembly.ToLibrary()).ToArray(), + Librarys = Librarys.Values.Select(assemblies => assemblies.Assembly.ToLibrary()).ToArray(), Nodes = Nodes.Values.Select(node => node.ToInfo()).Where(info => info is not null).ToArray(), StartNode = Nodes.Values.FirstOrDefault(it => it.IsStart)?.Guid, }; return projectData; } + /// /// 从文件路径中加载DLL /// @@ -521,9 +565,14 @@ namespace Serein.NodeFlow LoadDllNodeInfo(dllPath); } + /// + /// 移除DLL + /// + /// + /// public bool RemoteDll(string assemblyFullName) { - var library = NodeLibrarys.FirstOrDefault(nl => assemblyFullName.Equals(nl.Assembly.FullName)); + var library = Librarys.Values.FirstOrDefault(nl => assemblyFullName.Equals(nl.Assembly.FullName)); if(library is null) { return false; @@ -535,12 +584,14 @@ namespace Serein.NodeFlow .ToDictionary( key => key.Key, group => group.Count()); + + if(Nodes.Count == 0) { return true; // 当前无节点,可以直接删除 } - if (MethodDetailss.TryGetValue(library,out var mds)) // 存在方法 + if (MethodDetailsOfLibrarys.TryGetValue(library,out var mds)) // 存在方法 { foreach(var md in mds) { @@ -548,12 +599,17 @@ namespace Serein.NodeFlow { if (count > 0) { - return false; // 创建过相关的节点 + return false; // 创建过相关的节点,无法移除 } } } - MethodDetailss.Remove(library); - return true; // 没有创建相关的节点 + // 开始移除相关信息 + foreach (var md in mds) + { + MethodDetailss.TryRemove(md.MethodName, out _); + } + MethodDetailsOfLibrarys.TryRemove(library, out _); + return true; } else { @@ -567,9 +623,14 @@ namespace Serein.NodeFlow /// /// /// - /// - public void CreateNode(NodeControlType nodeControlType, Position position, MethodDetails? methodDetails = null) + /// 如果是表达式节点条件节点,该项为null + public void CreateNode(NodeControlType nodeControlType, Position position, MethodDetailsInfo? methodDetailsInfo = null) { + MethodDetails? methodDetails = null; + if (methodDetailsInfo != null &&!MethodDetailss.TryGetValue(methodDetailsInfo.MethodName, out methodDetails)) + { + return; + } var nodeModel = CreateNode(nodeControlType, methodDetails); TryAddNode(nodeModel); @@ -646,8 +707,8 @@ namespace Serein.NodeFlow /// /// 连接节点 /// - /// 起始节点 - /// 目标节点 + /// 起始节点 + /// 目标节点 /// 连接关系 public void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) { @@ -684,13 +745,13 @@ namespace Serein.NodeFlow /// /// 获取方法描述 /// - public bool TryGetMethodDetails(string name, out MethodDetails? md) + public bool TryGetMethodDetailsInfo(string name, out MethodDetailsInfo? md) { if (!string.IsNullOrEmpty(name)) { - foreach(var mds in MethodDetailss.Values) + foreach(var t_md in MethodDetailss.Values) { - md = mds.FirstOrDefault(it => it.MethodName == name); + md = t_md.ToInfo(); if(md != null) { return true; @@ -706,6 +767,16 @@ namespace Serein.NodeFlow } } + /// + /// 通过方法名称获取对应的Emit委托 + /// 方法无入参时需要传入空数组,void方法自动返回null + /// 普通方法:Func<object,object[],object> + /// 异步方法:Func<object,object[],Task> + /// 异步有返回值方法:Func<object,object[],Task<object>> + /// + /// + /// + /// public bool TryGetDelegateDetails(string methodName, out DelegateDetails? delegateDetails) { @@ -875,7 +946,8 @@ namespace Serein.NodeFlow } } - } /// + } + /// /// 关闭全局触发器 /// /// @@ -888,13 +960,48 @@ namespace Serein.NodeFlow flowStarter.TerminateGlobalFlipflopRuning(flipflopNode); } } - + + /// + /// 环境执行中断 + /// + /// public Task GetOrCreateGlobalInterruptAsync() { IsGlobalInterrupt = true; return ChannelFlowInterrupt.GetOrCreateChannelAsync(this.EnvName); } + /// + /// 获取当前环境信息(远程连接) + /// + /// + public object GetEnvInfo() + { + Dictionary> LibraryMds = []; + + foreach (var mdskv in this.MethodDetailsOfLibrarys) + { + var library = mdskv.Key; + var mds = mdskv.Value; + foreach (var md in mds) + { + if (!LibraryMds.TryGetValue(library, out var t_mds)) + { + t_mds = new List(); + LibraryMds[library] = t_mds; + } + var mdInfo = md.ToInfo(); + mdInfo.LibraryName = library.Assembly.GetName().FullName; + t_mds.Add(mdInfo); + } + } + var project = this.GetProjectInfo(); + return new + { + project = project, + envNode = LibraryMds.Values, + }; + } /// /// Guid 转 NodeModel @@ -902,7 +1009,7 @@ namespace Serein.NodeFlow /// 节点Guid /// 节点Model /// 无法获取节点、Guid/节点为null时报错 - public NodeModelBase? GuidToModel(string nodeGuid) + private NodeModelBase? GuidToModel(string nodeGuid) { if (string.IsNullOrEmpty(nodeGuid)) { @@ -931,10 +1038,15 @@ namespace Serein.NodeFlow (var nodeLibrary, var registerTypes, var mdlist) = LoadAssembly(dllPath); if (nodeLibrary is not null && mdlist.Count > 0) { - MethodDetailss.Add(nodeLibrary, mdlist); - NodeLibrarys.Add(nodeLibrary); + Librarys.TryAdd(nodeLibrary.Name, nodeLibrary); + MethodDetailsOfLibrarys.TryAdd(nodeLibrary, mdlist); + + foreach(var md in mdlist) + { + MethodDetailss.TryAdd(md.MethodName, md); + } - foreach(var kv in registerTypes) + foreach (var kv in registerTypes) { if (!AutoRegisterTypes.TryGetValue(kv.Key, out var types)) { @@ -943,8 +1055,8 @@ namespace Serein.NodeFlow } types.AddRange(kv.Value); } - - OnDllLoad?.Invoke(new LoadDllEventArgs(nodeLibrary, mdlist)); // 通知UI创建dll面板显示 + var mdInfo = mdlist.Select(md => md.ToInfo()).ToList(); // 转换成方法信息 + OnDllLoad?.Invoke(new LoadDllEventArgs(nodeLibrary, mdInfo)); // 通知UI创建dll面板显示 } } @@ -1046,6 +1158,7 @@ namespace Serein.NodeFlow var nodeLibrary = new NodeLibrary { + Name = assembly.GetName().FullName, Assembly = assembly, Path = dllPath, }; diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index 70ed71c..5bc6e80 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -24,15 +24,7 @@ namespace Serein.NodeFlow /// public class FlowStarter { - /// - /// 全局触发器CTS - /// - public const string FlipFlopCtsName = "<>.FlowFlipFlopCts"; - /// - /// 流程运行CTS - /// - public const string FlowRungCtsName = "<>.FlowRungCtsName"; - + public FlowStarter() { } @@ -209,7 +201,7 @@ namespace Serein.NodeFlow { throw new Exception("不存在对应委托"); } - await dd.Invoke(md.ActingInstance, [Context]); + await dd.InvokeAsync(md.ActingInstance, [Context]); //((Func)dd.EmitDelegate).Invoke(md.ActingInstance, [Context]); } Context.Env.IOC.Build(); // 绑定初始化时注册的类型 @@ -230,7 +222,7 @@ namespace Serein.NodeFlow { throw new Exception("不存在对应委托"); } - await dd.Invoke(md.ActingInstance, [Context]); + await dd.InvokeAsync(md.ActingInstance, [Context]); //((Action)del).Invoke(md.ActingInstance, [Context]); //((Func)dd.EmitDelegate).Invoke(md.ActingInstance, [Context]); } @@ -250,7 +242,7 @@ namespace Serein.NodeFlow { throw new Exception("不存在对应委托"); } - await dd.Invoke(md.ActingInstance, [Context]); + await dd.InvokeAsync(md.ActingInstance, [Context]); //((Func)dd.EmitDelegate).Invoke(md.ActingInstance, [Context]); } @@ -277,7 +269,7 @@ namespace Serein.NodeFlow env.FlipFlopState = RunState.Running; // 如果存在需要启动的触发器,则开始启动 _flipFlopCts = new CancellationTokenSource(); - env.IOC.CustomRegisterInstance(FlipFlopCtsName, _flipFlopCts,false); + env.IOC.CustomRegisterInstance(NodeStaticConfig.FlipFlopCtsName, _flipFlopCts,false); // 使用 TaskCompletionSource 创建未启动的触发器任务 var tasks = flipflopNodes.Select(async node => diff --git a/NodeFlow/Model/CompositeActionNode.cs b/NodeFlow/Model/CompositeActionNode.cs index d8c78e8..a9858ca 100644 --- a/NodeFlow/Model/CompositeActionNode.cs +++ b/NodeFlow/Model/CompositeActionNode.cs @@ -26,7 +26,7 @@ namespace Serein.NodeFlow.Model throw new NotImplementedException("动作区域暂未实现"); } - internal override Parameterdata[] GetParameterdatas() + public override Parameterdata[] GetParameterdatas() { return []; } diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index 506b46a..100a22a 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -37,7 +37,7 @@ namespace Serein.NodeFlow.Model break; } } - return Task.FromResult(PreviousNode?.GetFlowData()); // 条件区域透传上一节点的数据 + return Task.FromResult( PreviousNode?.GetFlowData()); // 条件区域透传上一节点的数据 } @@ -57,7 +57,7 @@ namespace Serein.NodeFlow.Model } } - internal override Parameterdata[] GetParameterdatas() + public override Parameterdata[] GetParameterdatas() { return []; } diff --git a/NodeFlow/Model/SingleActionNode.cs b/NodeFlow/Model/SingleActionNode.cs index d29b82c..a6ea759 100644 --- a/NodeFlow/Model/SingleActionNode.cs +++ b/NodeFlow/Model/SingleActionNode.cs @@ -10,7 +10,7 @@ namespace Serein.NodeFlow.Model public class SingleActionNode : NodeModelBase { - internal override Parameterdata[] GetParameterdatas() + public override Parameterdata[] GetParameterdatas() { if (base.MethodDetails.ParameterDetailss.Length > 0) { diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 264c0f0..1886b06 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -71,7 +71,7 @@ namespace Serein.NodeFlow.Model return Task.FromResult(result); } - internal override Parameterdata[] GetParameterdatas() + public override Parameterdata[] GetParameterdatas() { var value = CustomData switch { @@ -90,8 +90,6 @@ namespace Serein.NodeFlow.Model }]; } - - public override NodeModelBase LoadInfo(NodeInfo nodeInfo) { var node = this; diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 2fd2f38..079e846 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -49,7 +49,7 @@ namespace Serein.NodeFlow.Model } - internal override Parameterdata[] GetParameterdatas() + public override Parameterdata[] GetParameterdatas() { return [new Parameterdata{ Expression = Expression}]; } diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index bc726fc..be3b27f 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -42,7 +42,7 @@ namespace Serein.NodeFlow.Model try { var args = GetParameters(context, this, md); - var result = await dd.Invoke(md.ActingInstance, args); + var result = await dd.InvokeAsync(md.ActingInstance, args); dynamic flipflopContext = result; FlipflopStateType flipflopStateType = flipflopContext.State; NextOrientation = flipflopStateType.ToContentType(); @@ -80,7 +80,7 @@ namespace Serein.NodeFlow.Model { return context.Value; // dynamic 会在运行时处理类型 } - internal override Parameterdata[] GetParameterdatas() + public override Parameterdata[] GetParameterdatas() { if (base.MethodDetails.ParameterDetailss.Length > 0) { diff --git a/NodeFlow/MoveNodeData.cs b/NodeFlow/MoveNodeData.cs index 785bc2b..0996b63 100644 --- a/NodeFlow/MoveNodeData.cs +++ b/NodeFlow/MoveNodeData.cs @@ -11,6 +11,7 @@ namespace Serein.NodeFlow public class MoveNodeData { public NodeControlType NodeControlType { get; set; } - public MethodDetails MethodDetails { get; set; } + // public MethodDetails MethodDetails { get; set; } + public MethodDetailsInfo MethodDetailsInfo { get; set; } } } diff --git a/NodeFlow/NodeStaticConfig.cs b/NodeFlow/NodeStaticConfig.cs index 1b56a0a..7863c16 100644 --- a/NodeFlow/NodeStaticConfig.cs +++ b/NodeFlow/NodeStaticConfig.cs @@ -1,5 +1,4 @@ -using Serein.Library.Api; -using Serein.Library.Enums; +using Serein.Library.Enums; using System; using System.Collections.Generic; using System.Linq; diff --git a/NodeFlow/Serein.NodeFlow.csproj b/NodeFlow/Serein.NodeFlow.csproj index 5abf4d8..64eec3c 100644 --- a/NodeFlow/Serein.NodeFlow.csproj +++ b/NodeFlow/Serein.NodeFlow.csproj @@ -19,18 +19,25 @@ + + + + + + + diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index d7e1713..92039b8 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -10,7 +10,6 @@ Loaded="Window_Loaded" ContentRendered="Window_ContentRendered" PreviewKeyDown="Window_PreviewKeyDown" - PreviewTextInput="Window_PreviewTextInput" Closing="Window_Closing"> @@ -36,40 +35,56 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - + + - - /// - /// - /// + /// /// private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, Position position) { @@ -1764,6 +1755,7 @@ namespace Serein.WorkBench /// private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { + Console.WriteLine(1); if (!IsSelectControl) { // 进入选取状态 @@ -2335,32 +2327,8 @@ namespace Serein.WorkBench #endregion - /// - /// 卸载DLL文件,清空当前项目 - /// - /// - /// - private void UnloadAllButton_Click(object sender, RoutedEventArgs e) - { - FlowEnvironment.ClearAll(); - - - } - /// - /// 卸载DLL文件,清空当前项目 - /// - /// - /// - private void UnloadAllAssemblies() - { - DllStackPanel.Children.Clear(); - FlowChartCanvas.Children.Clear(); - Connections.Clear(); - NodeControls.Clear(); - currentLine = null; - startConnectNodeControl = null; - MessageBox.Show("所有DLL已卸载。", "信息", MessageBoxButton.OK, MessageBoxImage.Information); - } + + #region 顶部菜单栏 - 调试功能区 /// /// 运行测试 @@ -2394,7 +2362,7 @@ namespace Serein.WorkBench /// private async void ButtonStartFlowInSelectNode_Click(object sender, RoutedEventArgs e) { - if(selectNodeControls.Count == 0) + if (selectNodeControls.Count == 0) { Console.WriteLine("请至少选择一个节点"); } @@ -2409,6 +2377,14 @@ namespace Serein.WorkBench } + + + + #endregion + + #region 顶部菜单栏 - 项目文件菜单 + + /// /// 保存为项目文件 /// @@ -2416,7 +2392,7 @@ namespace Serein.WorkBench /// private void ButtonSaveFile_Click(object sender, RoutedEventArgs e) { - var projectData = FlowEnvironment.SaveProject(); + var projectData = FlowEnvironment.GetProjectInfo(); projectData.Basic = new Basic { @@ -2432,10 +2408,10 @@ namespace Serein.WorkBench Versions = "1", }; - foreach(var node in projectData.Nodes) + foreach (var node in projectData.Nodes) { - - if(NodeControls.TryGetValue(node.Guid,out var nodeControl)) + + if (NodeControls.TryGetValue(node.Guid, out var nodeControl)) { Point positionRelativeToParent = nodeControl.TranslatePoint(new Point(0, 0), FlowChartCanvas); node.Position = new Position(positionRelativeToParent.X, positionRelativeToParent.Y); @@ -2481,7 +2457,7 @@ namespace Serein.WorkBench } JObject projectJsonData = JObject.FromObject(projectData); - savaProjectFile?.Invoke(savePath, projectJsonData.ToString()); + savaProjectFile?.Invoke(savePath, projectJsonData.ToString()); } public static bool SaveContentToFile(out string savePath, out Action? savaProjectFile) { @@ -2499,7 +2475,7 @@ namespace Serein.WorkBench // 如果用户选择了文件并点击了保存按钮 if (result == true) { - savePath = saveFileDialog.FileName; + savePath = saveFileDialog.FileName; savaProjectFile = File.WriteAllText; return true; } @@ -2507,18 +2483,57 @@ namespace Serein.WorkBench savaProjectFile = null; return false; } - public static string GetRelativePath(string baseDirectory, string fullPath) - { - Uri baseUri = new(baseDirectory + System.IO.Path.DirectorySeparatorChar); - Uri fullUri = new(fullPath); - 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 OpenLocalProject_Click(object sender, RoutedEventArgs e) { } + + /// + /// 连接远程运行环境 + /// + /// + /// + private void OpenRemoteProject_Click(object sender, RoutedEventArgs e) + { + var windowEnvRemoteLoginView = new WindowEnvRemoteLoginView((addres,port,token) => + { + this.FlowEnvironment.LoadRemoteProject(addres, port, token); + }); + windowEnvRemoteLoginView.Show(); + + } + + #endregion + + #region 顶部菜单栏 - 视图管理 + /// + /// 重置画布 + /// + /// + /// + private void ButtonResetCanvas_Click(object sender, RoutedEventArgs e) + { + translateTransform.X = 0; + translateTransform.Y = 0; + scaleTransform.ScaleX = 1; + scaleTransform.ScaleY = 1; + } + private void ButtonOpenConsoleOutWindow_Click(object sender, RoutedEventArgs e) + { + logWindow?.Show(); + } + + #endregion + + + + /// /// 按键监听。esc取消操作 /// @@ -2596,16 +2611,29 @@ namespace Serein.WorkBench data = SerinExpressionEvaluator.Evaluate(exp,result!, out isChange); Console.WriteLine($"{exp} => {data}"); } - /// - /// 重置画布 + /// 卸载DLL文件,清空当前项目 /// /// /// - private void ButtonResetCanvas_Click(object sender, RoutedEventArgs e) + private void UnloadAllButton_Click(object sender, RoutedEventArgs e) { - translateTransform.X = 0; - translateTransform.Y = 0; + FlowEnvironment.ClearAll(); + + + } + /// + /// 卸载DLL文件,清空当前项目 + /// + private void UnloadAllAssemblies() + { + DllStackPanel.Children.Clear(); + FlowChartCanvas.Children.Clear(); + Connections.Clear(); + NodeControls.Clear(); + currentLine = null; + startConnectNodeControl = null; + MessageBox.Show("所有DLL已卸载。", "信息", MessageBoxButton.OK, MessageBoxImage.Information); } } diff --git a/WorkBench/Node/View/ConditionRegionControl.xaml.cs b/WorkBench/Node/View/ConditionRegionControl.xaml.cs index e1a650a..872d668 100644 --- a/WorkBench/Node/View/ConditionRegionControl.xaml.cs +++ b/WorkBench/Node/View/ConditionRegionControl.xaml.cs @@ -31,7 +31,7 @@ namespace Serein.WorkBench.Node.View /// /// 添加条件控件 /// - /// + /// public void AddCondition(NodeControlBase node) { ((CompositeConditionNode)ViewModel.Node).AddNode((SingleConditionNode)node.ViewModel.Node); diff --git a/WorkBench/Node/View/DllControlControl.xaml b/WorkBench/Node/View/DllControlControl.xaml index 43a5078..461d472 100644 --- a/WorkBench/Node/View/DllControlControl.xaml +++ b/WorkBench/Node/View/DllControlControl.xaml @@ -14,22 +14,17 @@ - - - - + - + diff --git a/WorkBench/Node/View/DllControlControl.xaml.cs b/WorkBench/Node/View/DllControlControl.xaml.cs index fc119d9..5b9f5f7 100644 --- a/WorkBench/Node/View/DllControlControl.xaml.cs +++ b/WorkBench/Node/View/DllControlControl.xaml.cs @@ -53,18 +53,18 @@ namespace Serein.WorkBench.Node.View /// 向动作面板添加类型的文本块 /// /// 要添加的类型 - public void AddAction(MethodDetails md) + public void AddAction(MethodDetailsInfo mdInfo) { - AddTypeToListBox(md, ActionsListBox); + AddTypeToListBox(mdInfo, ActionsListBox); } /// /// 向触发器面板添加类型的文本块 /// /// 要添加的类型 - public void AddFlipflop(MethodDetails md) + public void AddFlipflop(MethodDetailsInfo mdInfo) { - AddTypeToListBox(md, FlipflopsListBox); + AddTypeToListBox(mdInfo, FlipflopsListBox); } /// @@ -72,14 +72,14 @@ namespace Serein.WorkBench.Node.View /// /// 要添加的类型 /// 要添加到的面板 - private void AddTypeToListBox(MethodDetails md, ListBox listBox) + private void AddTypeToListBox(MethodDetailsInfo mdInfo, ListBox listBox) { // 创建一个新的 TextBlock 并设置其属性 TextBlock typeText = new TextBlock { - Text = $"{md.MethodTips}", + Text = $"{mdInfo.MethodTips}", Margin = new Thickness(10, 2, 0, 0), - Tag = md + Tag = mdInfo }; // 为 TextBlock 添加鼠标左键按下事件处理程序 typeText.MouseLeftButtonDown += TypeText_MouseLeftButtonDown; @@ -125,17 +125,17 @@ namespace Serein.WorkBench.Node.View // 获取触发事件的 TextBlock - if (sender is TextBlock typeText && typeText.Tag is MethodDetails md) + if (sender is TextBlock typeText && typeText.Tag is MethodDetailsInfo mdInfo) { MoveNodeData moveNodeData = new MoveNodeData { - NodeControlType = md.MethodDynamicType switch + NodeControlType = mdInfo.NodeType switch { NodeType.Action => NodeControlType.Action, NodeType.Flipflop => NodeControlType.Flipflop, _ => NodeControlType.None, }, - MethodDetails = md, + MethodDetailsInfo = mdInfo, }; if(moveNodeData.NodeControlType == NodeControlType.None) { diff --git a/WorkBench/Themes/IOCObjectViewControl.xaml.cs b/WorkBench/Themes/IOCObjectViewControl.xaml.cs index fe85eb6..e30d91a 100644 --- a/WorkBench/Themes/IOCObjectViewControl.xaml.cs +++ b/WorkBench/Themes/IOCObjectViewControl.xaml.cs @@ -91,7 +91,11 @@ namespace Serein.WorkBench.Themes public void ClearObjItem() { - DependenciesListBox.Items.Clear(); + DependenciesListBox.Dispatcher.Invoke(() => + { + DependenciesListBox.Items.Clear(); + }); + } private static void SortLisbox(ListBox listBox) diff --git a/WorkBench/Themes/WindowDialogInput.xaml b/WorkBench/Themes/WindowDialogInput.xaml new file mode 100644 index 0000000..1991e91 --- /dev/null +++ b/WorkBench/Themes/WindowDialogInput.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WorkBench/Themes/WindowDialogInput.xaml.cs b/WorkBench/Themes/WindowDialogInput.xaml.cs new file mode 100644 index 0000000..f4096fd --- /dev/null +++ b/WorkBench/Themes/WindowDialogInput.xaml.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace Serein.WorkBench.Themes +{ + /// + /// WindowDialogInput.xaml 的交互逻辑 + /// + public partial class WindowEnvRemoteLoginView : Window + { + private Action ConnectRemoteFlowEnv; + + /// + /// 弹窗输入 + /// + /// + public WindowEnvRemoteLoginView(Action connectRemoteFlowEnv) + { + WindowStartupLocation = WindowStartupLocation.CenterScreen; + InitializeComponent(); + ConnectRemoteFlowEnv = connectRemoteFlowEnv; + } + + private void ButtonTestConnect_Client(object sender, RoutedEventArgs e) + { + var addres = this.TextBlockAddres.Text; + _ = int.TryParse(this.TextBlockPort.Text, out var port); + _ = Task.Run(() => { + bool success = false; + try + { + TcpClient tcpClient = new TcpClient(); + var result = tcpClient.BeginConnect(addres, port, null, null); + success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(3)); + } + catch (Exception ex) + { + + } + if (!success) + { + Console.WriteLine($"无法连接远程:{addres}:{port}"); + } + }); + + } + + private void ButtonTestLoginEnv_Client(object sender, RoutedEventArgs e) + { + var addres = this.TextBlockAddres.Text; + _ = int.TryParse(this.TextBlockPort.Text, out var port); + var token = this.TextBlockToken.Text; + ConnectRemoteFlowEnv?.Invoke(addres, port, token); + } + } +}