diff --git a/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs b/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs index d715ae4..90071a3 100644 --- a/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs +++ b/Extend.FlowRemoteManagement/SereinFlowRemoteControl.cs @@ -1,6 +1,5 @@  using Serein.Library; -using Serein.Library.Entity; using Serein.Library.Api; using Serein.Library.Attributes; using Serein.Library.Enums; @@ -12,6 +11,7 @@ using Serein.Library.NodeFlow.Tool; using Serein.Library.Utils; using Serein.FlowRemoteManagement.Model; using System.Reflection; +using Serein.Library.FlowNode; namespace SereinFlowRemoteManagement { @@ -43,7 +43,7 @@ namespace SereinFlowRemoteManagement } [NodeAction(NodeType.Loading)] - public void Loading(IDynamicContext context) + public async Task Loading(IDynamicContext context) { environment.IOC.Run(async (socketServer) => { @@ -59,7 +59,7 @@ namespace SereinFlowRemoteManagement await Console.Out.WriteLineAsync("启动远程管理模块"); await socketServer.StartAsync($"http://*:{ServerPort}/"); }); - SereinProjectData projectData = environment.GetProjectInfo(); + SereinProjectData projectData = await environment.GetProjectInfoAsync(); } #endregion @@ -77,7 +77,7 @@ namespace SereinFlowRemoteManagement try { - var envInfo = this.environment.GetEnvInfo(); + var envInfo = this.environment.GetEnvInfoAsync(); return envInfo; } catch (Exception ex) @@ -100,7 +100,7 @@ namespace SereinFlowRemoteManagement if (this.environment.TryGetMethodDetailsInfo(methodName,out var mdInfo)) { - this.environment.CreateNode(connectionType, new Position(x, y), mdInfo); ; + this.environment.CreateNode(connectionType, new PositionOfUI(x, y), mdInfo); // } @@ -126,7 +126,7 @@ namespace SereinFlowRemoteManagement if (nodeInfo.Op) { - environment.ConnectNode(nodeInfo.FromNodeGuid, nodeInfo.ToNodeGuid, connectionType); + environment.ConnectNodeAsync(nodeInfo.FromNodeGuid, nodeInfo.ToNodeGuid, connectionType); } else { @@ -161,7 +161,7 @@ namespace SereinFlowRemoteManagement public async Task GetProjectInfo() { await Task.Delay(0); - return environment.GetProjectInfo(); + return await environment.GetProjectInfoAsync(); } diff --git a/FlowStartTool/Program.cs b/FlowStartTool/Program.cs index 608df8f..3db3c81 100644 --- a/FlowStartTool/Program.cs +++ b/FlowStartTool/Program.cs @@ -1,7 +1,7 @@ using Newtonsoft.Json; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.NodeFlow; +using Serein.NodeFlow.Env; using System.Diagnostics; using System.Reflection; @@ -67,7 +67,8 @@ namespace Serein.FlowStartTool public static async Task StartFlow(SereinProjectData flowProjectData, string fileDataPath) { Env = new FlowEnvironment(); - Env.LoadProject(flowProjectData, fileDataPath); // 加载项目 + + Env.LoadProject(new FlowEnvInfo { Project = flowProjectData }, fileDataPath); // 加载项目 await Env.StartAsync(); IsRuning = false; } diff --git a/FlowStartTool/Serein.FlowStartTool.csproj b/FlowStartTool/Serein.FlowStartTool.csproj index 5124857..71a1916 100644 --- a/FlowStartTool/Serein.FlowStartTool.csproj +++ b/FlowStartTool/Serein.FlowStartTool.csproj @@ -18,7 +18,7 @@ False - + diff --git a/Library.Core/NodeFlow/DynamicContext.cs b/Library.Core/NodeFlow/DynamicContext.cs index e555791..96aa34c 100644 --- a/Library.Core/NodeFlow/DynamicContext.cs +++ b/Library.Core/NodeFlow/DynamicContext.cs @@ -1,5 +1,4 @@ using Serein.Library.Api; -using Serein.Library.Enums; using Serein.Library.Utils; using System.Collections.Concurrent; diff --git a/Library.Core/NodeFlow/FlipflopContext.cs b/Library.Core/NodeFlow/FlipflopContext.cs index 3ef05f4..47baffe 100644 --- a/Library.Core/NodeFlow/FlipflopContext.cs +++ b/Library.Core/NodeFlow/FlipflopContext.cs @@ -1,6 +1,4 @@ using Serein.Library.Api; -using Serein.Library.Enums; -using Serein.Library.NodeFlow.Tool; namespace Serein.Library.Core.NodeFlow { diff --git a/Library.Core/Serein.Library.Core.csproj b/Library.Core/Serein.Library.Core.csproj index b484e3a..8c80e4b 100644 --- a/Library.Core/Serein.Library.Core.csproj +++ b/Library.Core/Serein.Library.Core.csproj @@ -1,7 +1,7 @@  - 1.0.13 + 1.0.14 net8.0 enable enable diff --git a/Library.Framework/NodeFlow/DynamicContext.cs b/Library.Framework/NodeFlow/DynamicContext.cs index 2e3d0f4..33a7020 100644 --- a/Library.Framework/NodeFlow/DynamicContext.cs +++ b/Library.Framework/NodeFlow/DynamicContext.cs @@ -1,10 +1,5 @@ using Serein.Library.Api; -using Serein.Library.Enums; -using Serein.Library.Utils; -using System; using System.Collections.Concurrent; -using System.Security.Claims; -using System.Threading.Tasks; namespace Serein.Library.Framework.NodeFlow { @@ -45,6 +40,8 @@ namespace Serein.Library.Framework.NodeFlow /// public object GetFlowData(string nodeGuid) { + + if (dictNodeFlowData.TryGetValue(nodeGuid, out var data)) { return data; diff --git a/Library.Framework/NodeFlow/FlipflopContext.cs b/Library.Framework/NodeFlow/FlipflopContext.cs index 61136c3..b1be1a5 100644 --- a/Library.Framework/NodeFlow/FlipflopContext.cs +++ b/Library.Framework/NodeFlow/FlipflopContext.cs @@ -1,6 +1,4 @@ using Serein.Library.Api; -using Serein.Library.Enums; -using Serein.Library.NodeFlow.Tool; using System; using System.Threading.Tasks; diff --git a/Library.Framework/Properties/AssemblyInfo.cs b/Library.Framework/Properties/AssemblyInfo.cs index 2d53e45..67817af 100644 --- a/Library.Framework/Properties/AssemblyInfo.cs +++ b/Library.Framework/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ using System.Runtime.InteropServices; //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 //通过使用 "*",如下所示: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.1.0")] -[assembly: AssemblyFileVersion("1.0.1.0")] +[assembly: AssemblyVersion("1.0.1.4")] +[assembly: AssemblyFileVersion("1.0.1.4")] [assembly: NeutralResourcesLanguage("")] diff --git a/Library.Framework/Serein.Library.Framework.csproj b/Library.Framework/Serein.Library.Framework.csproj index c1aaea5..5fc1e64 100644 --- a/Library.Framework/Serein.Library.Framework.csproj +++ b/Library.Framework/Serein.Library.Framework.csproj @@ -18,7 +18,7 @@ true full false - ..\.Output\Debug\net8.0\ + ..\.Output\Debug\librarynet462\ DEBUG;TRACE prompt 4 @@ -26,10 +26,11 @@ pdbonly true - bin\Release\ + ..\.Output\Release\librarynet462\ TRACE prompt 4 + ..\.Output\Release\librarynet462\Serein.Library.Framework.xml @@ -58,7 +59,7 @@ - {5E19D0F2-913A-4D1C-A6F8-1E1227BAA0E3} + {5e19d0f2-913a-4d1c-a6f8-1e1227baa0e3} Serein.Library diff --git a/Library/Api/IDynamicContext.cs b/Library/Api/IDynamicContext.cs index baf0d74..f9ab472 100644 --- a/Library/Api/IDynamicContext.cs +++ b/Library/Api/IDynamicContext.cs @@ -1,4 +1,4 @@ -using Serein.Library.Enums; +using Serein.Library; using Serein.Library.Utils; using System; using System.Threading.Tasks; diff --git a/Library/Api/IFlipflopContext.cs b/Library/Api/IFlipflopContext.cs index 276105d..288d2f5 100644 --- a/Library/Api/IFlipflopContext.cs +++ b/Library/Api/IFlipflopContext.cs @@ -1,5 +1,5 @@ -using Serein.Library.Enums; -using Serein.Library.NodeFlow.Tool; +using Serein.Library; + namespace Serein.Library.Api { diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 0342d0a..b1b777b 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -1,10 +1,7 @@ -using Serein.Library.Entity; -using Serein.Library.Enums; -using Serein.Library.Utils; +using Serein.Library.Utils; using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Reflection; +using System.Threading; using System.Threading.Tasks; using static Serein.Library.Utils.ChannelFlowInterrupt; @@ -86,6 +83,13 @@ namespace Serein.Library.Api /// public delegate void NodeMovedHandler(NodeMovedEventArgs eventArgs); + /// + /// 远程环境内容输出 + /// + /// 输出的文本信息 + public delegate void EnvOutHandler(string value); + + #endregion #region 环境事件签名 @@ -198,7 +202,7 @@ namespace Serein.Library.Api public class NodeCreateEventArgs : FlowEventArgs { - public NodeCreateEventArgs(object nodeModel, Position position) + public NodeCreateEventArgs(object nodeModel, PositionOfUI position) { this.NodeModel = nodeModel; this.Position = position; @@ -214,7 +218,7 @@ namespace Serein.Library.Api /// 节点Model对象,目前需要手动转换对应的类型 /// public object NodeModel { get; private set; } - public Position Position { get; private set; } + public PositionOfUI Position { get; private set; } public bool IsAddInRegion { get; private set; } public string RegeionGuid { get; private set; } } @@ -452,20 +456,32 @@ namespace Serein.Library.Api bool IsGlobalInterrupt { get; } /// - /// DLL中NodeAction特性的方法描述的所有原始副本 + /// 表示是否正在控制远程 + /// Local control remote env /// - // ConcurrentDictionary MethodDetailss { get; } + bool IsLcR { get; } + /// + /// 表示是否受到远程控制 + /// Remote control local env + /// + bool IsRcL { get; } /// /// 流程运行状态 /// RunState FlowState { get; set; } + /// /// 全局触发器运行状态 /// RunState FlipFlopState { get; set; } + /// + /// 拓展功能时,如需订阅事件,则需要使用该属性 + /// + IFlowEnvironment CurrentEnv { get; } + #endregion #region 事件 @@ -535,28 +551,27 @@ namespace Serein.Library.Api /// event NodeMovedHandler OnNodeMoved; + /// + /// 运行环境输出 + /// + event EnvOutHandler OnEnvOut; #endregion + #region 接口 /// - /// 获取方法描述信息 + /// 设置输出 /// - /// 方法描述 - /// 方法信息 - /// - bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo); + // + // + void SetConsoleOut(); // Action output, Action clearMsg /// - /// 获取指定方法的Emit委托 + /// 使用JSON处理库输出对象信息 /// - /// - /// - /// - bool TryGetDelegateDetails(string methodName, out DelegateDetails del); + /// + void WriteLineObjToJson(object obj); - //bool TryGetNodeData(string methodName, out NodeData node); - - #region 环境基础接口 /// /// 启动远程服务 /// @@ -571,20 +586,25 @@ namespace Serein.Library.Api /// 保存当前项目 /// /// - SereinProjectData GetProjectInfo(); + Task GetProjectInfoAsync(); /// /// 加载项目文件 /// - /// + /// 包含项目信息的远程环境 /// - void LoadProject(SereinProjectData projectFile, string filePath); + void LoadProject(FlowEnvInfo flowEnvInfo, string filePath); /// - /// 加载远程项目 + /// 加载远程环境 /// - /// 远程项目地址 - /// 远程项目端口 + /// 远程环境地址 + /// 远程环境端口 /// 密码 - void LoadRemoteProject(string addres,int port, string token); + Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres,int port, string token); + + /// + /// 退出远程环境 + /// + void ExitRemoteEnv(); /// /// 从文件中加载Dll @@ -638,15 +658,15 @@ namespace Serein.Library.Api /// 起始节点Guid /// 目标节点Guid /// 连接类型 - void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType); + Task ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType); /// /// 创建节点/区域/基础控件 /// - /// 节点/区域/基础控件 + /// 节点/区域/基础控件类型 /// 节点在画布上的位置( /// 节点绑定的方法说明( - void CreateNode(NodeControlType nodeBase, Position position, MethodDetailsInfo methodDetailsInfo = null); + Task CreateNodeAsync(NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null); /// /// 移除两个节点之间的连接关系 @@ -681,7 +701,7 @@ namespace Serein.Library.Api /// 被中断的节点Guid /// 新的中断级别 /// - bool SetNodeInterrupt(string nodeGuid, InterruptClass interruptClass); + Task SetNodeInterruptAsync(string nodeGuid, InterruptClass interruptClass); /// /// 添加作用于某个对象的中断表达式 @@ -689,7 +709,7 @@ namespace Serein.Library.Api /// /// /// - bool AddInterruptExpression(string key, string expression); + Task AddInterruptExpressionAsync(string key, string expression); /// /// 监视指定对象 @@ -701,10 +721,9 @@ namespace Serein.Library.Api /// /// 检查一个对象是否处于监听状态,如果是,则传出与该对象相关的表达式(用于中断),如果不是,则返回false。 /// - /// 判断的对象 - /// 表达式 + /// 判断的对象 /// - bool CheckObjMonitorState(string key, out List exps); + Task<(bool, string[])> CheckObjMonitorStateAsync(string key); /// @@ -715,13 +734,39 @@ namespace Serein.Library.Api /// Task GetOrCreateGlobalInterruptAsync(); + /// + /// (用于远程)通知节点属性变更 + /// + /// 节点Guid + /// 属性路径 + /// 属性值 + /// + Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value); + + + /// + /// 获取方法描述信息 + /// + /// 方法描述 + /// 方法信息 + /// + bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo); + + /// + /// 获取指定方法的Emit委托 + /// + /// + /// + /// + bool TryGetDelegateDetails(string methodName, out DelegateDetails del); + #region 远程相关 /// /// (适用于远程连接后获取环境的运行状态)获取当前环境的信息 /// /// - object GetEnvInfo(); + Task GetEnvInfoAsync(); #endregion #endregion diff --git a/Library/Entity/DelegateDetails.cs b/Library/Entity/DelegateDetails.cs index 62deda4..edd7d9e 100644 --- a/Library/Entity/DelegateDetails.cs +++ b/Library/Entity/DelegateDetails.cs @@ -6,7 +6,7 @@ using System.Text; using System.Threading.Tasks; using static Serein.Library.Utils.EmitHelper; -namespace Serein.Library.Entity +namespace Serein.Library { /// /// Emit创建的委托描述,用于WebApi、WebSocket、NodeFlow动态调用方法的场景。 @@ -22,7 +22,7 @@ namespace Serein.Library.Entity public DelegateDetails(EmitMethodType EmitMethodType, Delegate EmitDelegate) { this._emitMethodType = EmitMethodType; - this._emitDelegate = EmitDelegate; + this._emitDelegate = EmitDelegate; } /// /// 更新委托方法 @@ -37,42 +37,42 @@ 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; } + ///// + ///// 普通方法:Func<object,object[],object> + ///// 异步方法:Func<object,object[],Task> + ///// 异步有返回值方法:Func<object,object[],Task<object>> + ///// + //public Delegate EmitDelegate { get => _emitDelegate; } + ///// + ///// 表示Emit构造的委托类型 + ///// + //public EmitMethodType EmitMethodType { get => _emitMethodType; } /// /// 使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。 /// - /// 实例 - /// 入参 + /// 拥有符合委托签名的方法信息的实例 + /// 如果方法没有入参,也需要传入一个空数组 /// void方法自动返回null public async Task InvokeAsync(object instance, object[] args) { if(args is null) { - args = new object[0]; + args = Array.Empty(); } object result = null; try { - if (EmitMethodType == EmitMethodType.HasResultTask && EmitDelegate is Func> hasResultTask) + if (_emitMethodType == EmitMethodType.HasResultTask && _emitDelegate is Func> hasResultTask) { result = await hasResultTask(instance, args); } - else if (EmitMethodType == EmitMethodType.Task && EmitDelegate is Func task) + else if (_emitMethodType == EmitMethodType.Task && _emitDelegate is Func task) { await task.Invoke(instance, args); result = null; } - else if (EmitMethodType == EmitMethodType.Func && EmitDelegate is Func func) + else if (_emitMethodType == EmitMethodType.Func && _emitDelegate is Func func) { result = func.Invoke(instance, args); } diff --git a/Library/Entity/MethodDetails.cs b/Library/Entity/MethodDetails.cs deleted file mode 100644 index 41c19ba..0000000 --- a/Library/Entity/MethodDetails.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Serein.Library.Api; -using Serein.Library.Enums; -using System; -using System.Linq; - -namespace Serein.Library.Entity -{ - /// - /// 方法描述信息 - /// - public class MethodDetailsInfo - { - /// - /// 属于哪个DLL文件 - /// - public string LibraryName { get; set; } - - /// - /// 方法名称 - /// - public string MethodName { get; set; } - - /// - /// 节点类型 - /// - public NodeType NodeType { get; set; } - - /// - /// 方法说明 - /// - public string MethodTips { get; set; } - - /// - /// 参数内容 - /// - - public ParameterDetailsInfo[] ParameterDetailsInfos { get; set; } - - /// - /// 出参类型 - /// - public string ReturnTypeFullName { get; set; } - } - - - - /// - /// 每个节点有独自的MethodDetails实例 - /// - public class MethodDetails - { - /// - /// 转为信息 - /// - /// - public MethodDetailsInfo ToInfo() - { - return new MethodDetailsInfo - { - MethodName = MethodName, - MethodTips = MethodTips, - NodeType = MethodDynamicType, - ParameterDetailsInfos = this.ParameterDetailss.Select(p => p.ToInfo()).ToArray(), - ReturnTypeFullName = ReturnType.FullName, - - }; - } - - - /// - /// 从DLL拖动出来时拷贝新的实例 - /// - /// - public MethodDetails Clone() - { - return new MethodDetails - { - ActingInstance = ActingInstance, - ActingInstanceType = ActingInstanceType, - MethodDynamicType = MethodDynamicType, - MethodTips = MethodTips, - ReturnType = ReturnType, - MethodName = MethodName, - MethodLockName = MethodLockName, - IsProtectionParameter = IsProtectionParameter, - ParameterDetailss = ParameterDetailss?.Select(it => it.Clone()).ToArray(), - }; - } - - /// - /// 是否保护参数(仅视觉效果参数,不影响运行实现) - /// - public bool IsProtectionParameter { get; set; } = false; - - /// - /// 作用实例的类型(多个相同的节点将拥有相同的类型) - /// - public Type ActingInstanceType { get; set; } - - /// - /// 作用实例(多个相同的节点将会共享同一个实例) - /// - public object ActingInstance { get; set; } - - /// - /// 方法名称 - /// - public string MethodName { get; set; } - - /// - /// 节点类型 - /// - public NodeType MethodDynamicType { get; set; } - - /// - /// 锁名称(暂未实现) - /// - public string MethodLockName { get; set; } - - - /// - /// 方法说明 - /// - public string MethodTips { get; set; } - - - /// - /// 参数描述 - /// - - public ParameterDetails[] ParameterDetailss { get; set; } - - /// - /// 出参类型 - /// - - public Type ReturnType { get; set; } - - - } - - -} diff --git a/Library/Entity/MoveNodeData.cs b/Library/Entity/MoveNodeData.cs new file mode 100644 index 0000000..bfac713 --- /dev/null +++ b/Library/Entity/MoveNodeData.cs @@ -0,0 +1,18 @@ +using Serein.Library; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library +{ + /// + /// 拖拽创建节点使用的数据 + /// + public class MoveNodeData + { + public NodeControlType NodeControlType { get; set; } + public MethodDetailsInfo MethodDetailsInfo { get; set; } + } +} diff --git a/Library/Entity/NodeDebugSetting.cs b/Library/Entity/NodeDebugSetting.cs index fde7464..25ad76b 100644 --- a/Library/Entity/NodeDebugSetting.cs +++ b/Library/Entity/NodeDebugSetting.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; using static Serein.Library.Utils.ChannelFlowInterrupt; -namespace Serein.Library.Entity +namespace Serein.Library { /// /// 节点调试设置,用于中断节点的运行 @@ -12,7 +12,7 @@ namespace Serein.Library.Entity public class NodeDebugSetting { /// - /// 是否使能(调试中断功能) + /// 是否使能 /// public bool IsEnable { get; set; } = true; @@ -48,10 +48,6 @@ namespace Serein.Library.Entity /// Branch, /// - /// 分组中断,中断进入指定节点分组的分支。(暂未实现相关) - /// - // Group, - /// /// 全局中断,中断全局所有节点的运行。(暂未实现相关) /// Global, diff --git a/Library/Entity/NodeLibrary.cs b/Library/Entity/NodeLibrary.cs index f0d53be..32ca8fc 100644 --- a/Library/Entity/NodeLibrary.cs +++ b/Library/Entity/NodeLibrary.cs @@ -3,19 +3,31 @@ using System.Collections.Generic; using System.Reflection; using System.Text; -namespace Serein.Library.Entity +namespace Serein.Library { /// /// 节点DLL依赖类,如果一个项目中引入了多个DLL,需要放置在同一个文件夹中 /// public class NodeLibrary { + /// + /// 文件名 + /// + public string FileName { get; set; } + /// /// 路径 /// - public string Path { get; set; } + public string FilePath { get; set; } - public string Name{ get; set; } + /// + /// 依赖类的名称 + /// + public string FullName{ get; set; } + + /// + /// 对应的程序集 + /// public Assembly Assembly { get; set; } } diff --git a/Library/Entity/ParameterDetails.cs b/Library/Entity/ParameterDetails.cs deleted file mode 100644 index 1ac8289..0000000 --- a/Library/Entity/ParameterDetails.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Serein.Library.Entity -{ - - /// - /// 方法入参描述 - /// - public class ParameterDetailsInfo - { - /// - /// 参数索引 - /// - public int Index { get; set; } - - /// - /// 方法需要的类型 - /// - public string DataTypeFullName { get; set; } - - /// - /// 方法入参参数名称 - /// - public string Name { get; set; } - } - - /// - /// 节点入参参数详情 - /// - public class ParameterDetails - { - /// - /// 转为描述 - /// - /// - public ParameterDetailsInfo ToInfo() - { - return new ParameterDetailsInfo - { - Index = Index, - DataTypeFullName = DataType.FullName, - Name = Name - }; - } - - /// - /// 拷贝新的对象。 - /// - /// - public ParameterDetails Clone() => new ParameterDetails() - { - Index = Index, - IsExplicitData = IsExplicitData, - ExplicitType = ExplicitType, - ExplicitTypeName = ExplicitTypeName, - Convertor = Convertor, - DataType = DataType, - Name = Name, - DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue, - Items = Items.Select(it => it).ToArray(), - }; - - /// - /// 参数索引 - /// - public int Index { get; set; } - /// - /// 是否为显式参数(固定值/表达式) - /// - public bool IsExplicitData { get; set; } - /// - /// 转换器 IEnumConvertor<,> - /// - public Func Convertor { get; set; } - /// - /// 显式类型 - /// - public Type ExplicitType { get; set; } - - /// - /// 目前存在三种状态:Select/Bool/Value - /// Select : 枚举值 - /// Bool : 布尔类型 - /// Value : 除以上类型之外的任意参数 - /// - public string ExplicitTypeName { get; set; } - - /// - /// 方法需要的类型 - /// - public Type DataType { get; set; } - - /// - /// 方法入参参数名称 - /// - public string Name { get; set; } - - /// - /// 入参值(在UI上输入的文本内容) - /// - - public string DataValue { get; set; } - - /// - /// 如果是引用类型,拷贝时不会发生改变。 - /// - public object[] Items { get; set; } - - - } - - -} diff --git a/Library/Enums/ConnectionType.cs b/Library/Enums/ConnectionType.cs index e29df2a..fd26784 100644 --- a/Library/Enums/ConnectionType.cs +++ b/Library/Enums/ConnectionType.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace Serein.Library.Enums +namespace Serein.Library { /// diff --git a/Library/Enums/FlipflopStateType.cs b/Library/Enums/FlipflopStateType.cs index 6565eec..2f1f269 100644 --- a/Library/Enums/FlipflopStateType.cs +++ b/Library/Enums/FlipflopStateType.cs @@ -4,10 +4,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.Library.Enums +namespace Serein.Library { /// - /// 触发器说明 + /// 触发器状态 /// public enum FlipflopStateType { diff --git a/Library/Enums/NodeType.cs b/Library/Enums/NodeType.cs index f4dbe22..6c29aba 100644 --- a/Library/Enums/NodeType.cs +++ b/Library/Enums/NodeType.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.Library.Enums +namespace Serein.Library { /// /// 用来判断该方法属于什么节点,使运行环境决定方法的运行逻辑 diff --git a/Library/Enums/RunState.cs b/Library/Enums/RunState.cs index 7d649a2..613b817 100644 --- a/Library/Enums/RunState.cs +++ b/Library/Enums/RunState.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.Library.Enums +namespace Serein.Library { /// /// 流程运行状态 diff --git a/Library/Ex/FlipflopException.cs b/Library/Ex/FlipflopException.cs index 65d0add..48cdbc3 100644 --- a/Library/Ex/FlipflopException.cs +++ b/Library/Ex/FlipflopException.cs @@ -1,7 +1,7 @@ using System; using System.CodeDom; -namespace Serein.Library.Ex +namespace Serein.Library { /// /// 触发器异常 diff --git a/Library/FlowNode/Attribute.cs b/Library/FlowNode/Attribute.cs new file mode 100644 index 0000000..435b632 --- /dev/null +++ b/Library/FlowNode/Attribute.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library +{ + [AttributeUsage(AttributeTargets.Class, Inherited = true)] + internal sealed class AutoPropertyAttribute : Attribute + { + public string ValuePath = string.Empty; + } + + /// + /// 自动生成环境的属性 + /// + [AttributeUsage(AttributeTargets.Field, Inherited = true)] + internal sealed class PropertyInfoAttribute : Attribute + { + public bool IsNotification = false; + public bool IsPrint = false; + } + +} diff --git a/Library/FlowNode/MethodDetails.cs b/Library/FlowNode/MethodDetails.cs new file mode 100644 index 0000000..d6ce6ea --- /dev/null +++ b/Library/FlowNode/MethodDetails.cs @@ -0,0 +1,210 @@ +using Serein.Library.Api; +using Serein.Library.Utils; +using System; +using System.Linq; + +namespace Serein.Library +{ + + /// + /// 每个节点有独自的MethodDetails实例 + /// + [AutoProperty(ValuePath = nameof(MethodDetails))] + public partial class MethodDetails + { + private readonly IFlowEnvironment env; + private readonly NodeModelBase nodeModel; + /// + /// 是否保护参数(目前仅视觉效果参数,不影响运行实现,后续将设置作用在运行逻辑中) + /// + [PropertyInfo(IsNotification = true)] + private bool _isProtectionParameter; + + /// + /// 作用实例的类型(多个相同的节点将拥有相同的类型) + /// + [PropertyInfo] + private Type _actingInstanceType; + + /// + /// 作用实例(多个相同的节点将会共享同一个实例) + /// + [PropertyInfo] + private object _actingInstance; + + /// + /// 方法名称 + /// + [PropertyInfo] + private string _methodName; + + /// + /// 节点类型 + /// + [PropertyInfo] + private NodeType _methodDynamicType; + + /// + /// 锁名称(暂未实现) + /// + [PropertyInfo] + private string _methodLockName; + + + /// + /// 方法说明 + /// + [PropertyInfo] + private string _methodTips; + + + /// + /// 参数描述 + /// + [PropertyInfo] + private ParameterDetails[] _parameterDetailss; + + /// + /// 出参类型 + /// + [PropertyInfo] + private Type _returnType; + } + + + public partial class MethodDetails + { + /// + /// 不包含方法信息的基础节点(后续可能要改为DLL引入基础节点) + /// + public MethodDetails() + { + + } + /// + /// 生成元数据 + /// + /// 节点运行的环境 + /// 标识属于哪个节点 + public MethodDetails(IFlowEnvironment env, NodeModelBase nodeModel) + { + this.nodeModel = nodeModel; + } + + + /// + /// 从方法信息中读取 + /// + /// + public MethodDetails(MethodDetailsInfo Info) + { + if (!Info.NodeType.TryConvertEnum(out var nodeType)) + { + throw new ArgumentException("无效的节点类型"); + } + MethodName = Info.MethodName; + MethodTips = Info.MethodTips; + MethodDynamicType = nodeType; + ReturnType = Type.GetType(Info.ReturnTypeFullName); + ParameterDetailss = Info.ParameterDetailsInfos.Select(pinfo => new ParameterDetails(pinfo)).ToArray(); + } + + /// + /// 转为信息 + /// + /// + public MethodDetailsInfo ToInfo() + { + return new MethodDetailsInfo + { + MethodName = MethodName, + MethodTips = MethodTips, + NodeType = MethodDynamicType.ToString(), + ParameterDetailsInfos = ParameterDetailss.Select(p => p.ToInfo()).ToArray(), + ReturnTypeFullName = ReturnType.FullName, + }; + } + + /// + /// 从DLL拖动出来时拷贝属于节点的实例 + /// + /// + public MethodDetails CloneOfNode(IFlowEnvironment env, NodeModelBase nodeModel) + { + var md = new MethodDetails(env, nodeModel) // 创建新节点时拷贝实例 + { + ActingInstance = this.ActingInstance, + ActingInstanceType = this.ActingInstanceType, + MethodDynamicType = this.MethodDynamicType, + MethodTips = this.MethodTips, + ReturnType = this.ReturnType, + MethodName = this.MethodName, + MethodLockName = this.MethodLockName, + IsProtectionParameter = this.IsProtectionParameter, + }; + md.ParameterDetailss = this.ParameterDetailss.Select(p => p.CloneOfClone(env, nodeModel)).ToArray(); // 拷贝属于节点方法的新入参描述 + return md; + } + + + + + + ///// + ///// 每个节点有独自的MethodDetails实例 + ///// + //public partial class TmpMethodDetails + //{ + // /// + // /// 是否保护参数(目前仅视觉效果参数,不影响运行实现,后续将设置作用在运行逻辑中) + // /// + // public bool IsProtectionParameter { get; set; } = false; + + // /// + // /// 作用实例的类型(多个相同的节点将拥有相同的类型) + // /// + // public Type ActingInstanceType { get; set; } + + // /// + // /// 作用实例(多个相同的节点将会共享同一个实例) + // /// + // public object ActingInstance { get; set; } + + // /// + // /// 方法名称 + // /// + // public string MethodName { get; set; } + + // /// + // /// 节点类型 + // /// + // public NodeType MethodDynamicType { get; set; } + + // /// + // /// 锁名称(暂未实现) + // /// + // public string MethodLockName { get; set; } + + + // /// + // /// 方法说明 + // /// + // public string MethodTips { get; set; } + + + // /// + // /// 参数描述 + // /// + + // public ParameterDetails[] ParameterDetailss { get; set; } + + // /// + // /// 出参类型 + // /// + + // public Type ReturnType { get; set; } + //} + + } + +} diff --git a/Library/FlowNode/MethodDetailsInfo.cs b/Library/FlowNode/MethodDetailsInfo.cs new file mode 100644 index 0000000..c7a228a --- /dev/null +++ b/Library/FlowNode/MethodDetailsInfo.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library +{ + /// + /// 方法描述信息 + /// + public class MethodDetailsInfo + { + /// + /// 属于哪个DLL文件 + /// + public string LibraryName { get; set; } + + /// + /// 方法名称 + /// + public string MethodName { get; set; } + + /// + /// 节点类型 + /// + public string NodeType { get; set; } + + /// + /// 方法说明 + /// + public string MethodTips { get; set; } + + /// + /// 参数内容 + /// + + public ParameterDetailsInfo[] ParameterDetailsInfos { get; set; } + + /// + /// 出参类型 + /// + public string ReturnTypeFullName { get; set; } + } + +} diff --git a/Library/Entity/NodeModelBaseData.cs b/Library/FlowNode/NodeModelBaseData.cs similarity index 90% rename from Library/Entity/NodeModelBaseData.cs rename to Library/FlowNode/NodeModelBaseData.cs index 18e8acb..f221326 100644 --- a/Library/Entity/NodeModelBaseData.cs +++ b/Library/FlowNode/NodeModelBaseData.cs @@ -1,11 +1,10 @@ using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.Library.Enums; +using Serein.Library; using System; using System.Collections.Generic; using System.Threading; -namespace Serein.NodeFlow.Base +namespace Serein.Library { /// /// 节点基类(数据):条件控件,动作控件,条件区域,动作区域 @@ -13,7 +12,7 @@ namespace Serein.NodeFlow.Base public abstract partial class NodeModelBase : IDynamicFlowNode { - public NodeModelBase() + public NodeModelBase(IFlowEnvironment environment) { PreviousNodes = new Dictionary>(); SuccessorNodes = new Dictionary>(); @@ -23,26 +22,36 @@ namespace Serein.NodeFlow.Base SuccessorNodes[ctType] = new List(); } DebugSetting = new NodeDebugSetting(); + this.Env = environment; } + /// + /// 节点保留对环境的引用,因为需要在属性更改时通知 + /// + public IFlowEnvironment Env { get; } /// - /// 调试功能 + /// 在画布中的位置 + /// + public PositionOfUI Position { get; set; } + + /// + /// 附加的调试功能 /// public NodeDebugSetting DebugSetting { get; set; } /// - /// 节点对应的控件类型 + /// 描述节点对应的控件类型 /// public NodeControlType ControlType { get; set; } /// - /// 方法描述,对应DLL的方法 + /// 方法描述但不包含Method与委托,需要通过MethodName从环境中获取委托进行调用。 /// public MethodDetails MethodDetails { get; set; } /// - /// 节点guid + /// 标识节点对象全局唯一 /// public string Guid { get; set; } diff --git a/Library/Entity/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs similarity index 92% rename from Library/Entity/NodeModelBaseFunc.cs rename to Library/FlowNode/NodeModelBaseFunc.cs index 6a90a82..1895138 100644 --- a/Library/Entity/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -1,12 +1,9 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Serein.Library; 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 Serein.Library.Utils.SereinExpression; using System; using System.Collections; using System.Collections.Generic; @@ -20,7 +17,7 @@ using System.Threading.Tasks; using System.Xml.Linq; using static Serein.Library.Utils.ChannelFlowInterrupt; -namespace Serein.NodeFlow.Base +namespace Serein.Library { /// @@ -46,7 +43,16 @@ namespace Serein.NodeFlow.Base #region 导出/导入项目文件节点信息 + /// + /// 获取节点参数 + /// + /// public abstract Parameterdata[] GetParameterdatas(); + + /// + /// 导出为节点信息 + /// + /// public virtual NodeInfo ToInfo() { // if (MethodDetails == null) return null; @@ -70,10 +76,15 @@ namespace Serein.NodeFlow.Base UpstreamNodes = upstreamNodes.ToArray(), ParameterData = parameterData.ToArray(), ErrorNodes = errorNodes.ToArray(), - + Position = Position, }; } + /// + /// 从节点信息加载节点 + /// + /// + /// public virtual NodeModelBase LoadInfo(NodeInfo nodeInfo) { this.Guid = nodeInfo.Guid; @@ -86,7 +97,7 @@ namespace Serein.NodeFlow.Base this.MethodDetails.ParameterDetailss[i].DataValue = pd.Value; } } - + this.Position = nodeInfo.Position;// 加载位置信息 return this; } @@ -103,7 +114,7 @@ namespace Serein.NodeFlow.Base public static bool IsBradk(IDynamicContext context, CancellationTokenSource flowCts) { // 上下文不再执行 - if(context.RunState == RunState.Completion) + if (context.RunState == RunState.Completion) { return true; } @@ -117,7 +128,7 @@ namespace Serein.NodeFlow.Base if (flowCts != null) { if (flowCts.IsCancellationRequested) - return true; + return true; } return false; } @@ -132,17 +143,21 @@ namespace Serein.NodeFlow.Base public async Task StartFlowAsync(IDynamicContext context) { Stack stack = new Stack(); + HashSet processedNodes = new HashSet(); // 用于记录已处理上游节点的节点 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(); +#if DEBUG + await Task.Delay(1); +#endif #region 执行相关 + // 从栈中弹出一个节点作为当前节点进行处理 + var currentNode = stack.Pop(); + // 筛选出上游分支 var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream].ToArray(); for (int index = 0; index < upstreamNodes.Length; index++) @@ -166,8 +181,8 @@ namespace Serein.NodeFlow.Base } } } - if (IsBradk(context, flowCts)) break; // 退出执行 // 上游分支执行完成,才执行当前节点 + if (IsBradk(context, flowCts)) break; // 退出执行 object newFlowData = await currentNode.ExecutingAsync(context); if (IsBradk(context, flowCts)) break; // 退出执行 @@ -224,7 +239,7 @@ namespace Serein.NodeFlow.Base { throw new Exception($"节点{this.Guid}不存在对应委托"); } - if(md.ActingInstance is null) + if (md.ActingInstance is null) { md.ActingInstance = context.Env.IOC.Get(md.ActingInstanceType); } @@ -310,7 +325,7 @@ namespace Serein.NodeFlow.Base } //if (Enum.TryParse(ed.ExplicitType, ed.DataValue, out var resultEnum)) //{ - + //} } @@ -332,7 +347,7 @@ namespace Serein.NodeFlow.Base parameters[i] = value; continue; } - + } } @@ -346,19 +361,19 @@ namespace Serein.NodeFlow.Base else { var valueStr = inputParameter?.ToString(); - if(ed.DataType == typeof(string)) + if (ed.DataType == typeof(string)) { parameters[i] = valueStr; } - else if(ed.DataType == typeof(IDynamicContext)) + else if (ed.DataType == typeof(IDynamicContext)) { parameters[i] = context; } - else if(ed.DataType == typeof(MethodDetails)) + else if (ed.DataType == typeof(MethodDetails)) { parameters[i] = md; } - else if(ed.DataType == typeof(NodeModelBase)) + else if (ed.DataType == typeof(NodeModelBase)) { parameters[i] = nodeModel; } @@ -402,7 +417,7 @@ namespace Serein.NodeFlow.Base { } else - { + { await MonitorObjExpInterrupt(context, nodeModel, newData, 0); // 首先监视对象 await MonitorObjExpInterrupt(context, nodeModel, newData, 1); // 然后监视节点 nodeModel.FlowData = newData; // 替换数据 @@ -428,17 +443,17 @@ namespace Serein.NodeFlow.Base { return; } - - if (context.Env.CheckObjMonitorState(key, out List exps)) // 如果新的数据处于查看状态,通知UI进行更新?交给运行环境判断? + (var isMonitor, var exps) = await context.Env.CheckObjMonitorStateAsync(key); + if (isMonitor) // 如果新的数据处于查看状态,通知UI进行更新?交给运行环境判断? { context.Env.MonitorObjectNotification(nodeModel.Guid, data, sourceType); // 对象处于监视状态,通知UI更新数据显示 - if (exps.Count > 0) + if (exps.Length > 0) { // 表达式环境下判断是否需要执行中断 bool isExpInterrupt = false; string exp = ""; // 判断执行监视表达式,直到为 true 时退出 - for (int i = 0; i < exps.Count && !isExpInterrupt; i++) + for (int i = 0; i < exps.Length && !isExpInterrupt; i++) { exp = exps[i]; if (string.IsNullOrEmpty(exp)) continue; @@ -448,7 +463,7 @@ namespace Serein.NodeFlow.Base if (isExpInterrupt) // 触发中断 { InterruptClass interruptClass = InterruptClass.Branch; // 分支中断 - if (context.Env.SetNodeInterrupt(nodeModel.Guid, interruptClass)) + if (await context.Env.SetNodeInterruptAsync(nodeModel.Guid, interruptClass)) { context.Env.TriggerInterrupt(nodeModel.Guid, exp, InterruptTriggerEventArgs.InterruptTriggerType.Exp); var cancelType = await nodeModel.DebugSetting.GetInterruptTask(); diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs new file mode 100644 index 0000000..09116b0 --- /dev/null +++ b/Library/FlowNode/ParameterDetails.cs @@ -0,0 +1,220 @@ +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text; + +namespace Serein.Library +{ + + /// + /// 节点入参参数详情 + /// + [AutoProperty(ValuePath = nameof(ParameterDetails))] + public partial class ParameterDetails + { + private readonly IFlowEnvironment env; + private readonly NodeModelBase nodeModel; + /// + /// 参数索引 + /// + [PropertyInfo] + private int _index; + + /// + /// 是否为显式参数(固定值/表达式) + /// + [PropertyInfo(IsNotification = true)] + private bool _isExplicitData ; + + /// + /// 转换器 IEnumConvertor<,> + /// + [PropertyInfo] + private Func _convertor ; + + /// + /// 显式类型 + /// + [PropertyInfo] + private Type _explicitType ; + + /// + /// 目前存在三种状态:Select/Bool/Value + /// Select : 枚举值 + /// Bool : 布尔类型 + /// Value : 除以上类型之外的任意参数 + /// + [PropertyInfo] + private string _explicitTypeName ; + + /// + /// 方法需要的类型 + /// + [PropertyInfo] + private Type _dataType ; + + /// + /// 方法入参参数名称 + /// + [PropertyInfo] + private string _name ; + + /// + /// 自定义的方法入参数据 + /// + [PropertyInfo(IsNotification = true)] // IsPrint = true + private string _dataValue; + + /// + /// 如果是引用类型,拷贝时不会发生改变。 + /// + [PropertyInfo(IsNotification = true)] + private string[] _items ; + } + + + public partial class ParameterDetails + { + /// + /// 为节点实例化新的入参描述 + /// + public ParameterDetails(IFlowEnvironment env, NodeModelBase nodeModel) + { + this.env = env; + this.nodeModel = nodeModel; + } + /// + /// 通过参数信息加载实体,用于加载项目文件、远程连接的场景 + /// + /// 参数信息 + public ParameterDetails(ParameterDetailsInfo info) + { + //this.env = env; + Index = info.Index; + Name = info.Name; + DataType = Type.GetType(info.DataTypeFullName); + ExplicitType = Type.GetType(info.ExplicitTypeFullName); + ExplicitTypeName = info.ExplicitTypeName; + Items = info.Items; + } + + /// + /// 用于创建元数据 + /// + /// 方法参数信息 + public ParameterDetails() + { + + } + + /// + /// 转为描述 + /// + /// + public ParameterDetailsInfo ToInfo() + { + return new ParameterDetailsInfo + { + Index = Index, + DataTypeFullName = DataType.FullName, + Name = Name, + ExplicitTypeFullName = ExplicitType.FullName, + ExplicitTypeName = ExplicitTypeName, + Items = Items, + }; + } + + /// + /// 为某个节点拷贝方法描述的入参描述 + /// + /// 运行环境 + /// 运行环境 + /// + public ParameterDetails CloneOfClone(IFlowEnvironment env, NodeModelBase nodeModel) + { + var pd = new ParameterDetails(env, nodeModel) + { + Index = this.Index, + IsExplicitData = this.IsExplicitData, + ExplicitType = this.ExplicitType, + ExplicitTypeName = this.ExplicitTypeName, + Convertor = this.Convertor, + DataType = this.DataType, + Name = this.Name, + DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue, + Items = this.Items?.Select(it => it).ToArray(), + }; + return pd; + } + } + + + + + ///// + ///// 节点入参参数详情 + ///// + + //public partial class TempParameterDetails + //{ + // private readonly MethodDetails methodDetails; + + // /// + // /// 参数索引 + // /// + // public int Index { get; set; } + // /// + // /// 是否为显式参数(固定值/表达式) + // /// + // public bool IsExplicitData { get; set; } + // /// + // /// 转换器 IEnumConvertor<,> + // /// + // public Func Convertor { get; set; } + // /// + // /// 显式类型 + // /// + // public Type ExplicitType { get; set; } + + // /// + // /// 目前存在三种状态:Select/Bool/Value + // /// Select : 枚举值 + // /// Bool : 布尔类型 + // /// Value : 除以上类型之外的任意参数 + // /// + // public string ExplicitTypeName { get; set; } + + // /// + // /// 方法需要的类型 + // /// + // public Type DataType { get; set; } + + // /// + // /// 方法入参参数名称 + // /// + // public string Name { get; set; } + + + // private string _dataValue; + // /// + // /// 入参值(在UI上输入的文本内容) + // /// + + // public string DataValue + // { + // get => _dataValue; set + // { + // _dataValue = value; + // Console.WriteLine($"更改了{value}"); + // } + // } + + // /// + // /// 如果是引用类型,拷贝时不会发生改变。 + // /// + // public string[] Items { get; set; } + //} + +} diff --git a/Library/FlowNode/ParameterDetailsInfo.cs b/Library/FlowNode/ParameterDetailsInfo.cs new file mode 100644 index 0000000..3f8380f --- /dev/null +++ b/Library/FlowNode/ParameterDetailsInfo.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library +{ + + /// + /// 方法入参描述 + /// + public class ParameterDetailsInfo + { + /// + /// 参数索引 + /// + public int Index { get; set; } + + /// + /// 方法需要的类型 + /// + public string DataTypeFullName { get; set; } + + /// + /// 方法入参参数名称 + /// + public string Name { get; set; } + /// + /// 显式类型 + /// + public string ExplicitTypeFullName { get; set; } + + /// + /// 目前存在三种状态:Select/Bool/Value + /// Select : 枚举值 + /// Bool : 布尔类型 + /// Value : 除以上类型之外的任意参数 + /// + public string ExplicitTypeName { get; set; } + + /// + /// 参数选择器 + /// + public string[] Items { get; set; } + } +} diff --git a/Library/Entity/SereinProjectData.cs b/Library/FlowNode/SereinProjectData.cs similarity index 78% rename from Library/Entity/SereinProjectData.cs rename to Library/FlowNode/SereinProjectData.cs index 7c21793..473103b 100644 --- a/Library/Entity/SereinProjectData.cs +++ b/Library/FlowNode/SereinProjectData.cs @@ -7,9 +7,37 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; -namespace Serein.Library.Entity +namespace Serein.Library { + /// + /// 环境信息(远程控制用) + /// + public class FlowEnvInfo + { + /// + /// 环境方法信息 + /// + public LibraryMds[] LibraryMds { get; set; } + /// + /// 项目信息 + /// + public SereinProjectData Project { get; set; } + + // IOC节点对象信息 + } + + public class LibraryMds + { + public string LibraryName { get; set; } + + public MethodDetailsInfo[] Mds { get; set; } + + } + + + + /// /// 项目保存文件 /// @@ -70,7 +98,7 @@ namespace Serein.Library.Entity /// /// 高度 /// - public double Lenght { get; set; } + public double Height { get; set; } /// /// 预览位置X @@ -99,19 +127,22 @@ namespace Serein.Library.Entity public class Library { /// - /// DLL名称 + /// 文件名称 /// - public string Name { get; set; } + public string FileName { get; set; } /// - /// 路径 + /// 文件路径 /// + public string FilePath { get; set; } - public string Path { get; set; } - - + /// + /// 程序集名称 + /// + public string AssemblyName { get; set; } } + /// /// 节点 /// @@ -175,7 +206,7 @@ namespace Serein.Library.Entity /// 于画布中的位置 /// - public Position Position { get; set; } + public PositionOfUI Position { get; set; } /// /// 是否选中(暂时无效) @@ -207,14 +238,20 @@ namespace Serein.Library.Entity /// /// 节点于画布中的位置 /// - public class Position - { + public class PositionOfUI + { /// + /// 构造一个坐标 + /// + public PositionOfUI() + { + + } /// /// 构造一个坐标 /// - public Position(double x, double y) + public PositionOfUI(double x, double y) { - this.X = x; this.Y = y; + X = x; Y = y; } public double X { get; set; } = 0; diff --git a/Library/Network/Http/Router.cs b/Library/Network/Http/Router.cs index 06983a5..d99d594 100644 --- a/Library/Network/Http/Router.cs +++ b/Library/Network/Http/Router.cs @@ -1,22 +1,17 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Entity; using Serein.Library.Utils; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.ComponentModel.Design; using System.IO; using System.Linq; using System.Net; using System.Reflection; -using System.Security.AccessControl; using System.Text; using System.Threading.Tasks; -using System.Web; using Enum = System.Enum; using Type = System.Type; diff --git a/Library/Network/Http/WebApiServer.cs b/Library/Network/Http/WebApiServer.cs index d001ab2..12bfc28 100644 --- a/Library/Network/Http/WebApiServer.cs +++ b/Library/Network/Http/WebApiServer.cs @@ -1,7 +1,4 @@ -using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Utils; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; diff --git a/Library/Network/WebSocket/Attribute.cs b/Library/Network/WebSocket/Attribute.cs index 730e38b..940ee2d 100644 --- a/Library/Network/WebSocket/Attribute.cs +++ b/Library/Network/WebSocket/Attribute.cs @@ -31,9 +31,10 @@ namespace Serein.Library.Network.WebSocketCommunication /// - /// 作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理。 - /// 如果没有显式设置ThemeValue,将默认使用方法名称作为ThemeValue。 - /// 如果没有显式设置IsReturnValue标记为false,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去 + /// 作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理,同时要求Data中必须存在对应入参。 + /// 如果没有显式设置 ThemeValue,将默认使用方法名称作为ThemeValue。 + /// 如果没有显式设置 IsReturnValue 标记为 false ,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去 + /// 如果没有显式设置 ArgNotNull 标记为 false ,当外部尝试调用时,若 Json Data 不包含响应的数据,将会被忽略此次调用 /// 如果返回类型为Task或Task<TResult>,将会自动等待异步完成并获取结果(无法处理Task<Task<TResult>>的情况)。 /// 如果返回了值类型,会自动装箱为引用对象。 /// 如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。 @@ -61,9 +62,24 @@ namespace Serein.Library.Network.WebSocketCommunication /// 会进行异步等待,当Task结束后,自动获取TResult进行发送(请避免Task<Task<TResult>>诸如此类的Task泛型嵌套) /// public bool IsReturnValue = true; + /// + /// 表示该方法所有入参不能为空(所需的参数在请求Json的Data不存在) + /// 若有一个参数无法从data获取,则不会进行调用该方法 + /// 如果设置该属性为 false ,但某些入参不能为空,而不希望在代码中进行检查,请为入参添加[NotNull]/[Needful]特性 + /// + public bool ArgNotNull = true; } - - internal class SocketHandleModel + + /// + /// 使用消息DataKey整体数据 + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class UseMsgDataAttribute : Attribute + { + } + + + internal class SocketHandleModule { public string ThemeValue { get; set; } = string.Empty; public bool IsReturnValue { get; set; } = true; diff --git a/Library/Network/WebSocket/Handle/Attribute.cs b/Library/Network/WebSocket/Handle/Attribute.cs new file mode 100644 index 0000000..d5f2287 --- /dev/null +++ b/Library/Network/WebSocket/Handle/Attribute.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Network.WebSocketCommunication.Handle +{ + /// + /// 表示参数可以为空(Net462不能使用NutNull的情况) + /// + public sealed class NeedfulAttribute : Attribute + { + } +} diff --git a/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs b/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs index 4206f56..10e888a 100644 --- a/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs +++ b/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs @@ -4,7 +4,9 @@ using Serein.Library.Utils; using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -20,53 +22,164 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// public class JsonMsgHandleConfig { - private readonly Delegate EmitDelegate; - private readonly EmitHelper.EmitMethodType EmitMethodType; + public Guid HandleGuid { get; } - private Action> OnExceptionTracking; - internal JsonMsgHandleConfig(SocketHandleModel model,ISocketHandleModule instance, MethodInfo methodInfo, Action> onExceptionTracking) + internal JsonMsgHandleConfig(SocketHandleModule model, + ISocketHandleModule instance, + MethodInfo methodInfo, + Action> onExceptionTracking, + bool ArgNotNull) { EmitMethodType = EmitHelper.CreateDynamicMethod(methodInfo,out EmitDelegate); - this.Model = model; + this.Module = model; Instance = instance; var parameterInfos = methodInfo.GetParameters(); - ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray(); - ParameterName = parameterInfos.Select(t => t.Name).ToArray(); + this.ParameterType = parameterInfos.Select(t => t.ParameterType).ToArray(); + this.ParameterName = parameterInfos.Select(t => t.Name).ToArray(); this.HandleGuid = instance.HandleGuid; this.OnExceptionTracking = onExceptionTracking; + this.ArgNotNull = ArgNotNull; + + this.useMsgData = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); +#if NET5_0_OR_GREATER + this.IsCheckArgNotNull = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); +#endif + + + + if(IsCheckArgNotNull is null) + { + IsCheckArgNotNull = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); + } + else + { + // 兼容两种非空特性的写法 + var argNotNull = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray(); + for (int i = 0; i < IsCheckArgNotNull.Length; i++) + { + if (!IsCheckArgNotNull[i] && argNotNull[i]) + { + IsCheckArgNotNull[i] = true; + } + } + } } - private SocketHandleModel Model; - private ISocketHandleModule Instance; - public Guid HandleGuid { get; } - private string[] ParameterName; - private Type[] ParameterType; + /// + /// 参数不能为空 + /// + private bool ArgNotNull; + + /// + /// Emit委托 + /// + private readonly Delegate EmitDelegate; + /// + /// Emit委托类型 + /// + private readonly EmitHelper.EmitMethodType EmitMethodType; + /// + /// 未捕获的异常跟踪 + /// + private readonly Action> OnExceptionTracking; + /// + /// 所在的模块 + /// + private readonly SocketHandleModule Module; + /// + /// 所使用的实例 + /// + private readonly ISocketHandleModule Instance; + /// + /// 参数名称 + /// + private readonly string[] ParameterName; + /// + /// 参数类型 + /// + private readonly Type[] ParameterType; + /// + /// 是否使用整体data参数 + /// + private readonly bool[] useMsgData; + /// + /// 是否检查变量为空 + /// + private readonly bool[] IsCheckArgNotNull; - public async void Handle(Func RecoverAsync, JObject jsonObject) + + public async void Handle(Func SendAsync, JObject jsonObject) { object[] args = new object[ParameterType.Length]; + bool isCanInvoke = true;; // 表示是否可以调用方法 for (int i = 0; i < ParameterType.Length; i++) { var type = ParameterType[i]; var argName = ParameterName[i]; - if (type.IsGenericType) + if (useMsgData[i]) { - if (type.IsAssignableFrom(typeof(Func))) + args[i] = jsonObject.ToObject(type); + } + else if (type.IsValueType) + { + var jsonValue = jsonObject.GetValue(argName); + if (!(jsonValue is null)) + { + args[i] = jsonValue.ToObject(type); + } + else + { + if (ArgNotNull && !IsCheckArgNotNull[i]) // 检查不能为空 + { + + args[i] = Activator.CreateInstance(type); // 值类型返回默认值 + } + else + { + isCanInvoke = false; // 参数不能为空,终止调用 + break; + } + } + } + else if (type.IsClass) + { + var jsonValue = jsonObject.GetValue(argName); + if (!(jsonValue is null)) + { + args[i] = jsonValue.ToObject(type); + } + else + { + if (ArgNotNull && !IsCheckArgNotNull[i]) + { + + args[i] = null; // 引用类型返回null + } + else + { + isCanInvoke = false; // 参数不能为空,终止调用 + break; + } + } + } + else if (type.IsGenericType) // 传递SendAsync委托 + { + if (type.IsAssignableFrom(typeof(Func))) { args[i] = new Func(async data => { var jsonText = JsonConvert.SerializeObject(data); - await RecoverAsync.Invoke(jsonText); + await SendAsync.Invoke(jsonText); }); } else if (type.IsAssignableFrom(typeof(Func))) { args[i] = new Func(async data => { - await RecoverAsync.Invoke(data); + await SendAsync.Invoke(data); }); } else if (type.IsAssignableFrom(typeof(Action))) @@ -74,7 +187,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle args[i] = new Action(async data => { var jsonText = JsonConvert.SerializeObject(data); - await RecoverAsync.Invoke(jsonText); + await SendAsync.Invoke(jsonText); }); } else if (type.IsAssignableFrom(typeof(Action))) @@ -82,28 +195,17 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle args[i] = new Action(async data => { var jsonText = JsonConvert.SerializeObject(data); - await RecoverAsync.Invoke(jsonText); + await SendAsync.Invoke(jsonText); }); } - } - else if (type.IsValueType || type.IsClass) - { - var jsonValue = jsonObject.GetValue(argName); - if (jsonValue is null) - { - // 值类型返回默认值,引用类型返回null - args[i] = type.IsValueType ? Activator.CreateInstance(type) : null; - } - else - { - args[i] = jsonValue.ToObject(type); - } - } - - + } } - //Stopwatch sw = new Stopwatch(); - //sw.Start(); + + if (!isCanInvoke) + { + return; + } + object result; try { @@ -133,27 +235,21 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle { var jsonText = JsonConvert.SerializeObject(data); - await RecoverAsync.Invoke(jsonText); + await SendAsync.Invoke(jsonText); })); } //sw.Stop(); //Console.WriteLine($"Emit Invoke:{sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs"); - if(Model.IsReturnValue && result != null && result.GetType().IsClass) + if(Module.IsReturnValue && result != null && result.GetType().IsClass) { - var reusltJsonText = JsonConvert.SerializeObject(result); - _ = RecoverAsync.Invoke($"{reusltJsonText}"); + //var reusltJsonText = JsonConvert.SerializeObject(result); + _ = SendAsync.Invoke(result); + //_ = SendAsync.Invoke($"{reusltJsonText}"); } } - public void Clear() - { - Instance = null; - ParameterName = null; - ParameterType = null; - //expressionDelegate = null; - } } diff --git a/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs b/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs index 6410c8d..cac93a2 100644 --- a/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs +++ b/Library/Network/WebSocket/Handle/WebSocketHandleModule.cs @@ -1,9 +1,12 @@ -using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; +using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Serein.Library.Network.WebSocketCommunication.Handle @@ -37,13 +40,21 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// public ConcurrentDictionary MyHandleConfigs = new ConcurrentDictionary(); - internal void AddHandleConfigs(SocketHandleModel model, ISocketHandleModule instance, MethodInfo methodInfo - , Action> onExceptionTracking) + /// + /// 添加处理配置 + /// + /// 处理模块 + /// 处理配置 + internal bool AddHandleConfigs(SocketHandleModule module,JsonMsgHandleConfig jsonMsgHandleConfig) { - if (!MyHandleConfigs.ContainsKey(model.ThemeValue)) + if (!MyHandleConfigs.ContainsKey(module.ThemeValue)) { - var jsonMsgHandleConfig = new JsonMsgHandleConfig(model,instance, methodInfo, onExceptionTracking); - MyHandleConfigs[model.ThemeValue] = jsonMsgHandleConfig; + MyHandleConfigs[module.ThemeValue] = jsonMsgHandleConfig; + return true; + } + else + { + return false; } } @@ -72,18 +83,14 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle { var temp = MyHandleConfigs.Values; MyHandleConfigs.Clear(); - foreach (var config in temp) - { - config.Clear(); - } } /// /// 处理JSON数据 /// - /// + /// /// - public void HandleSocketMsg(Func RecoverAsync, JObject jsonObject) + public void HandleSocketMsg(Func tSendAsync, JObject jsonObject) { // 获取到消息 string themeKeyName = jsonObject.GetValue(ThemeJsonKey)?.ToString(); @@ -92,11 +99,36 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle // 没有主题 return; } - if (jsonObject[DataJsonKey] is JObject dataJsonObject) + + Func SendAsync = async (data) => { - handldConfig.Handle(RecoverAsync, dataJsonObject); + var sendMsg = new + { + theme = themeKeyName, + token = "", + data = data, + }; + var msg = JsonConvert.SerializeObject(sendMsg); + await tSendAsync(msg); + }; + try + { + + JObject dataObj = jsonObject.GetValue(DataJsonKey).ToObject(); + handldConfig.Handle(SendAsync, dataObj); + } + catch (Exception ex) + { + Console.WriteLine($"error in ws : {ex.Message}{Environment.NewLine}json value:{jsonObject}"); + return; + } + + + + } + } } diff --git a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs b/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs index f74cebd..e7c6713 100644 --- a/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs +++ b/Library/Network/WebSocket/Handle/WebSocketMsgHandleHelper.cs @@ -93,7 +93,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle var themeKey = moduleAttribute.ThemeKey; var dataKey = moduleAttribute.DataKey; - + var handlemodule = AddMyHandleModule(themeKey, dataKey); var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Select(method => @@ -101,7 +101,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle var methodsAttribute = method.GetCustomAttribute(); if (methodsAttribute is null) { - return (null, null); + return (null, null,false); } else { @@ -109,13 +109,14 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle { methodsAttribute.ThemeValue = method.Name; } - var model = new SocketHandleModel + var model = new SocketHandleModule { IsReturnValue = methodsAttribute.IsReturnValue, ThemeValue = methodsAttribute.ThemeValue, }; var value = methodsAttribute.ThemeValue; - return (model, method); + var argNotNull = methodsAttribute.ArgNotNull; + return (model, method, argNotNull); } }) .Where(x => !(x.model is null)).ToList(); @@ -124,14 +125,21 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle return; } + + Console.WriteLine($"add websocket handle model :"); Console.WriteLine($"theme key, data key : {themeKey}, {dataKey}"); - foreach ((var model, var method) in methods) + foreach ((var module, var method,var argNotNull) in methods) { - Console.WriteLine($"theme value : {model.ThemeValue}"); + Console.WriteLine($"theme value : {module.ThemeValue}"); try { - handlemodule.AddHandleConfigs(model, socketControlBase, method, onExceptionTracking); + var jsonMsgHandleConfig = new JsonMsgHandleConfig(module, socketControlBase, method, onExceptionTracking, argNotNull); + var result = handlemodule.AddHandleConfigs(module,jsonMsgHandleConfig); + if (!result) + { + throw new Exception("添加失败,已经添加过相同的配置"); + } } catch (Exception ex) { @@ -145,17 +153,17 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle /// /// 异步处理消息 /// - /// + /// /// /// - public async Task HandleMsgAsync(Func RecoverAsync, string message) + public async Task HandleMsgAsync(Func SendAsync, string message) { JObject json = JObject.Parse(message); await Task.Run(() => { foreach (var module in MyHandleModuleDict.Values) { - module.HandleSocketMsg(RecoverAsync, json); + module.HandleSocketMsg(SendAsync, json); } }); diff --git a/Library/Network/WebSocket/SocketControlBase.cs b/Library/Network/WebSocket/SocketControlBase.cs index 7a409c1..a736bbf 100644 --- a/Library/Network/WebSocket/SocketControlBase.cs +++ b/Library/Network/WebSocket/SocketControlBase.cs @@ -1,10 +1,4 @@ -using Serein.Library.Attributes; -using System; -using System.Collections.Generic; -using System.Net.WebSockets; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; +using System; namespace Serein.Library.Network.WebSocketCommunication { diff --git a/Library/Network/WebSocket/WebSocketClient.cs b/Library/Network/WebSocket/WebSocketClient.cs index bb8d815..b300a55 100644 --- a/Library/Network/WebSocket/WebSocketClient.cs +++ b/Library/Network/WebSocket/WebSocketClient.cs @@ -1,10 +1,5 @@ -using Newtonsoft.Json.Linq; -using Serein.Library.Attributes; -using Serein.Library.Network.WebSocketCommunication.Handle; -using Serein.Library.Web; +using Serein.Library.Network.WebSocketCommunication.Handle; using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; using System.Net.WebSockets; using System.Text; @@ -17,7 +12,6 @@ namespace Serein.Library.Network.WebSocketCommunication /// /// WebSocket客户端 /// - [AutoRegister] public class WebSocketClient { /// @@ -25,7 +19,7 @@ namespace Serein.Library.Network.WebSocketCommunication /// public WebSocketClient() { - + } /// @@ -40,10 +34,20 @@ namespace Serein.Library.Network.WebSocketCommunication /// /// /// - public async Task ConnectAsync(string uri) + public async Task ConnectAsync(string uri) { - await _client.ConnectAsync(new Uri(uri), CancellationToken.None); - await ReceiveAsync(); + try + { + + await _client.ConnectAsync(new Uri(uri), CancellationToken.None); + _ = ReceiveAsync(); + return true; + } + catch (Exception) + { + + return false; + } } /// @@ -65,91 +69,106 @@ namespace Serein.Library.Network.WebSocketCommunication private async Task ReceiveAsync() { var buffer = new byte[1024]; - + var receivedMessage = new StringBuilder(); // 用于拼接长消息 + while (_client.State == WebSocketState.Open) { try { - var result = await _client.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + WebSocketReceiveResult result; + + do + { + result = await _client.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + // 根据接收到的字节数解码为部分字符串,并添加到 StringBuilder 中 + var partialMessage = Encoding.UTF8.GetString(buffer, 0, result.Count); + receivedMessage.Append(partialMessage); + + } while (!result.EndOfMessage); // 判断是否已经收到完整消息 + + // 处理收到的完整消息 if (result.MessageType == WebSocketMessageType.Close) { await _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); } else { - var message = Encoding.UTF8.GetString(buffer, 0, result.Count); - _ = MsgHandleHelper.HandleMsgAsync(SendAsync, message); // 处理消息 - Debug.WriteLine($"Received: {message}"); + var completeMessage = receivedMessage.ToString(); + _ = MsgHandleHelper.HandleMsgAsync(SendAsync, completeMessage); // 处理消息 + Debug.WriteLine($"Received: {completeMessage}"); } + + // 清空 StringBuilder 为下一条消息做准备 + receivedMessage.Clear(); } catch (Exception ex) { - Debug.WriteLine($"Received: {EX.ToString()}"); + Debug.WriteLine($"Received: {ex.ToString()}"); } } } - } + /* #region 消息处理 + private readonly string ThemeField; + private readonly ConcurrentDictionary ThemeConfigs = new ConcurrentDictionary(); - /* #region 消息处理 - private readonly string ThemeField; - private readonly ConcurrentDictionary ThemeConfigs = new ConcurrentDictionary(); - - public async Task HandleSocketMsg(string jsonStr) - { - JObject json; - try + public async Task HandleSocketMsg(string jsonStr) { - json = JObject.Parse(jsonStr); - } - catch (Exception ex) - { - await SendAsync(_client, ex.Message); - return; - } - // 获取到消息 - string themeName = json[ThemeField]?.ToString(); - if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig)) - { - return; - } - - object dataValue; - if (string.IsNullOrEmpty(handldConfig.DataField)) - { - dataValue = json.ToObject(handldConfig.DataType); - } - else - { - dataValue = json[handldConfig.DataField].ToObject(handldConfig.DataType); - } - await handldConfig.Invoke(dataValue, SendAsync); - } - - public void AddConfig(string themeName, Type dataType, MsgHandler msgHandler) - { - if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig)) - { - handldConfig = new HandldConfig + JObject json; + try { - DataField = themeName, - DataType = dataType - }; - ThemeConfigs.TryAdd(themeName, handldConfig); - } - handldConfig.HandldAsync += msgHandler; - } - public void RemoteConfig(string themeName, MsgHandler msgHandler) - { - if (ThemeConfigs.TryGetValue(themeName, out var handldConfig)) - { - handldConfig.HandldAsync -= msgHandler; - if (!handldConfig.HasSubscribers) + json = JObject.Parse(jsonStr); + } + catch (Exception ex) { - ThemeConfigs.TryRemove(themeName, out _); + await SendAsync(_client, ex.Message); + return; + } + // 获取到消息 + string themeName = json[ThemeField]?.ToString(); + if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig)) + { + return; + } + + object dataValue; + if (string.IsNullOrEmpty(handldConfig.DataField)) + { + dataValue = json.ToObject(handldConfig.DataType); + } + else + { + dataValue = json[handldConfig.DataField].ToObject(handldConfig.DataType); + } + await handldConfig.Invoke(dataValue, SendAsync); + } + + public void AddConfig(string themeName, Type dataType, MsgHandler msgHandler) + { + if (!ThemeConfigs.TryGetValue(themeName, out var handldConfig)) + { + handldConfig = new HandldConfig + { + DataField = themeName, + DataType = dataType + }; + ThemeConfigs.TryAdd(themeName, handldConfig); + } + handldConfig.HandldAsync += msgHandler; + } + public void RemoteConfig(string themeName, MsgHandler msgHandler) + { + if (ThemeConfigs.TryGetValue(themeName, out var handldConfig)) + { + handldConfig.HandldAsync -= msgHandler; + if (!handldConfig.HasSubscribers) + { + ThemeConfigs.TryRemove(themeName, out _); + } } } + #endregion*/ } - #endregion*/ } diff --git a/Library/Network/WebSocket/WebSocketServer.cs b/Library/Network/WebSocket/WebSocketServer.cs index 42b92a4..1ee4343 100644 --- a/Library/Network/WebSocket/WebSocketServer.cs +++ b/Library/Network/WebSocket/WebSocketServer.cs @@ -1,14 +1,10 @@ using Newtonsoft.Json.Linq; -using Serein.Library.Attributes; using Serein.Library.Network.WebSocketCommunication.Handle; using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; using System.Net; using System.Net.WebSockets; -using System.Reflection; -using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -35,15 +31,6 @@ namespace Serein.Library.Network.WebSocketCommunication /// public string AddresPort { get; } - /// - /// 是否已经鉴权 - /// - public bool IsAuthorized { get => isAuthorized; } //set => isAuthorized = value; - - /// - /// 是否已经鉴权 - /// - private bool isAuthorized; /// /// 授权字段 @@ -61,34 +48,21 @@ namespace Serein.Library.Network.WebSocketCommunication /// 处理消息授权 /// /// - public async Task HandleAuthorized(string message) + public async Task HandleAuthorized(string message) { - if(!isAuthorized && semaphoreSlim is null) // 需要重新授权 - { - semaphoreSlim = new SemaphoreSlim(1); - } await semaphoreSlim.WaitAsync(1); - if(isAuthorized) // 授权通过,无须再次检查授权 - { - return; - } + bool isAuthorized = false; JObject json = JObject.Parse(message); if(json.TryGetValue(TokenKey,out var token)) { // 交给之前定义的授权方法进行判断 isAuthorized = await InspectionAuthorizedFunc?.Invoke(token); - if (isAuthorized) - { - // 授权通过,释放资源 - semaphoreSlim.Release(); - semaphoreSlim.Dispose(); - semaphoreSlim = null; - } } else { isAuthorized = false; } + return isAuthorized; } } @@ -114,7 +88,7 @@ namespace Serein.Library.Network.WebSocketCommunication { this.AuthorizedClients = new ConcurrentDictionary(); this.InspectionAuthorizedFunc = (tokenObj) => Task.FromResult(true); - this.IsNeedInspectionAuthorized = false; + this.IsCheckToken = false; } /// @@ -127,7 +101,7 @@ namespace Serein.Library.Network.WebSocketCommunication this.TokenKey = tokenKey; this.AuthorizedClients = new ConcurrentDictionary(); this.InspectionAuthorizedFunc = inspectionAuthorizedFunc; - this.IsNeedInspectionAuthorized = true; + this.IsCheckToken = true; } /// @@ -136,7 +110,7 @@ namespace Serein.Library.Network.WebSocketCommunication public ConcurrentDictionary AuthorizedClients; private readonly string TokenKey; private readonly Func> InspectionAuthorizedFunc; - private bool IsNeedInspectionAuthorized = false; + private bool IsCheckToken = false; /// /// 进行监听服务 /// @@ -169,7 +143,7 @@ namespace Serein.Library.Network.WebSocketCommunication if (context.Request.IsWebSocketRequest) { WebSocketAuthorizedHelper authorizedHelper = null; - if (IsNeedInspectionAuthorized) + if (IsCheckToken) { if (AuthorizedClients.TryAdd(clientPoint, new WebSocketAuthorizedHelper(clientPoint, TokenKey, InspectionAuthorizedFunc))) { @@ -200,69 +174,78 @@ namespace Serein.Library.Network.WebSocketCommunication private async Task HandleWebSocketAsync(WebSocket webSocket, WebSocketAuthorizedHelper authorizedHelper) { // 需要授权,却没有成功创建授权类,关闭连接 - if (IsNeedInspectionAuthorized && authorizedHelper is null) + if (IsCheckToken && authorizedHelper is null) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); return; } - Func SendAsync = async (text) => { await WebSocketServer.SendAsync(webSocket, text); }; var buffer = new byte[1024]; + var receivedMessage = new StringBuilder(); // 用于拼接长消息 + while (webSocket.State == WebSocketState.Open) { - var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - if (result.MessageType == WebSocketMessageType.Close) + WebSocketReceiveResult result; + + try { - SendAsync = null; - await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); - if (IsNeedInspectionAuthorized) + do { - AuthorizedClients.TryRemove(authorizedHelper.AddresPort, out var _); - } - } - else - { - var message = Encoding.UTF8.GetString(buffer, 0, result.Count); // 序列为文本 - if(!IsNeedInspectionAuthorized) + result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + + // 将接收到的部分消息解码并拼接 + var partialMessage = Encoding.UTF8.GetString(buffer, 0, result.Count); + receivedMessage.Append(partialMessage); + + } while (!result.EndOfMessage); // 循环直到接收到完整的消息 + + // 完整消息已经接收到,准备处理 + var message = receivedMessage.ToString(); + + if (result.MessageType == WebSocketMessageType.Close) { - // 无须授权 - _ = MsgHandleHelper.HandleMsgAsync(SendAsync, message); // 处理消息 - + SendAsync = null; + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); + if (IsCheckToken) + { + AuthorizedClients.TryRemove(authorizedHelper.AddresPort, out var _); + } } else { - // 需要授权 - if (!authorizedHelper.IsAuthorized) + if (IsCheckToken) { - // 该连接尚未验证授权,尝试检测授权 - _ = SendAsync("正在授权"); - await authorizedHelper.HandleAuthorized(message); - } - - - if (authorizedHelper.IsAuthorized) - { - // 该连接通过了验证 - _ = SendAsync("授权成功"); - _ = MsgHandleHelper.HandleMsgAsync(SendAsync, message); // 处理消息 - } - else - { - _ = SendAsync("授权失败"); + var authorizedResult = await authorizedHelper.HandleAuthorized(message); // 尝试检测授权 + if (!authorizedResult) // 授权失败 + { + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); + if (IsCheckToken) + { + AuthorizedClients.TryRemove(authorizedHelper.AddresPort, out var _); + } + continue; + } } + // 消息处理 + _ = MsgHandleHelper.HandleMsgAsync(SendAsync, message); // 处理消息 } - + // 清空 StringBuilder 为下一条消息做准备 + receivedMessage.Clear(); + } + catch (Exception ex) + { + // 处理异常 + Debug.WriteLine($"Error: {ex.ToString()}"); } } } - /// /// 发送消息 /// diff --git a/Library/NodeAttribute.cs b/Library/NodeAttribute.cs index 0cab7c2..750308a 100644 --- a/Library/NodeAttribute.cs +++ b/Library/NodeAttribute.cs @@ -1,7 +1,6 @@ -using Serein.Library.Enums; -using System; +using System; -namespace Serein.Library.Attributes +namespace Serein.Library { /// /// 表示该属性为自动注入依赖项。 diff --git a/Library/NodeStaticConfig.cs b/Library/NodeStaticConfig.cs index 304ab89..562df2d 100644 --- a/Library/NodeStaticConfig.cs +++ b/Library/NodeStaticConfig.cs @@ -1,12 +1,11 @@ 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 +namespace Serein.Library { public static class NodeStaticConfig { diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index bb11020..7b419ee 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -1,8 +1,9 @@  - 1.0.13 + 1.0.15 net8.0;net462 + D:\Project\C#\DynamicControl\SereinFlow\.Output True SereinFow @@ -12,6 +13,9 @@ MIT True true + + true + .\obj\g @@ -27,6 +31,9 @@ + + + diff --git a/Library/Utils/ChannelFlowInterrupt.cs b/Library/Utils/ChannelFlowInterrupt.cs index d6fe5f0..c87fa40 100644 --- a/Library/Utils/ChannelFlowInterrupt.cs +++ b/Library/Utils/ChannelFlowInterrupt.cs @@ -97,7 +97,7 @@ namespace Serein.Library.Utils /// /// 信号标识符 /// 超时时间 - public CancelType CreateChannelWithTimeoutSync(string signal, TimeSpan timeout) + public async Task CreateChannelWithTimeoutSync(string signal, TimeSpan timeout) { var channel = GetOrCreateChannel(signal); var cts = new CancellationTokenSource(); @@ -119,7 +119,7 @@ namespace Serein.Library.Utils }); // 同步阻塞直到信号触发或超时 - var result = channel.Reader.ReadAsync().AsTask().GetAwaiter().GetResult(); + var result = await channel.Reader.ReadAsync(); return result; } diff --git a/Library/Utils/ChannelFlowTrigger.cs b/Library/Utils/ChannelFlowTrigger.cs new file mode 100644 index 0000000..fe63503 --- /dev/null +++ b/Library/Utils/ChannelFlowTrigger.cs @@ -0,0 +1,103 @@ + + +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; + + +namespace Serein.Library.Utils +{ + + + + public class ChannelFlowTrigger + { + // 使用并发字典管理每个枚举信号对应的 Channel + private readonly ConcurrentDictionary> _channels = new ConcurrentDictionary>(); + + /// + /// 创建信号并指定超时时间,到期后自动触发(异步方法) + /// + /// 枚举信号标识符 + /// 超时时间 + /// 等待任务 + public async Task<(TriggerType, TResult)> WaitDataWithTimeoutAsync(TSignal signal, TimeSpan outTime) + { + var channel = GetOrCreateChannel(signal); + var cts = new CancellationTokenSource(); + + // 异步任务:超时后自动触发信号 + _ = Task.Run(async () => + { + try + { + await Task.Delay(outTime, cts.Token); + await channel.Writer.WriteAsync((TriggerType.Overtime, null)); + } + catch (OperationCanceledException) + { + // 超时任务被取消 + } + }, cts.Token); + + // 等待信号传入(超时或手动触发) + (var type, var result) = await channel.Reader.ReadAsync(); + + return (type, result.ToConvert()); + } + + /// + /// 创建信号,直到触发 + /// + /// 枚举信号标识符 + /// 等待任务 + public async Task WaitData(TSignal signal) + { + var channel = GetOrCreateChannel(signal); + // 等待信号传入(超时或手动触发) + (var type, var result) = await channel.Reader.ReadAsync(); + return result.ToConvert(); + } + + + /// + /// 触发信号 + /// + /// 枚举信号标识符 + /// 是否成功触发 + public bool TriggerSignal(TSignal signal, object value) + { + if (_channels.TryGetValue(signal, out var channel)) + { + // 手动触发信号 + channel.Writer.TryWrite((TriggerType.External,value)); + return true; + } + return false; + } + + /// + /// 取消所有任务 + /// + public void CancelAllTasks() + { + foreach (var channel in _channels.Values) + { + channel.Writer.Complete(); + } + _channels.Clear(); + } + + /// + /// 获取或创建指定信号的 Channel + /// + /// 枚举信号标识符 + /// 对应的 Channel + private Channel<(TriggerType, object)> GetOrCreateChannel(TSignal signal) + { + return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded<(TriggerType, object)>()); + } + } +} diff --git a/Library/Utils/DebounceHelper.cs b/Library/Utils/DebounceHelper.cs new file mode 100644 index 0000000..c48ca75 --- /dev/null +++ b/Library/Utils/DebounceHelper.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Utils +{ + /// + /// 消息防抖 + /// + public static class DebounceHelper + { + private static readonly ConcurrentDictionary _lastExecutionTimes = new ConcurrentDictionary(); + private static readonly object _lockObject = new object(); + + /// + /// 检查是否可以执行操作,根据传入的 key 和 debounceTime 来决定是否允许执行 + /// + /// 操作的唯一标识 + /// 防抖时间,单位为毫秒 + /// 如果可以执行操作,返回 true;否则返回 false + public static bool CanExecute(string key, int debounceTimeInMs) + { + lock (_lockObject) + { + var currentTime = DateTime.Now; + + if (_lastExecutionTimes.TryGetValue(key, out DateTime lastExecutionTime)) + { + var timeSinceLastExecution = (currentTime - lastExecutionTime).TotalMilliseconds; + + if (timeSinceLastExecution < debounceTimeInMs) + { + // 如果距离上次执行时间小于防抖时间,不允许执行 + return false; + } + } + + // 更新上次执行时间 + _lastExecutionTimes[key] = currentTime; + return true; + } + } + } + +} diff --git a/Library/Utils/EnumHelper.cs b/Library/Utils/EnumHelper.cs index ed0a477..43e2e45 100644 --- a/Library/Utils/EnumHelper.cs +++ b/Library/Utils/EnumHelper.cs @@ -1,8 +1,5 @@ -using Serein.Library.Attributes; -using System; -using System.Collections.Generic; +using System; using System.Reflection; -using System.Text; namespace Serein.Library.Utils { diff --git a/Library/Utils/ExpressionHelper.cs b/Library/Utils/ExpressionHelper.cs index b454697..27186b5 100644 --- a/Library/Utils/ExpressionHelper.cs +++ b/Library/Utils/ExpressionHelper.cs @@ -312,9 +312,13 @@ namespace Serein.Library.Utils string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCallerAsync"; return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateAsync(type, methodInfo)); } + /// /// 表达式树构建无参数,有返回值(Task)的方法(触发器) /// + /// + /// + /// private static Delegate CreateMethodCallerDelegateAsync(Type type, MethodInfo methodInfo) { var parameter = Expression.Parameter(typeof(object), "instance"); @@ -336,6 +340,7 @@ namespace Serein.Library.Utils string cacheKey = $"{type.FullName}.{method.Name}.MethodCallerAsync"; return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateAsync(type, method, parameterTypes)); } + /// /// 表达式树构建多个参数,有返回值(Task)的方法(触发器) /// diff --git a/Library/Utils/FlowTrigger.cs b/Library/Utils/FlowTrigger.cs index 384975c..3ae6f54 100644 --- a/Library/Utils/FlowTrigger.cs +++ b/Library/Utils/FlowTrigger.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; -namespace Serein.Library.NodeFlow.Tool +namespace Serein.Library { /// /// 触发类型 diff --git a/Library/Utils/MessageIdGenerator.cs b/Library/Utils/MessageIdGenerator.cs new file mode 100644 index 0000000..0ddc718 --- /dev/null +++ b/Library/Utils/MessageIdGenerator.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Utils +{ + /// + /// 消息ID生成工具 + /// + public class MessageIdGenerator + { + private static readonly object _lock = new object(); + private static int _counter = 0; + + /// + /// 生成一个不重复的标识 + /// + /// + /// + public static string GenerateMessageId(string theme) + { + lock (_lock) + { + // 时间戳 + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + // 机器标识(可以替换成更加独特的标识,如机器的MAC地址等) + string machineId = GetMachineId(); + + // 进程ID + int processId = Process.GetCurrentProcess().Id; + + // 递增计数器,确保在同一毫秒内的多次生成也不重复 + int count = _counter++; + + // 随机数 + byte[] randomBytes = new byte[8]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(randomBytes); + } + string randomPart = BitConverter.ToString(randomBytes).Replace("-", ""); + + // 将所有部分组合起来 + return $"{timestamp}-{machineId}-{processId}-{count}-{randomPart}-{theme}"; + } + } + + private static string GetMachineId() + { + // 这里使用 GUID 模拟机器标识 + // 可以替换为更具体的机器信息 + return Guid.NewGuid().ToString("N"); + } + } + +} diff --git a/Library/Utils/RemoteEnvControl.cs b/Library/Utils/RemoteEnvControl.cs new file mode 100644 index 0000000..643ed78 --- /dev/null +++ b/Library/Utils/RemoteEnvControl.cs @@ -0,0 +1,121 @@ +using Newtonsoft.Json; +using Serein.Library.Network.WebSocketCommunication; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Utils +{ + /// + /// 管理远程环境,具备连接、发送消息、停止的功能 + /// + public class RemoteEnvControl + { + /// + /// 配置远程连接IP端口 + /// + public RemoteEnvControl(string addres, int port, object token) + { + this.Addres = addres; + this.Port = port; + this.Token = token; + } + + /// + /// 远程环境的网络地址 + /// + public string Addres { get; } + + /// + /// 远程环境的对外端口 + /// + public int Port { get; } + + /// + /// 登录远程环境必须携带的token(可以为可序列化的JSON对象) + /// + public object Token { get; } + + + + /// + /// 连接到远程的客户端 + /// + public WebSocketClient EnvClient { get; } = new WebSocketClient(); + + /// + /// 是否连接到了远程环境 + /// + public bool IsConnectdRemoteEnv { get => isConnectdRemoteEnv; } + private bool isConnectdRemoteEnv = false; + + /// + /// 尝试连接到远程环境 + /// + /// + public async Task ConnectAsync() + { + // 第2种,WebSocket连接到远程环境,实时接收远程环境的响应? + Console.WriteLine($"准备连接:{Addres}:{Port},{Token}"); + bool success = false; + try + { + var tcpClient = new TcpClient(); + var result = tcpClient.BeginConnect(Addres, Port, null, null); + success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(3)); + } + finally + { + + } + if (!success) + { + Console.WriteLine($"无法连通远程端口 {Addres}:{Port}"); + return false; + } + else + { + var url = $"ws://{Addres}:{Port}/"; + var result = await EnvClient.ConnectAsync(url); // 尝试连接远程环境 + this.isConnectdRemoteEnv = result; + return result; + } + } + + + + /// + /// 发送消息 + /// + /// + /// + /// + public async Task SendAsync(string theme, object data) + { + var sendMsg = new + { + theme = theme, + token = this.Token, + data = data, + }; + var msg = JsonConvert.SerializeObject(sendMsg); + await EnvClient.SendAsync(msg); + } + + + + + + + + + + + + } + + +} diff --git a/Library/Utils/SereinExpression/Resolver/BoolConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/BoolConditionResolver.cs index f5fe3ed..c35fe02 100644 --- a/Library/Utils/SereinExpression/Resolver/BoolConditionResolver.cs +++ b/Library/Utils/SereinExpression/Resolver/BoolConditionResolver.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +namespace Serein.Library.Utils.SereinExpression.Resolver { public class BoolConditionResolver : SereinConditionResolver { diff --git a/Library/Utils/SereinExpression/Resolver/MemberConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/MemberConditionResolver.cs index 0153a97..8b2cd20 100644 --- a/Library/Utils/SereinExpression/Resolver/MemberConditionResolver.cs +++ b/Library/Utils/SereinExpression/Resolver/MemberConditionResolver.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +namespace Serein.Library.Utils.SereinExpression.Resolver { public class MemberConditionResolver : SereinConditionResolver where T : struct, IComparable { diff --git a/Library/Utils/SereinExpression/Resolver/MemberStringConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/MemberStringConditionResolver.cs index 60e89e9..1c84632 100644 --- a/Library/Utils/SereinExpression/Resolver/MemberStringConditionResolver.cs +++ b/Library/Utils/SereinExpression/Resolver/MemberStringConditionResolver.cs @@ -5,7 +5,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +namespace Serein.Library.Utils.SereinExpression.Resolver { public class MemberStringConditionResolver : SereinConditionResolver { diff --git a/Library/Utils/SereinExpression/Resolver/PassConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/PassConditionResolver.cs index 5a30e7e..7728c27 100644 --- a/Library/Utils/SereinExpression/Resolver/PassConditionResolver.cs +++ b/Library/Utils/SereinExpression/Resolver/PassConditionResolver.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +namespace Serein.Library.Utils.SereinExpression.Resolver { public class PassConditionResolver : SereinConditionResolver { diff --git a/Library/Utils/SereinExpression/Resolver/StringConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/StringConditionResolver.cs index 7e2d178..426ff26 100644 --- a/Library/Utils/SereinExpression/Resolver/StringConditionResolver.cs +++ b/Library/Utils/SereinExpression/Resolver/StringConditionResolver.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +namespace Serein.Library.Utils.SereinExpression.Resolver { public class StringConditionResolver : SereinConditionResolver { diff --git a/Library/Utils/SereinExpression/Resolver/ValueTypeConditionResolver.cs b/Library/Utils/SereinExpression/Resolver/ValueTypeConditionResolver.cs index f1cf7e1..87ae767 100644 --- a/Library/Utils/SereinExpression/Resolver/ValueTypeConditionResolver.cs +++ b/Library/Utils/SereinExpression/Resolver/ValueTypeConditionResolver.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.NodeFlow.Tool.SereinExpression.Resolver +namespace Serein.Library.Utils.SereinExpression.Resolver { public class ValueTypeConditionResolver : SereinConditionResolver where T : struct, IComparable { diff --git a/Library/Utils/SereinExpression/SereinConditionParser.cs b/Library/Utils/SereinExpression/SereinConditionParser.cs index e73b189..71202d6 100644 --- a/Library/Utils/SereinExpression/SereinConditionParser.cs +++ b/Library/Utils/SereinExpression/SereinConditionParser.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json.Linq; using Serein.Library.Utils; -using Serein.NodeFlow.Tool.SereinExpression.Resolver; +using Serein.Library.Utils.SereinExpression.Resolver; using System; using System.Collections.Generic; using System.ComponentModel.Design; @@ -8,7 +8,7 @@ using System.Globalization; using System.Linq; using System.Reflection; -namespace Serein.NodeFlow.Tool.SereinExpression +namespace Serein.Library.Utils.SereinExpression { /// /// 字符串工具类 diff --git a/Library/Utils/SereinExpression/SereinConditionResolver.cs b/Library/Utils/SereinExpression/SereinConditionResolver.cs index e0ac6be..7f8c253 100644 --- a/Library/Utils/SereinExpression/SereinConditionResolver.cs +++ b/Library/Utils/SereinExpression/SereinConditionResolver.cs @@ -1,6 +1,6 @@ using System.Reflection; -namespace Serein.NodeFlow.Tool.SereinExpression +namespace Serein.Library.Utils.SereinExpression { /// /// 条件解析抽象类 diff --git a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs index ada8fbf..1b12021 100644 --- a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs +++ b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Data; using System.Linq; -namespace Serein.NodeFlow.Tool.SereinExpression +namespace Serein.Library.Utils.SereinExpression { /// /// 使用表达式操作/获取 对象的值 diff --git a/Library/Utils/SereinIoc.cs b/Library/Utils/SereinIoc.cs index 6f5323f..1be8414 100644 --- a/Library/Utils/SereinIoc.cs +++ b/Library/Utils/SereinIoc.cs @@ -1,19 +1,14 @@ -using Newtonsoft.Json.Linq; -using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Web; +using Serein.Library.Api; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Xml; -using System.Xml.Schema; namespace Serein.Library.Utils { - + /// /// IOC管理容器 /// @@ -195,56 +190,60 @@ namespace Serein.Library.Utils public Type Type { get; set; } } private const string FlowBaseClassName = "<>$FlowBaseClass!@#"; + + public Dictionary> BuildDependencyTree() { - var dependencyMap = new Dictionary>(); - //var tmpTypeFullName = new HashSet(); - //var tmpTypeFullName2 = new HashSet(); - dependencyMap[FlowBaseClassName] = new List(); + var dependencyMap = new Dictionary>(); + dependencyMap[FlowBaseClassName] = new HashSet(); foreach (var typeMapping in _typeMappings) { - var constructor = GetConstructorWithMostParameters(typeMapping.Value); // 获取参数最多的构造函数 - if (constructor != null) + //var constructor = GetConstructorWithMostParameters(typeMapping.Value); // 获取参数最多的构造函数 + + var constructors = GetConstructor(typeMapping.Value); // 获取参数最多的构造函数 + + foreach (var constructor in constructors) { - var parameters = constructor.GetParameters() - .Select(p => p.ParameterType) - .ToList(); - //if(parameters.Count == 0) - //{ - // if (!dependencyMap.ContainsKey(typeMapping.Value.FullName)) - // { - // dependencyMap[typeMapping.Value.FullName] = new List(); - // } - // dependencyMap[typeMapping.Value.FullName].Add(typeMapping.Key); - //} - - - if(parameters .Count > 0) + if (constructor != null) { - // 从类型的构造函数中提取类型 - foreach (var param in parameters) + var parameters = constructor.GetParameters() + .Select(p => p.ParameterType) + .ToList(); + if (parameters.Count == 0) // 无参的构造函数 { - if (!dependencyMap.ContainsKey(param.FullName)) + var type = typeMapping.Value; + if (!dependencyMap[FlowBaseClassName].Contains(type.FullName)) { - dependencyMap[param.FullName] = new List(); + dependencyMap[FlowBaseClassName].Add(type.FullName); + } + } + else + { + // 从类型的有参构造函数中提取类型 + foreach (var param in parameters) + { + if (!dependencyMap.TryGetValue(param.FullName, out var hashSet)) + { + hashSet = new HashSet(); + hashSet.Add(typeMapping.Key); + dependencyMap.Add(param.FullName, hashSet); + } + else + { + if (!hashSet.Contains(typeMapping.Key)) + { + hashSet.Add(typeMapping.Key); + } + } + } - dependencyMap[param.FullName].Add(typeMapping.Key); - //tmpTypeFullName.Add(param.FullName); - //if (tmpTypeFullName2.Contains(param.FullName)) - //{ - // tmpTypeFullName2.Remove(param.FullName); - //} } } - else - { - var type = typeMapping.Value; - dependencyMap[FlowBaseClassName].Add(type.FullName); - } } + } - - return dependencyMap; + var tmp = dependencyMap.ToDictionary(key => key.Key, value => value.Value.ToList()); + return tmp; } // 获取参数最多的构造函数 private ConstructorInfo GetConstructorWithMostParameters(Type type) @@ -253,6 +252,14 @@ namespace Serein.Library.Utils .OrderByDescending(c => c.GetParameters().Length) .FirstOrDefault(); } + // 获取所有构造函数 + private ConstructorInfo[] GetConstructor(Type type) + { + return type.GetConstructors() + .OrderByDescending(c => c.GetParameters().Length) + .OrderBy(ctor => ctor.GetParameters().Length).ToArray(); + } + // 生成顺序 public List GetCreationOrder(Dictionary> dependencyMap) { @@ -334,27 +341,57 @@ namespace Serein.Library.Utils { instance = Activator.CreateInstance(type, @params); } + + // 字符串、值类型,抽象类型,暂时不支持自动创建 + if (type == typeof(string) || type.IsValueType || type.IsAbstract) + { + return null; + } + else { // 没有显示指定构造函数入参,选择参数最多的构造函数 - var constructor = GetConstructorWithMostParameters(type); - var parameters = constructor.GetParameters(); - var args = new object[parameters.Length]; - for (int i = 0; i < parameters.Length; i++) + //var constructor = GetConstructorWithMostParameters(type); + var constructors = GetConstructor(type); // 获取参数最多的构造函数 + + foreach(var constructor in constructors) { - var argType = parameters[i].ParameterType; - var fullName = parameters[i].ParameterType.FullName; - if (!_dependencies.TryGetValue(fullName, out var argObj)) + var parameters = constructor.GetParameters(); + var args = new object[parameters.Length]; + for (int i = 0; i < parameters.Length; i++) { - if (!_typeMappings.ContainsKey(fullName)) + var argType = parameters[i].ParameterType; + var fullName = parameters[i].ParameterType.FullName; + if (!_dependencies.TryGetValue(fullName, out var argObj)) { - _typeMappings.TryAdd(fullName, argType); + if (!_typeMappings.ContainsKey(fullName)) + { + _typeMappings.TryAdd(fullName, argType); + } + argObj = CreateInstance(fullName); + if (argObj is null) + { + Console.WriteLine("构造参数创建失败"); // + continue; + } } - argObj = CreateInstance(fullName); + args[i] = argObj; + } + try + { + instance = Activator.CreateInstance(type, args); + if(instance != null) + { + break; + } + } + catch (Exception) + { + continue; } - args[i] = argObj; } - instance = Activator.CreateInstance(type, args); + + } InjectDependencies(instance); // 完成创建后注入实例需要的特性依赖项 diff --git a/Library/Utils/UIContextOperation.cs b/Library/Utils/UIContextOperation.cs new file mode 100644 index 0000000..680bbca --- /dev/null +++ b/Library/Utils/UIContextOperation.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Serein.Library.Utils +{ + /// + /// 为类库提供了在UI线程上下文操作的方法 + /// + public class UIContextOperation + { + private readonly SynchronizationContext context; + + /// + /// 传入UI线程上下文 + /// + /// 线程上下文 + public UIContextOperation(SynchronizationContext synchronizationContext) + { + this.context = synchronizationContext; + } + + /// + /// 同步方式进行调用方法 + /// + /// 要执行的UI操作 + public void Invoke(Action uiAction) + { + context?.Post(state => + { + uiAction?.Invoke(); + }, null); + } + + /// + /// 异步方式进行调用 + /// + /// 要执行的UI操作 + /// + public Task InvokeAsync(Action uiAction) + { + var tcs = new TaskCompletionSource(); + + context?.Post(state => + { + try + { + uiAction?.Invoke(); + tcs.SetResult(true); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + }, null); + + return tcs.Task; + } + + } +} diff --git a/Net462DllTest/Enums/FromValue.cs b/Net462DllTest/Enums/FromValue.cs index 9cefffb..c503862 100644 --- a/Net462DllTest/Enums/FromValue.cs +++ b/Net462DllTest/Enums/FromValue.cs @@ -1,10 +1,5 @@ using Net462DllTest.View; -using Serein.Library.Attributes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Serein.Library; namespace Net462DllTest.Signal { diff --git a/Net462DllTest/Enums/PlcVarName.cs b/Net462DllTest/Enums/PlcVarName.cs index 3d4408d..17a9ba7 100644 --- a/Net462DllTest/Enums/PlcVarName.cs +++ b/Net462DllTest/Enums/PlcVarName.cs @@ -1,17 +1,5 @@ -using IoTClient.Clients.PLC; -using IoTClient.Enums; -using Net462DllTest.Trigger; +using IoTClient.Enums; using Net462DllTest.Signal; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Emit; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using static Net462DllTest.Signal.PlcVarInfoAttribute; -using Serein.Library.Attributes; using static Net462DllTest.Signal.PlcVarInfo; namespace Net462DllTest.Enums diff --git a/Net462DllTest/LogicControl/ParkingLogicControl.cs b/Net462DllTest/LogicControl/ParkingLogicControl.cs index c90fd79..457f7b2 100644 --- a/Net462DllTest/LogicControl/ParkingLogicControl.cs +++ b/Net462DllTest/LogicControl/ParkingLogicControl.cs @@ -1,10 +1,7 @@ using Net462DllTest.Trigger; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Enums; -using Serein.Library.Ex; using Serein.Library.Framework.NodeFlow; -using Serein.Library.NodeFlow.Tool; using System; using System.Threading.Tasks; diff --git a/Net462DllTest/LogicControl/PlcLogicControl.cs b/Net462DllTest/LogicControl/PlcLogicControl.cs index bba2645..976c1f1 100644 --- a/Net462DllTest/LogicControl/PlcLogicControl.cs +++ b/Net462DllTest/LogicControl/PlcLogicControl.cs @@ -2,14 +2,9 @@ using Net462DllTest.Enums; using Net462DllTest.Model; using Net462DllTest.Trigger; -using Net462DllTest.Web; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Enums; -using Serein.Library.Ex; using Serein.Library.Framework.NodeFlow; -using Serein.Library.NodeFlow.Tool; -using Serein.Library.Web; using System; using System.Threading.Tasks; diff --git a/Net462DllTest/LogicControl/ViewLogicControl.cs b/Net462DllTest/LogicControl/ViewLogicControl.cs index d3ffd6d..51a7a68 100644 --- a/Net462DllTest/LogicControl/ViewLogicControl.cs +++ b/Net462DllTest/LogicControl/ViewLogicControl.cs @@ -1,17 +1,11 @@  using Net462DllTest.Signal; using Net462DllTest.Trigger; -using Net462DllTest.ViewModel; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Enums; -using Serein.Library.Ex; using Serein.Library.Framework.NodeFlow; -using Serein.Library.NodeFlow.Tool; using Serein.Library.Utils; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; diff --git a/Net462DllTest/Main.cs b/Net462DllTest/Main.cs index 5d3f330..aed5926 100644 --- a/Net462DllTest/Main.cs +++ b/Net462DllTest/Main.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -33,4 +34,12 @@ namespace Net462DllTest.Properties 动态的配置事件触发的原因、过程与结果。 */ + + public class My + { + public void Run() + { + + } + } } diff --git a/Net462DllTest/Model/PlcVarModel.cs b/Net462DllTest/Model/PlcVarModel.cs index 01a28aa..c5a957d 100644 --- a/Net462DllTest/Model/PlcVarModel.cs +++ b/Net462DllTest/Model/PlcVarModel.cs @@ -1,12 +1,6 @@ using Net462DllTest.Enums; -using Net462DllTest.LogicControl; -using Net462DllTest.Trigger; -using Serein.Library.Attributes; +using Serein.Library; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Net462DllTest.Model { diff --git a/Net462DllTest/Net462DllTest.csproj b/Net462DllTest/Net462DllTest.csproj index ab55b01..32c7fbf 100644 --- a/Net462DllTest/Net462DllTest.csproj +++ b/Net462DllTest/Net462DllTest.csproj @@ -61,6 +61,7 @@ + diff --git a/Net462DllTest/Signal/PLCVarSignal.cs b/Net462DllTest/Signal/PLCVarSignal.cs index 03ca5e2..e1770fc 100644 --- a/Net462DllTest/Signal/PLCVarSignal.cs +++ b/Net462DllTest/Signal/PLCVarSignal.cs @@ -1,8 +1,6 @@ using IoTClient.Enums; using Net462DllTest.Enums; -using Serein.Library.Attributes; using System; -using static Net462DllTest.Signal.PlcVarInfoAttribute; using static Net462DllTest.Signal.PlcVarInfo; namespace Net462DllTest.Signal diff --git a/Net462DllTest/Trigger/PrakingDevice.cs b/Net462DllTest/Trigger/PrakingDevice.cs index 6295a3b..99c31f7 100644 --- a/Net462DllTest/Trigger/PrakingDevice.cs +++ b/Net462DllTest/Trigger/PrakingDevice.cs @@ -1,11 +1,5 @@ using Net462DllTest.LogicControl; -using Serein.Library.Attributes; -using Serein.Library.NodeFlow.Tool; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Serein.Library; namespace Net462DllTest.Trigger { diff --git a/Net462DllTest/Trigger/SiemensPlcDevice.cs b/Net462DllTest/Trigger/SiemensPlcDevice.cs index b73220d..d34ca5a 100644 --- a/Net462DllTest/Trigger/SiemensPlcDevice.cs +++ b/Net462DllTest/Trigger/SiemensPlcDevice.cs @@ -3,21 +3,16 @@ using IoTClient.Clients.PLC; using IoTClient.Common.Enums; using IoTClient.Enums; using Net462DllTest.Enums; +using Net462DllTest.Model; using Net462DllTest.Signal; using Net462DllTest.Utils; -using Serein.Library.Attributes; -using Serein.Library.NodeFlow.Tool; +using Serein.Library; using Serein.Library.Utils; using System; -using System.Collections.Generic; -using System.Reflection.Emit; -using System.Reflection; -using Net462DllTest.Model; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; using System.Threading.Tasks; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar; -using System.Linq; -using Serein.Library.Network.WebSocketCommunication; namespace Net462DllTest.Trigger { diff --git a/Net462DllTest/Trigger/ViewManagement.cs b/Net462DllTest/Trigger/ViewManagement.cs index 6c24936..cbd6b79 100644 --- a/Net462DllTest/Trigger/ViewManagement.cs +++ b/Net462DllTest/Trigger/ViewManagement.cs @@ -1,13 +1,14 @@ using Net462DllTest.Signal; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.NodeFlow.Tool; using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; +using System.Threading; using System.Windows.Forms; +using System.Windows.Threading; +using Serein.Library.Utils; namespace Net462DllTest.Trigger { @@ -17,9 +18,10 @@ namespace Net462DllTest.Trigger [AutoRegister] public class ViewManagement : FlowTrigger { - public ViewManagement(IFlowEnvironment environment) + private readonly UIContextOperation uiContextOperation; + public ViewManagement(UIContextOperation uiContextOperation) { - + this.uiContextOperation = uiContextOperation; } public int Id = new Random().Next(1, 10000); private readonly List
forms = new List(); @@ -30,20 +32,53 @@ namespace Net462DllTest.Trigger /// 是否置顶 public void OpenView(Form form, bool isTop) { - form.TopMost = isTop; - form.Show(); + //Application.Current.Dispatcher. forms.Add(form); + + uiContextOperation.Invoke(() => { + form.TopMost = isTop; + form.Show(); + }); + + + + //environment.IOC.Run(uiContext => + //{ + // uiContext?.Post(state => { + + // },null); + //}); + + //var uiContext = SynchronizationContext.Current; + //Task.Run(() => + //{ + // uiContext.Post(_ => + // { + + // }, null); + //}); + } + + + public void CloseView(Type formType) { var remoteForms = forms.Where(f => f.GetType() == formType).ToArray(); - foreach (Form f in remoteForms) + + Dispatcher.CurrentDispatcher.Invoke(() => { - f.Close(); - f.Dispose(); - this.forms.Remove(f); - } + foreach (Form f in remoteForms) + { + f.Close(); + f.Dispose(); + this.forms.Remove(f); + } + }); + } + + } } diff --git a/Net462DllTest/Utils/GSModel.cs b/Net462DllTest/Utils/GSModel.cs index dd0b99d..31a26af 100644 --- a/Net462DllTest/Utils/GSModel.cs +++ b/Net462DllTest/Utils/GSModel.cs @@ -1,11 +1,8 @@ -using System; +using Serein.Library; +using System; using System.Collections.Generic; -using System.Linq; -using System.Reflection.Emit; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Serein.Library.Attributes; +using System.Reflection.Emit; namespace Net462DllTest.Utils { diff --git a/Net462DllTest/View/FromWorkBenchView.cs b/Net462DllTest/View/FromWorkBenchView.cs index 30d6f7b..cb29fd2 100644 --- a/Net462DllTest/View/FromWorkBenchView.cs +++ b/Net462DllTest/View/FromWorkBenchView.cs @@ -2,15 +2,7 @@ using Net462DllTest.Signal; using Net462DllTest.ViewModel; using Serein.Library.Api; -using Serein.Library.Attributes; using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Forms; namespace Net462DllTest diff --git a/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs b/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs index 881c3c6..2c080ea 100644 --- a/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs +++ b/Net462DllTest/ViewModel/FromWorkBenchViewModel.cs @@ -1,21 +1,9 @@ -using IoTClient; -using Net462DllTest.Trigger; +using Net462DllTest.Model; using Net462DllTest.Signal; +using Net462DllTest.Trigger; using Net462DllTest.Utils; -using Serein.Library.Attributes; -using Serein.Library.Utils; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Input; -using Net462DllTest.LogicControl; -using Net462DllTest.Model; using Serein.Library.Network.WebSocketCommunication; -using Newtonsoft.Json.Linq; -using System.Diagnostics; +using System.ComponentModel; namespace Net462DllTest.ViewModel { diff --git a/Net462DllTest/Web/FlowController.cs b/Net462DllTest/Web/FlowController.cs index 3208ab4..253a60f 100644 --- a/Net462DllTest/Web/FlowController.cs +++ b/Net462DllTest/Web/FlowController.cs @@ -2,14 +2,9 @@ using Net462DllTest.Enums; using Net462DllTest.Signal; using Net462DllTest.Trigger; -using Serein.Library.Attributes; using Serein.Library.Utils; using Serein.Library.Web; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Net462DllTest.Web { diff --git a/Net462DllTest/Web/PlcSocketService.cs b/Net462DllTest/Web/PlcSocketService.cs index cfeaeab..d0ef32b 100644 --- a/Net462DllTest/Web/PlcSocketService.cs +++ b/Net462DllTest/Web/PlcSocketService.cs @@ -2,12 +2,9 @@ using Net462DllTest.Enums; using Net462DllTest.Model; using Net462DllTest.Trigger; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Enums; -using Serein.Library.Ex; using Serein.Library.Network.WebSocketCommunication; -using Serein.Library.NodeFlow.Tool; using Serein.Library.Web; using System; using System.Threading.Tasks; diff --git a/NodeFlow/Env/EnvMsgTheme.cs b/NodeFlow/Env/EnvMsgTheme.cs new file mode 100644 index 0000000..ee193ce --- /dev/null +++ b/NodeFlow/Env/EnvMsgTheme.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Env +{ + /// + /// 消息主题 + /// + public static class EnvMsgTheme + { + /// + /// 获取远程环境信息 + /// + public const string GetEnvInfo = nameof(GetEnvInfo); + /// + /// 尝试开始流程 + /// + public const string StartFlow = nameof(StartFlow); + /// + /// 尝试从指定节点开始运行 + /// + public const string StartFlowInSelectNode = nameof(StartFlowInSelectNode); + /// + /// 尝试结束流程运行 + /// + public const string ExitFlow = nameof(ExitFlow); + /// + /// 尝试移动某个节点 + /// + public const string MoveNode = nameof(MoveNode); + /// + /// 尝试设置流程起点 + /// + public const string SetStartNode = nameof(SetStartNode); + /// + /// 尝试连接两个节点 + /// + public const string ConnectNode = nameof(ConnectNode); + /// + /// 尝试创建节点 + /// + public const string CreateNode = nameof(CreateNode); + /// + /// 尝试移除节点之间的连接关系 + /// + public const string RemoveConnect = nameof(RemoveConnect); + /// + /// 尝试移除节点 + /// + public const string RemoveNode = nameof(RemoveNode); + /// + /// 激活一个触发器 + /// + public const string ActivateFlipflopNode = nameof(ActivateFlipflopNode); + /// + /// 终结一个触发器 + /// + public const string TerminateFlipflopNode = nameof(TerminateFlipflopNode); + + /// + /// 属性通知 + /// + public const string ValueNotification = nameof(ValueNotification); + + + + /// + /// 尝试获取项目信息 + /// + public const string GetProjectInfo = nameof(GetProjectInfo); + /// + /// 尝试设置节点中断 + /// + public const string SetNodeInterrupt = nameof(SetNodeInterrupt); + /// + /// 尝试添加中断表达式 + /// + public const string AddInterruptExpression = nameof(AddInterruptExpression); + /// + /// 尝试设置节点/对象监视状态 + /// + public const string SetMonitor = nameof(SetMonitor); + + } +} diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs similarity index 70% rename from NodeFlow/FlowEnvironment.cs rename to NodeFlow/Env/FlowEnvironment.cs index 59717bc..c6502a7 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -1,182 +1,114 @@  +using Newtonsoft.Json; +using Newtonsoft.Json.Bson; using Newtonsoft.Json.Linq; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Entity; -using Serein.Library.Enums; using Serein.Library.Network.WebSocketCommunication; using Serein.Library.Utils; -using Serein.NodeFlow.Base; +using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Net.Sockets; using System.Reflection; using System.Security.Cryptography; +using System.Threading; using System.Xml.Linq; using static Serein.Library.Utils.ChannelFlowInterrupt; using static Serein.NodeFlow.FlowStarter; +using static System.Runtime.InteropServices.JavaScript.JSType; -namespace Serein.NodeFlow +namespace Serein.NodeFlow.Env { - /* - - 脱离wpf平台独立运行。 - 加载文件。 - 创建节点对象,设置节点属性,确定连接关系,设置起点。 - - ↓抽象↓ - - wpf依赖于运行环境,而不是运行环境依赖于wpf。 - - 运行环境实现以下功能: - ①从项目文件加载数据,生成项目文件对象。 - ②运行项目,调试项目,中止项目,终止项目。 - ③自动包装数据类型,在上下文中传递数据。 - - */ - /* - - public List get(){ - } - - libray - { - string dllname, - MethodInfo[] nodeinfos - } - - methodInfo{ - - } + // ******************************************************88 - */ - - /* - void StopRemoteServer()//结束远程管理 - async Task StartAsync()//异步运行 - void ExitFlow()//退出 - Task StartAsyncInSelectNode(string startNodeGuid)//从选定节点开始运行 - void ActivateFlipflopNode(string nodeGuid)//激活全局触发器 - void TerminateFlipflopNode(string nodeGuid)//关闭全局触发器 - object GetEnvInfo() // 获取当前环境信息(远程连接) - void LoadProject(SereinProjectData project, string filePath) //加载项目文件 - void LoadRemoteProject(string addres, int port, string token) //加载远程项目 - SereinProjectData GetProjectInfo() // 序列化当前项目的依赖信息、节点信息 - void LoadDll(string dllPath) //从文件路径中加载DLL - bool RemoteDll(string assemblyFullName) //移除DLL - NodeModelBase CreateNode(NodeControlType nodeControlType, Position position, MethodDetailsInfo? methodDetailsInfo = null) //流程正在运行时创建节点 - void RemoveNode(string nodeGuid) //移除节点 - void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) // 连接节点 - void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) //移除连接关系 - - void MoveNode(string nodeGuid,double x,double y) //移动了某个节点(远程插件使用) - void SetStartNode(string newNodeGuid) //设置起点控件 - bool SetNodeInterrupt(string nodeGuid, InterruptClass interruptClass) //中断指定节点,并指定中断等级。 - bool AddInterruptExpression(string key, string expression)//添加表达式中断 - void SetMonitorObjState(string key, bool isMonitor) // 设置对象的监视状态 - - */ /// /// 运行环境 /// - [AutoSocketModule(ThemeKey = "theme", DataKey = "data")] - public class FlowEnvironment : IFlowEnvironment, ISereinIOC, ISocketHandleModule + public class FlowEnvironment : IFlowEnvironment, ISereinIOC { + /// + /// 节点的命名空间 + /// + public const string SpaceName = $"{nameof(Serein)}.{nameof(NodeFlow)}.{nameof(Model)}"; + public const string ThemeKey = "theme"; + public const string DataKey = "data"; + /// /// 流程运行环境 /// - public FlowEnvironment() + public FlowEnvironment(UIContextOperation uiContextOperation) { - sereinIOC = new SereinIOC(); - ChannelFlowInterrupt = new ChannelFlowInterrupt(); - IsGlobalInterrupt = false; - flowStarter = null; - sereinIOC.OnIOCMembersChanged += e => this?.OnIOCMembersChanged?.Invoke(e) ; // 监听IOC容器的注册 + this.sereinIOC = new SereinIOC(); + this.ChannelFlowInterrupt = new ChannelFlowInterrupt(); + this.IsGlobalInterrupt = false; + this.flowStarter = null; + this.sereinIOC.OnIOCMembersChanged += e => this?.OnIOCMembersChanged?.Invoke(e); // 监听IOC容器的注册 + this.uiContextOperation = uiContextOperation; // 本地环境需要存放视图管理 } - /// - /// WebSocket处理 - /// - public Guid HandleGuid { get; } = new Guid(); + #region 远程管理 + + private MsgControllerOfServer clientMsgManage; + /// - /// 流程环境远程管理服务 + /// 表示是否正在控制远程 + /// Local control remote env /// - private WebSocketServer FlowEnvRemoteWebSocket; + public bool IsLcR { get; set; } + /// + /// 表示是否受到远程控制 + /// Remote control local env + /// + public bool IsRcL { get; set; } + + + + + - private async Task InspectionAuthorized(dynamic token) - { - await Task.Delay(0); - var tokenValue = token.ToString(); - if ("123456".Equals(tokenValue)) - { - return true; - } - else - { - return false; - } - } /// /// 打开远程管理 /// /// public async Task StartRemoteServerAsync(int port = 7525) { - FlowEnvRemoteWebSocket ??= new WebSocketServer("token", InspectionAuthorized); - FlowEnvRemoteWebSocket.MsgHandleHelper.AddModule(this, - (ex, send) => + if (clientMsgManage is null) { - send(new - { - code = 400, - ex = ex.Message - }); - }); - var url = $"http://*:{port}/"; - try - { - await FlowEnvRemoteWebSocket.StartAsync(url); - } - catch (Exception ex) - { - FlowEnvRemoteWebSocket.MsgHandleHelper.RemoveModule(this); - Console.WriteLine("打开远程管理异常:" + ex); + clientMsgManage = new MsgControllerOfServer(this); + //clientMsgManage = new MsgControllerOfServer(this, "token"); } + await clientMsgManage.StartRemoteServerAsync(port); } /// /// 结束远程管理 /// - [AutoSocketHandle] public void StopRemoteServer() { try { - FlowEnvRemoteWebSocket.Stop(); + clientMsgManage.StopRemoteServer(); } catch (Exception ex) { Console.WriteLine("结束远程管理异常:" + ex); } } - - - /// - /// 节点的命名空间 - /// - public const string SpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; + #endregion #region 环境运行事件 /// @@ -249,17 +181,29 @@ namespace Serein.NodeFlow /// public event NodeMovedHandler? OnNodeMoved; + /// + /// 运行环境输出 + /// + public event EnvOutHandler? OnEnvOut; + #endregion #region 属性 + + public IFlowEnvironment CurrentEnv { get => this; } + + + + + /// /// 如果没有全局触发器,且没有循环分支,流程执行完成后自动为 Completion 。 /// - public RunState FlowState { get; set; } = RunState.NoStart; + public RunState FlowState { get; set; } = RunState.NoStart; /// /// 如果全局触发器还在运行,则为 Running 。 /// - public RunState FlipFlopState { get; set; } = RunState.NoStart; + public RunState FlipFlopState { get; set; } = RunState.NoStart; /// /// 环境名称 @@ -282,6 +226,10 @@ namespace Serein.NodeFlow /// public ISereinIOC IOC { get => this; } + + #endregion + + #region 私有变量 /// /// Library 与 MethodDetailss的依赖关系 /// @@ -297,29 +245,31 @@ namespace Serein.NodeFlow ///
public ConcurrentDictionary MethodDetailss { get; } = []; - #endregion + /// + /// UI线程操作类 + /// + private readonly UIContextOperation uiContextOperation; - #region 私有变量 /// /// 容器管理 /// - public readonly SereinIOC sereinIOC; + private readonly SereinIOC sereinIOC; /// /// 环境加载的节点集合 /// Node Guid - Node Model /// - public Dictionary Nodes { get; } = []; + private Dictionary Nodes { get; } = []; /// /// 存放触发器节点(运行时全部调用) /// - public List FlipflopNodes { get; } = []; + private List FlipflopNodes { get; } = []; /// /// 从dll中加载的类的注册类型 /// - public Dictionary> AutoRegisterTypes { get; } = []; + private Dictionary> AutoRegisterTypes { get; } = []; /// /// 存放委托 @@ -327,17 +277,17 @@ namespace Serein.NodeFlow /// md.Methodname - delegate /// - public ConcurrentDictionary MethodDelegates { get; } = []; + private ConcurrentDictionary MethodDelegates { get; } = []; /// /// 起始节点私有属性 /// - public NodeModelBase? _startNode = null; + private NodeModelBase? _startNode = null; /// /// 起始节点 /// - public NodeModelBase? StartNode + private NodeModelBase? StartNode { get { @@ -361,18 +311,38 @@ namespace Serein.NodeFlow /// /// 流程启动器(每次运行时都会重新new一个) /// - public FlowStarter? flowStarter; + private FlowStarter? flowStarter; + #endregion #region 环境对外接口 + /// + /// 重定向Console输出 + /// + public void SetConsoleOut() + { + var logTextWriter = new LogTextWriter(msg => Output(msg)); + Console.SetOut(logTextWriter); + } + + /// + /// 使用JSON处理库输出对象信息 + /// + /// + public void WriteLineObjToJson(object obj) + { + var msg = JsonConvert.SerializeObject(obj); + OnEnvOut?.Invoke(msg + Environment.NewLine); + } + + /// /// 异步运行 /// /// - [AutoSocketHandle] public async Task StartAsync() { ChannelFlowInterrupt?.CancelAllTasks(); @@ -398,17 +368,16 @@ namespace Serein.NodeFlow loadMethods.AddRange(loadMds); exitMethods.AddRange(exitMds); - this.IOC.Reset(); // 开始运行时清空ioc中注册的实例 - this.IOC.CustomRegisterInstance(typeof(IFlowEnvironment).FullName,this); - - + IOC.Reset(); // 开始运行时清空ioc中注册的实例 + IOC.CustomRegisterInstance(typeof(UIContextOperation).FullName, this.uiContextOperation, false); + IOC.CustomRegisterInstance(typeof(IFlowEnvironment).FullName, this); await flowStarter.RunAsync(this, nodes, AutoRegisterTypes, initMethods, loadMethods, exitMethods); - if (this.FlipFlopState == RunState.Completion) + if (FlipFlopState == RunState.Completion) { - this.ExitFlow(); // 未运行触发器时,才会调用结束方法 + ExitFlow(); // 未运行触发器时,才会调用结束方法 } flowStarter = null; } @@ -418,21 +387,21 @@ namespace Serein.NodeFlow ///
/// /// - [AutoSocketHandle] public async Task StartAsyncInSelectNode(string startNodeGuid) { + if (flowStarter is null) { return; } - if (this.FlowState == RunState.Running || this.FlipFlopState == RunState.Running) + if (FlowState == RunState.Running || FlipFlopState == RunState.Running) { NodeModelBase? nodeModel = GuidToModel(startNodeGuid); if (nodeModel is null || nodeModel is SingleFlipflopNode) { return; } - await flowStarter.StartFlowInSelectNodeAsync(nodeModel); + await flowStarter.StartFlowInSelectNodeAsync(this, nodeModel); } else { @@ -443,7 +412,6 @@ namespace Serein.NodeFlow /// /// 退出 /// - [AutoSocketHandle] public void ExitFlow() { ChannelFlowInterrupt?.CancelAllTasks(); @@ -474,7 +442,7 @@ namespace Serein.NodeFlow if (nodeModel is null) return; if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器 { - if (this.FlowState != RunState.Completion + if (FlowState != RunState.Completion && flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点 { _ = flowStarter.RunGlobalFlipflopAsync(this, flipflopNode);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中 @@ -482,6 +450,7 @@ namespace Serein.NodeFlow } } } + /// /// 关闭全局触发器 /// @@ -502,11 +471,11 @@ namespace Serein.NodeFlow ///
/// [AutoSocketHandle] - public object GetEnvInfo() + public async Task GetEnvInfoAsync() { Dictionary> LibraryMds = []; - foreach (var mdskv in this.MethodDetailsOfLibrarys) + foreach (var mdskv in MethodDetailsOfLibrarys) { var library = mdskv.Key; var mds = mdskv.Value; @@ -518,15 +487,22 @@ namespace Serein.NodeFlow LibraryMds[library] = t_mds; } var mdInfo = md.ToInfo(); - mdInfo.LibraryName = library.Assembly.GetName().FullName; + mdInfo.LibraryName = library.FullName; t_mds.Add(mdInfo); } } - var project = this.GetProjectInfo(); - return new + + LibraryMds[] libraryMdss = LibraryMds.Select(kv => new LibraryMds { - project = project, - envNode = LibraryMds.Values, + LibraryName = kv.Key.FullName, + Mds = kv.Value.ToArray() + }).ToArray(); + var project = await GetProjectInfoAsync(); + Console.WriteLine("已将当前环境信息发送到远程客户端"); + return new FlowEnvInfo + { + Project = project, // 项目信息 + LibraryMds = libraryMdss, // 环境方法 }; } @@ -546,20 +522,15 @@ namespace Serein.NodeFlow /// /// 加载项目文件 /// - /// + /// 环境信息 /// - [AutoSocketHandle] - public void LoadProject(SereinProjectData project, string filePath) + public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath) { + var projectData = flowEnvInfo.Project; // 加载项目配置文件 - var dllPaths = project.Librarys.Select(it => it.Path).ToList(); + var dllPaths = projectData.Librarys.Select(it => it.FileName).ToList(); List methodDetailss = []; - //string currentPath = Environment.CurrentDirectory; // 获取当前目录 - //string path = Assembly.GetExecutingAssembly().Location; // 获取当前正在执行的文件的路径 - //string exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); // 获取包含可执行文件的目录 - //string basePath = AppDomain.CurrentDomain.BaseDirectory; // 获取应用程序的执行路径: - // 遍历依赖项中的特性注解,生成方法详情 foreach (var dllPath in dllPaths) { @@ -569,38 +540,27 @@ namespace Serein.NodeFlow } List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>(); - List<(NodeModelBase, Position)> ordinaryNodes = new List<(NodeModelBase, Position)>(); + List<(NodeModelBase, PositionOfUI)> ordinaryNodes = new List<(NodeModelBase, PositionOfUI)>(); // 加载节点 - foreach (var nodeInfo in project.Nodes) + foreach (var nodeInfo in projectData.Nodes) { - var controlType = GetNodeControlType(nodeInfo); + var controlType = FlowFunc.GetNodeControlType(nodeInfo); if (controlType == NodeControlType.None) { continue; } else { - //TryGetMethodDetails(nodeInfo.MethodName, out MethodDetailsInfo? methodDetailsInfo); MethodDetailss.TryGetValue(nodeInfo.MethodName, out var methodDetails);// 加载项目时尝试获取方法信息 - if (controlType == NodeControlType.ExpOp || controlType == NodeControlType.ExpOp) - { - methodDetails ??= new MethodDetails(); - } - if(methodDetails is null) - { - continue; // 节点对应的方法不存在于DLL中 - } - - - var nodeModel = CreateNode(controlType, methodDetails); + var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 nodeModel.LoadInfo(nodeInfo); // 创建节点model if (nodeModel is null) { nodeInfo.Guid = string.Empty; continue; } - TryAddNode(nodeModel); + TryAddNode(nodeModel); // 加载项目时将节点加载到环境中 if (nodeInfo.ChildNodeGuids?.Length > 0) { regionChildNodes.Add((nodeModel, nodeInfo.ChildNodeGuids)); @@ -612,7 +572,7 @@ namespace Serein.NodeFlow } } } - // 加载区域的子项 + // 加载区域子项 foreach ((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes) { foreach (var childNodeGuid in item.childNodeGuids) @@ -628,7 +588,7 @@ namespace Serein.NodeFlow } } // 加载节点 - foreach ((NodeModelBase nodeModel, Position position) item in ordinaryNodes) + foreach ((NodeModelBase nodeModel, PositionOfUI position) item in ordinaryNodes) { bool IsContinue = false; foreach ((NodeModelBase region, string[] childNodeGuids) item2 in regionChildNodes) @@ -648,7 +608,7 @@ namespace Serein.NodeFlow // 确定节点之间的连接关系 - foreach (var nodeInfo in project.Nodes) + foreach (var nodeInfo in projectData.Nodes) { if (!Nodes.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode)) { @@ -678,51 +638,57 @@ namespace Serein.NodeFlow } } - SetStartNode(project.StartNode); + SetStartNode(projectData.StartNode); OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs()); + + } + + + + + /// + /// 加载远程环境 + /// + /// 远程环境地址 + /// 远程环境端口 + /// 密码 + public async Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres, int port, string token) + { + if (IsLcR) + { + await Console.Out.WriteLineAsync($"当前已经连接远程环境"); + return (false, null); + } + // 没有连接远程环境,可以重新连接 + var remoteEnvControl = new RemoteEnvControl(addres, port, token); + var result = await remoteEnvControl.ConnectAsync(); + if (!result) + { + await Console.Out.WriteLineAsync("连接失败,请检查地址与端口是否正确"); + return (false, null); + } + await Console.Out.WriteLineAsync("连接成功,开始验证Token"); + IsLcR = true; + return (true, remoteEnvControl); } /// - /// 加载远程项目 + /// 退出远程环境 /// - /// 远程项目地址 - /// 远程项目端口 - /// 密码 - [AutoSocketHandle] - public void LoadRemoteProject(string addres, int port, string token) + public void ExitRemoteEnv() { - // -- 第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}"); - //} + IsLcR = false; } - /// /// 序列化当前项目的依赖信息、节点信息 /// /// - [AutoSocketHandle] - public SereinProjectData GetProjectInfo() + public async Task GetProjectInfoAsync() { var projectData = new SereinProjectData() { - Librarys = Librarys.Values.Select(assemblies => assemblies.Assembly.ToLibrary()).ToArray(), + Librarys = Librarys.Values.Select(lib => lib.ToLibrary()).ToArray(), Nodes = Nodes.Values.Select(node => node.ToInfo()).Where(info => info is not null).ToArray(), StartNode = Nodes.Values.FirstOrDefault(it => it.IsStart)?.Guid, }; @@ -746,11 +712,10 @@ namespace Serein.NodeFlow ///
/// /// - [AutoSocketHandle] public bool RemoteDll(string assemblyFullName) { - var library = Librarys.Values.FirstOrDefault(nl => assemblyFullName.Equals(nl.Assembly.FullName)); - if(library is null) + var library = Librarys.Values.FirstOrDefault(nl => assemblyFullName.Equals(nl.FullName)); + if (library is null) { return false; } @@ -763,16 +728,16 @@ namespace Serein.NodeFlow group => group.Count()); - if(Nodes.Count == 0) + if (Nodes.Count == 0) { return true; // 当前无节点,可以直接删除 } - if (MethodDetailsOfLibrarys.TryGetValue(library,out var mds)) // 存在方法 + if (MethodDetailsOfLibrarys.TryGetValue(library, out var mds)) // 存在方法 { - foreach(var md in mds) + foreach (var md in mds) { - if(groupedNodes.TryGetValue(md.MethodName,out int count)) + if (groupedNodes.TryGetValue(md.MethodName, out int count)) { if (count > 0) { @@ -786,11 +751,11 @@ namespace Serein.NodeFlow MethodDetailss.TryRemove(md.MethodName, out _); } MethodDetailsOfLibrarys.TryRemove(library, out _); - return true; + return true; } else { - return true; + return true; } } @@ -801,23 +766,30 @@ namespace Serein.NodeFlow /// /// /// 如果是表达式节点条件节点,该项为null - [AutoSocketHandle] - public void CreateNode(NodeControlType nodeControlType, Position position, MethodDetailsInfo? methodDetailsInfo = null) + public async Task CreateNodeAsync(NodeControlType nodeControlType, PositionOfUI position, MethodDetailsInfo? methodDetailsInfo = null) { - MethodDetails? methodDetails = null; - if (methodDetailsInfo != null &&!MethodDetailss.TryGetValue(methodDetailsInfo.MethodName, out methodDetails)) - { - return; - } - var nodeModel = CreateNode(nodeControlType, methodDetails); - TryAddNode(nodeModel); + - //if (flowStarter?.FlowState != RunState.Completion - // && nodeControlType == NodeControlType.Flipflop - // && nodeModel is SingleFlipflopNode flipflopNode) - //{ - // _ = flowStarter?.RunGlobalFlipflopAsync(this, flipflopNode); // 当前添加节点属于触发器,且当前正在运行,则加载到运行环境中 - //} + NodeModelBase? nodeModel = null; + if (methodDetailsInfo is null) + { + nodeModel = FlowFunc.CreateNode(this, nodeControlType); // 加载基础节点 + } + else + { + if (MethodDetailss.TryGetValue(methodDetailsInfo.MethodName, out var methodDetails)) + { + nodeModel = FlowFunc.CreateNode(this, nodeControlType, methodDetails); // 一般的加载节点方法 + } + else + { + return null; + } + } + + + TryAddNode(nodeModel); + nodeModel.Position = position; // 通知UI更改 OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position)); @@ -827,6 +799,9 @@ namespace Serein.NodeFlow { SetStartNode(nodeModel); } + return nodeModel.ToInfo(); + + } /// @@ -834,7 +809,6 @@ namespace Serein.NodeFlow /// /// /// - [AutoSocketHandle] public void RemoveNode(string nodeGuid) { var remoteNode = GuidToModel(nodeGuid); @@ -889,16 +863,15 @@ namespace Serein.NodeFlow /// 起始节点 /// 目标节点 /// 连接关系 - [AutoSocketHandle] - public void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + public async Task ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) { // 获取起始节点与目标节点 var fromNode = GuidToModel(fromNodeGuid); var toNode = GuidToModel(toNodeGuid); - if (fromNode is null) return; - if (toNode is null) return; + if (fromNode is null) return false; + if (toNode is null) return false; // 开始连接 - ConnectNode(fromNode, toNode, connectionType); // 外部调用连接方法 + return await ConnectNode(fromNode, toNode, connectionType); // 外部调用连接方法 } @@ -909,7 +882,6 @@ namespace Serein.NodeFlow /// 目标节点Guid /// 连接关系 /// - [AutoSocketHandle] public void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) { // 获取起始节点与目标节点 @@ -926,15 +898,15 @@ namespace Serein.NodeFlow /// /// 获取方法描述 /// - + public bool TryGetMethodDetailsInfo(string name, out MethodDetailsInfo? md) { if (!string.IsNullOrEmpty(name)) { - foreach(var t_md in MethodDetailss.Values) + foreach (var t_md in MethodDetailss.Values) { md = t_md.ToInfo(); - if(md != null) + if (md != null) { return true; } @@ -980,17 +952,19 @@ namespace Serein.NodeFlow /// /// /// - [AutoSocketHandle] - public void MoveNode(string nodeGuid,double x,double y) + public void MoveNode(string nodeGuid, double x, double y) { - this.OnNodeMoved?.Invoke(new NodeMovedEventArgs(nodeGuid, x, y)); + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null) return; + nodeModel.Position.X = x; + nodeModel.Position.Y = y; + OnNodeMoved?.Invoke(new NodeMovedEventArgs(nodeGuid, x, y)); } /// /// 设置起点控件 /// /// - [AutoSocketHandle] public void SetStartNode(string newNodeGuid) { var newStartNodeModel = GuidToModel(newNodeGuid); @@ -1004,9 +978,10 @@ namespace Serein.NodeFlow /// 被中断的目标节点Guid /// 中断级别 /// 操作是否成功 - [AutoSocketHandle] - public bool SetNodeInterrupt(string nodeGuid, InterruptClass interruptClass) + public async Task SetNodeInterruptAsync(string nodeGuid, InterruptClass interruptClass) { + + var nodeModel = GuidToModel(nodeGuid); if (nodeModel is null) return false; if (interruptClass == InterruptClass.None) @@ -1043,10 +1018,9 @@ namespace Serein.NodeFlow /// 如果是节点,传入Guid;如果是对象,传入类型FullName /// 合法的条件表达式 /// - [AutoSocketHandle] - public bool AddInterruptExpression(string key, string expression) + public async Task AddInterruptExpressionAsync(string key, string expression) { - if(string.IsNullOrEmpty(expression)) return false; + if (string.IsNullOrEmpty(expression)) return false; if (dictMonitorObjExpInterrupt.TryGetValue(key, out var condition)) { condition.Clear(); // 暂时 @@ -1073,8 +1047,7 @@ namespace Serein.NodeFlow /// 如果是节点,传入Guid;如果是对象,传入类型FullName /// ture监视对象;false取消对象监视 /// - [AutoSocketHandle] - public void SetMonitorObjState(string key, bool isMonitor) + public void SetMonitorObjState(string key, bool isMonitor) { if (string.IsNullOrEmpty(key)) { return; } var isExist = dictMonitorObjExpInterrupt.ContainsKey(key); @@ -1100,10 +1073,20 @@ namespace Serein.NodeFlow /// /// /// - public bool CheckObjMonitorState(string key, out List? exps) + public async Task<(bool, string[])> CheckObjMonitorStateAsync(string key) { - if (string.IsNullOrEmpty(key)) { exps = null; return false; } - return dictMonitorObjExpInterrupt.TryGetValue(key, out exps); + if (string.IsNullOrEmpty(key)) + return (false, Array.Empty()); + var isMonitor = dictMonitorObjExpInterrupt.TryGetValue(key, out var exps); + if (exps is null) + { + return (isMonitor, Array.Empty()); + } + else + { + return (isMonitor, exps.ToArray()); + } + } /// @@ -1133,13 +1116,63 @@ namespace Serein.NodeFlow /// 环境执行中断 /// /// - public Task GetOrCreateGlobalInterruptAsync() + public async Task GetOrCreateGlobalInterruptAsync() { IsGlobalInterrupt = true; - return ChannelFlowInterrupt.GetOrCreateChannelAsync(this.EnvName); + var result = await ChannelFlowInterrupt.GetOrCreateChannelAsync(EnvName); + return result; } + /// + /// 记录节点更改数据,防止重复更改 + /// + public HashSet<(string, string, object)> NodeValueChangeLogger = new HashSet<(string, string, object)>(); + + /// + /// 数据更改通知(来自远程) + /// + /// 发生在哪个节点 + /// 属性路径 + /// 变化后的属性值 + /// + public async Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value) + { + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null) return; + if(NodeValueChangeLogger.Remove((nodeGuid, path, value))) + { + // 说明存在过重复的修改 + return; + } + + Console.WriteLine($"本地环境收到数据更改通知:{value}"); + + var getExp = $"@Get .{path}"; + //Console.WriteLine($"取值表达式:{getExp}"); + var getResult = SerinExpressionEvaluator.Evaluate(getExp, nodeModel, out _); + Console.WriteLine($"原数据 :{getResult}"); + if (getResult.Equals(value)) + { + Console.WriteLine("无须修改" ); + return; + } + + + NodeValueChangeLogger.Add((nodeGuid, path, value)); + + + var setExp = $"@Set .{path} = {value}"; + //Console.WriteLine($"设值表达式:{setExp}"); + SerinExpressionEvaluator.Evaluate(setExp, nodeModel, out _); + getResult = SerinExpressionEvaluator.Evaluate(getExp, nodeModel, out _); + Console.WriteLine($"新数据 :{getResult}"); + } + + + + + /// /// Guid 转 NodeModel /// @@ -1165,27 +1198,28 @@ namespace Serein.NodeFlow #region 私有方法 + /// /// 加载指定路径的DLL文件 /// /// - + private void LoadDllNodeInfo(string dllPath) { (var nodeLibrary, var registerTypes, var mdlist) = LoadAssembly(dllPath); if (nodeLibrary is not null && mdlist.Count > 0) { - Librarys.TryAdd(nodeLibrary.Name, nodeLibrary); + Librarys.TryAdd(nodeLibrary.FullName, nodeLibrary); MethodDetailsOfLibrarys.TryAdd(nodeLibrary, mdlist); - - foreach(var md in mdlist) + + foreach (var md in mdlist) { MethodDetailss.TryAdd(md.MethodName, md); } foreach (var kv in registerTypes) { - if (!AutoRegisterTypes.TryGetValue(kv.Key, out var types)) + if (!AutoRegisterTypes.TryGetValue(kv.Key, out var types)) { types = new List(); AutoRegisterTypes.Add(kv.Key, types); @@ -1197,6 +1231,7 @@ namespace Serein.NodeFlow } } + /// /// 移除连接关系 /// @@ -1229,22 +1264,23 @@ namespace Serein.NodeFlow var autoRegisterAttribute = type.GetCustomAttribute(); if (autoRegisterAttribute is not null) { - if(!autoRegisterTypes.TryGetValue(autoRegisterAttribute.Class,out var valus)) + if (!autoRegisterTypes.TryGetValue(autoRegisterAttribute.Class, out var valus)) { valus = new List(); autoRegisterTypes.Add(autoRegisterAttribute.Class, valus); } valus.Add(type); } - + } - + //Dictionary autoRegisterTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute() is not null).ToList(); - List<(Type, string)> scanTypes = types.Select(t => { + List<(Type, string)> scanTypes = types.Select(t => + { if (t.GetCustomAttribute() is DynamicFlowAttribute dynamicFlowAttribute && dynamicFlowAttribute.Scan == true) { @@ -1254,7 +1290,7 @@ namespace Serein.NodeFlow { return (null, null); } - }).Where(it => it.t is not null) .ToList(); + }).Where(it => it.t is not null).ToList(); if (scanTypes.Count == 0) { return (null, [], []); @@ -1262,7 +1298,7 @@ namespace Serein.NodeFlow List methodDetails = new List(); // 遍历扫描的类型 - foreach ((var type,var flowName ) in scanTypes) + foreach ((var type, var flowName) in scanTypes) { // 加载DLL,创建 MethodDetails、实例作用对象、委托方法 var assemblyName = type.Assembly.GetName().Name; @@ -1271,10 +1307,10 @@ namespace Serein.NodeFlow continue; } var methods = NodeMethodDetailsHelper.GetMethodsToProcess(type); - foreach(var method in methods) + foreach (var method in methods) { (var md, var del) = NodeMethodDetailsHelper.CreateMethodDetails(type, method, assemblyName); - if(md is null || del is null) + if (md is null || del is null) { Console.WriteLine($"无法加载方法信息:{assemblyName}-{type}-{method}"); continue; @@ -1289,93 +1325,42 @@ namespace Serein.NodeFlow Console.WriteLine($"节点委托创建失败:{md.MethodName}"); } } - + //methodDetails.AddRange(itemMethodDetails); } var nodeLibrary = new NodeLibrary { - Name = assembly.GetName().FullName, + FullName = assembly.GetName().FullName, Assembly = assembly, - Path = dllPath, + FileName = Path.GetFileName(dllPath), + FilePath = dllPath, }; //LoadedAssemblies.Add(assembly); // 将加载的程序集添加到列表中 //LoadedAssemblyPaths.Add(dllPath); // 记录加载的DLL路径 - return (nodeLibrary, autoRegisterTypes , methodDetails); + return (nodeLibrary, autoRegisterTypes, methodDetails); } catch (Exception ex) { Console.WriteLine(ex.ToString()); - return (null, [],[]); + return (null, [], []); } } - /// - /// 运行环节加载了项目文件,需要创建节点控件 - /// - /// - /// - /// - /// - private NodeControlType GetNodeControlType(NodeInfo nodeInfo) - { - // 创建控件实例 - NodeControlType controlType = nodeInfo.Type switch - { - $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件 - $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件 - - $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件 - $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件 - - $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件 - _ => NodeControlType.None, - }; - - return controlType; - } /// /// 创建节点 /// /// - private NodeModelBase CreateNode(NodeControlType nodeControlType, MethodDetails? methodDetails = null) + + private bool TryAddNode(NodeModelBase nodeModel) { - // 确定创建的节点类型 - Type? nodeType = nodeControlType switch - { - NodeControlType.Action => typeof(SingleActionNode), - NodeControlType.Flipflop => typeof(SingleFlipflopNode), - - NodeControlType.ExpOp => typeof(SingleExpOpNode), - NodeControlType.ExpCondition => typeof(SingleConditionNode), - NodeControlType.ConditionRegion => typeof(CompositeConditionNode), - _ => null - }; - - if (nodeType is null) - { - throw new Exception($"节点类型错误[{nodeControlType}]"); - } - // 生成实例 - var nodeObj = Activator.CreateInstance(nodeType); - if (nodeObj is not NodeModelBase nodeBase) - { - throw new Exception($"无法创建目标节点类型的实例[{nodeControlType}]"); - } - - // 配置基础的属性 - nodeBase.ControlType = nodeControlType; - if (methodDetails != null) - { - var md = methodDetails.Clone(); - nodeBase.DisplayName = md.MethodTips; - nodeBase.MethodDetails = md; - } + nodeModel.Guid ??= Guid.NewGuid().ToString(); + Nodes[nodeModel.Guid] = nodeModel; // 如果是触发器,则需要添加到专属集合中 - if (nodeControlType == NodeControlType.Flipflop && nodeBase is SingleFlipflopNode flipflopNode) + if (nodeModel is SingleFlipflopNode flipflopNode) { var guid = flipflopNode.Guid; if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid))) @@ -1383,14 +1368,6 @@ namespace Serein.NodeFlow FlipflopNodes.Add(flipflopNode); } } - - return nodeBase; - } - - private bool TryAddNode(NodeModelBase nodeModel) - { - nodeModel.Guid ??= Guid.NewGuid().ToString(); - Nodes[nodeModel.Guid] = nodeModel; return true; } @@ -1400,11 +1377,11 @@ namespace Serein.NodeFlow /// 起始节点 /// 目标节点 /// 连接关系 - private void ConnectNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) + private Task ConnectNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) { if (fromNode is null || toNode is null || fromNode == toNode) { - return; + return Task.FromResult(false); } var ToExistOnFrom = true; @@ -1418,6 +1395,8 @@ namespace Serein.NodeFlow { flowStarter?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被连接的是全局触发器,尝试移除 } + + var isPass = false; foreach (ConnectionType ctType in ct) { var FToTo = fromNode.SuccessorNodes[ctType].Where(it => it.Guid.Equals(toNode.Guid)).ToArray(); @@ -1427,7 +1406,7 @@ namespace Serein.NodeFlow if (ToExistOnFrom && FromExistInTo) { Console.WriteLine("起始节点已与目标节点存在连接"); - return; + isPass = false; } else { @@ -1435,28 +1414,37 @@ namespace Serein.NodeFlow if (!ToExistOnFrom && FromExistInTo) { Console.WriteLine("目标节点不是起始节点的子节点,起始节点却是目标节点的父节点"); - return; + isPass = false; } else if (ToExistOnFrom && !FromExistInTo) { // Console.WriteLine(" 起始节点不是目标节点的父节点,目标节点却是起始节点的子节点"); - return; + isPass = false; } - else // if (!ToExistOnFrom && !FromExistInTo) + else { - // 可以正常连接 + isPass = true; } } } + if (!isPass) + { + return Task.FromResult(false); + } + else + { + fromNode.SuccessorNodes[connectionType].Add(toNode); // 添加到起始节点的子分支 + toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支 + OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, + toNode.Guid, + connectionType, + NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI + return Task.FromResult(true); + } - fromNode.SuccessorNodes[connectionType].Add(toNode); // 添加到起始节点的子分支 - toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支 - OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, - toNode.Guid, - connectionType, - NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI + } /// @@ -1471,6 +1459,14 @@ namespace Serein.NodeFlow OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(oldNodeGuid, StartNode.Guid)); } + /// + /// 输出内容 + /// + /// + private void Output(string msg) + { + OnEnvOut?.Invoke(msg); + } #endregion @@ -1610,102 +1606,17 @@ namespace Serein.NodeFlow } - /// - /// 流程环境需要的扩展方法 - /// - public static class FlowFunc - { - /// - /// 程序集封装依赖 - /// - /// - /// - public static Library.Entity.Library ToLibrary(this Assembly assembly) - { - var tmp = assembly.ManifestModule.Name; - return new Library.Entity.Library - { - Name = assembly.GetName().Name, - Path = assembly.ManifestModule.Name, - }; - } - - /// - /// 触发器运行后状态转为对应的后继分支类别 - /// - /// - /// - /// - public static ConnectionType ToContentType(this FlipflopStateType flowStateType) - { - return flowStateType switch - { - FlipflopStateType.Succeed => ConnectionType.IsSucceed, - FlipflopStateType.Fail => ConnectionType.IsFail, - FlipflopStateType.Error => ConnectionType.IsError, - FlipflopStateType.Cancel => ConnectionType.None, - _ => throw new NotImplementedException("未定义的流程状态") - }; - } - - /// - /// 判断 触发器节点 是否存在上游分支 - /// - /// - /// - public static bool NotExitPreviousNode(this SingleFlipflopNode node) - { - ConnectionType[] ct = [ConnectionType.IsSucceed, - ConnectionType.IsFail, - ConnectionType.IsError, - ConnectionType.Upstream]; - foreach (ConnectionType ctType in ct) - { - if (node.PreviousNodes[ctType].Count > 0) - { - return false; - } - } - return true; - } - ///// - ///// 从节点类型枚举中转为对应的 Model 类型 - ///// - ///// - ///// - //public static Type? ControlTypeToModel(this NodeControlType nodeControlType) - //{ - // // 确定创建的节点类型 - // Type? nodeType = nodeControlType switch - // { - // NodeControlType.Action => typeof(SingleActionNode), - // NodeControlType.Flipflop => typeof(SingleFlipflopNode), - // NodeControlType.ExpOp => typeof(SingleExpOpNode), - // NodeControlType.ExpCondition => typeof(SingleConditionNode), - // NodeControlType.ConditionRegion => typeof(CompositeConditionNode), - // _ => null - // }; - // return nodeType; - //} - //public static NodeControlType ModelToControlType(this NodeControlType nodeControlType) - //{ - // var type = nodeControlType.GetType(); - // NodeControlType controlType = type switch - // { - // Type when type == typeof(SingleActionNode) => NodeControlType.Action, - // Type when type == typeof(SingleFlipflopNode) => NodeControlType.Flipflop, - // Type when type == typeof(SingleExpOpNode) => NodeControlType.ExpOp, - // Type when type == typeof(SingleConditionNode) => NodeControlType.ExpCondition, - // Type when type == typeof(CompositeConditionNode) => NodeControlType.ConditionRegion, - // _ => NodeControlType.None, - // }; - // return controlType; - //} - } + + + + + + + diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs new file mode 100644 index 0000000..3624612 --- /dev/null +++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs @@ -0,0 +1,470 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.Utils; +using Serein.Library.Web; +using Serein.NodeFlow.Tool; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Env +{ + /// + /// 自动管理本地与远程的环境 + /// + public class FlowEnvironmentDecorator : IFlowEnvironment, ISereinIOC + { + public FlowEnvironmentDecorator(UIContextOperation uiContextOperation) + { + flowEnvironment = new FlowEnvironment(uiContextOperation); + // 默认使用本地环境 + currentFlowEnvironment = flowEnvironment; + } + + /// + /// 本地环境 + /// + private readonly FlowEnvironment flowEnvironment; + + /// + /// 远程环境 + /// + private RemoteFlowEnvironment remoteFlowEnvironment; + + /// + /// 管理当前环境 + /// + + private IFlowEnvironment currentFlowEnvironment; + + + private int _flag = 0; // 使用原子自增代替锁 + /// + /// 传入false时,将停止数据通知。传入true时, + /// + /// + public void SetFlag(bool value) + { + Interlocked.Exchange(ref _flag, value ? 1 : 0); + } + /// + /// + /// + /// + public bool IsFlagSet() + { + return Interlocked.CompareExchange(ref _flag, 1, 1) == 1; + } + + + + + /// + /// 当前环境,用于切换远程与本地环境 + /// + public IFlowEnvironment CurrentEnv { get => currentFlowEnvironment; } + + + public ISereinIOC IOC => (ISereinIOC)currentFlowEnvironment; + + public string EnvName => currentFlowEnvironment.EnvName; + + public bool IsGlobalInterrupt => currentFlowEnvironment.IsGlobalInterrupt; + + public bool IsLcR => currentFlowEnvironment.IsLcR; + + public bool IsRcL => currentFlowEnvironment.IsRcL; + + public RunState FlowState { get => currentFlowEnvironment.FlowState; set => currentFlowEnvironment.FlowState = value; } + public RunState FlipFlopState { get => currentFlowEnvironment.FlipFlopState; set => currentFlowEnvironment.FlipFlopState = value; } + + public event LoadDllHandler OnDllLoad { + add { currentFlowEnvironment.OnDllLoad += value; } + remove { currentFlowEnvironment.OnDllLoad -= value; } + } + public event ProjectLoadedHandler OnProjectLoaded + { + add { currentFlowEnvironment.OnProjectLoaded += value; } + remove { currentFlowEnvironment.OnProjectLoaded -= value; } + } + + public event NodeConnectChangeHandler OnNodeConnectChange + { + add { currentFlowEnvironment.OnNodeConnectChange += value; } + remove { currentFlowEnvironment.OnNodeConnectChange -= value; } + } + + public event NodeCreateHandler OnNodeCreate + { + add { currentFlowEnvironment.OnNodeCreate += value; } + remove { currentFlowEnvironment.OnNodeCreate -= value; } + } + + public event NodeRemoteHandler OnNodeRemote + { + add { currentFlowEnvironment.OnNodeRemote += value; } + remove { currentFlowEnvironment.OnNodeRemote -= value; } + } + + public event StartNodeChangeHandler OnStartNodeChange + { + add { currentFlowEnvironment.OnStartNodeChange += value; } + remove { currentFlowEnvironment.OnStartNodeChange -= value; } + } + + public event FlowRunCompleteHandler OnFlowRunComplete + { + add { currentFlowEnvironment.OnFlowRunComplete += value; } + remove { currentFlowEnvironment.OnFlowRunComplete -= value; } + } + + public event MonitorObjectChangeHandler OnMonitorObjectChange + { + add { currentFlowEnvironment.OnMonitorObjectChange += value; } + remove { currentFlowEnvironment.OnMonitorObjectChange -= value; } + } + + public event NodeInterruptStateChangeHandler OnNodeInterruptStateChange + { + add { currentFlowEnvironment.OnNodeInterruptStateChange += value; } + remove { currentFlowEnvironment.OnNodeInterruptStateChange -= value; } + } + + public event ExpInterruptTriggerHandler OnInterruptTrigger + { + add { currentFlowEnvironment.OnInterruptTrigger += value; } + remove { currentFlowEnvironment.OnInterruptTrigger -= value; } + } + + public event IOCMembersChangedHandler OnIOCMembersChanged + { + add { currentFlowEnvironment.OnIOCMembersChanged += value; } + remove { currentFlowEnvironment.OnIOCMembersChanged -= value; } + } + + public event NodeLocatedHandler OnNodeLocated + { + add { currentFlowEnvironment.OnNodeLocated += value; } + remove { currentFlowEnvironment.OnNodeLocated -= value; } + } + + public event NodeMovedHandler OnNodeMoved + { + add { currentFlowEnvironment.OnNodeMoved += value; } + remove { currentFlowEnvironment.OnNodeMoved -= value; } + } + + public event EnvOutHandler OnEnvOut + { + add { currentFlowEnvironment.OnEnvOut += value; } + remove { currentFlowEnvironment.OnEnvOut -= value; } + } + + + + + public void ActivateFlipflopNode(string nodeGuid) + { + currentFlowEnvironment.ActivateFlipflopNode(nodeGuid); + } + + public async Task AddInterruptExpressionAsync(string key, string expression) + { + return await currentFlowEnvironment.AddInterruptExpressionAsync(key, expression); + } + + + public async Task<(bool, string[])> CheckObjMonitorStateAsync(string key) + { + return await currentFlowEnvironment.CheckObjMonitorStateAsync(key); + } + + public void ClearAll() + { + currentFlowEnvironment.ClearAll(); + } + + public async Task ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + { + return await currentFlowEnvironment.ConnectNodeAsync(fromNodeGuid, toNodeGuid, connectionType); + } + + public async Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres, int port, string token) + { + // 连接成功,切换远程环境 + (var isConnect, var remoteEnvControl) = await currentFlowEnvironment.ConnectRemoteEnv(addres, port, token); + if (isConnect) + { + remoteFlowEnvironment ??= new RemoteFlowEnvironment(remoteEnvControl); + currentFlowEnvironment = remoteFlowEnvironment; + } + return (isConnect, remoteEnvControl); + } + + public async Task CreateNodeAsync(NodeControlType nodeBase, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null) + { + SetFlag(false); + var result = await currentFlowEnvironment.CreateNodeAsync(nodeBase, position, methodDetailsInfo); // 装饰器调用 + SetFlag(true); + return result; + } + + + + + public void ExitFlow() + { + currentFlowEnvironment.ExitFlow(); + } + + public void ExitRemoteEnv() + { + currentFlowEnvironment.ExitRemoteEnv(); + } + + + public async Task GetEnvInfoAsync() + { + return await currentFlowEnvironment.GetEnvInfoAsync(); + } + + public async Task GetOrCreateGlobalInterruptAsync() + { + return await currentFlowEnvironment.GetOrCreateGlobalInterruptAsync(); + } + + public async Task GetProjectInfoAsync() + { + return await currentFlowEnvironment.GetProjectInfoAsync(); + } + + + public void LoadDll(string dllPath) + { + currentFlowEnvironment.LoadDll(dllPath); + } + + public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath) + { + if (flowEnvInfo is null) return; + SetFlag(false); + currentFlowEnvironment.LoadProject(flowEnvInfo, filePath); + SetFlag(true); + } + + public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType) + { + currentFlowEnvironment.MonitorObjectNotification(nodeGuid, monitorData, sourceType); + } + + public void MoveNode(string nodeGuid, double x, double y) + { + currentFlowEnvironment.MoveNode(nodeGuid, x, y); + } + + public void NodeLocated(string nodeGuid) + { + currentFlowEnvironment.NodeLocated(nodeGuid); + } + + + + public bool RemoteDll(string assemblyFullName) + { + return currentFlowEnvironment.RemoteDll(assemblyFullName); + } + + public void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + { + currentFlowEnvironment.RemoveConnect(fromNodeGuid, toNodeGuid, connectionType); + } + + public void RemoveNode(string nodeGuid) + { + currentFlowEnvironment.RemoveNode(nodeGuid); + } + + + + public void SetConsoleOut() + { + currentFlowEnvironment.SetConsoleOut(); + } + + public void SetMonitorObjState(string key, bool isMonitor) + { + currentFlowEnvironment.SetMonitorObjState(key, isMonitor); + } + + public async Task SetNodeInterruptAsync(string nodeGuid, InterruptClass interruptClass) + { + return await currentFlowEnvironment.SetNodeInterruptAsync(nodeGuid, interruptClass); + } + + public void SetStartNode(string nodeGuid) + { + currentFlowEnvironment.SetStartNode(nodeGuid); + } + + public async Task StartAsync() + { + await currentFlowEnvironment.StartAsync(); + } + + public async Task StartAsyncInSelectNode(string startNodeGuid) + { + await currentFlowEnvironment.StartAsyncInSelectNode(startNodeGuid); + } + + public async Task StartRemoteServerAsync(int port = 7525) + { + await currentFlowEnvironment.StartRemoteServerAsync(port); + } + + public void StopRemoteServer() + { + currentFlowEnvironment.StopRemoteServer(); + } + + public void TerminateFlipflopNode(string nodeGuid) + { + currentFlowEnvironment.TerminateFlipflopNode(nodeGuid); + } + + public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type) + { + currentFlowEnvironment.TriggerInterrupt(nodeGuid, expression, type); + } + + public bool TryGetDelegateDetails(string methodName, out DelegateDetails del) + { + return currentFlowEnvironment.TryGetDelegateDetails(methodName, out del); + } + + public bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo) + { + return currentFlowEnvironment.TryGetMethodDetailsInfo(methodName, out mdInfo); + } + + public void WriteLineObjToJson(object obj) + { + currentFlowEnvironment.WriteLineObjToJson(obj); + } + + + public async Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value) + { + if (!IsFlagSet()) + { + return; + } + await currentFlowEnvironment.NotificationNodeValueChangeAsync(nodeGuid, path, value); + } + + + + + #region IOC容器 + public ISereinIOC Build() + { + return IOC.Build(); + } + + public bool CustomRegisterInstance(string key, object instance, bool needInjectProperty = true) + { + return IOC.CustomRegisterInstance(key, instance, needInjectProperty); + } + + public object Get(Type type) + { + return IOC.Get(type); + } + + public T Get() + { + return IOC.Get(); + } + + public T Get(string key) + { + return IOC.Get(key); + } + + public object Instantiate(Type type) + { + return IOC.Instantiate(type); + } + + public T Instantiate() + { + return IOC.Instantiate(); + } + + public ISereinIOC Register(Type type, params object[] parameters) + { + return IOC.Register(type, parameters); + } + + public ISereinIOC Register(params object[] parameters) + { + return IOC.Register(parameters); + } + + public ISereinIOC Register(params object[] parameters) where TImplementation : TService + { + return IOC.Register(parameters); + } + + public ISereinIOC Reset() + { + return IOC.Reset(); + } + + public ISereinIOC Run(Action action) + { + return IOC.Run(action); + } + + public ISereinIOC Run(Action action) + { + return IOC.Run(action); + } + + public ISereinIOC Run(Action action) + { + return IOC.Run(action); + } + + public ISereinIOC Run(Action action) + { + return IOC.Run(action); + } + + public ISereinIOC Run(Action action) + { + return IOC.Run(action); + } + + public ISereinIOC Run(Action action) + { + return IOC.Run(action); + } + + public ISereinIOC Run(Action action) + { + return IOC.Run(action); + } + + public ISereinIOC Run(Action action) + { + return IOC.Run(action); + } + + #endregion + + + } +} diff --git a/NodeFlow/Env/FlowFunc.cs b/NodeFlow/Env/FlowFunc.cs new file mode 100644 index 0000000..cda1add --- /dev/null +++ b/NodeFlow/Env/FlowFunc.cs @@ -0,0 +1,187 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.NodeFlow.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Env +{ + + /// + /// 流程环境需要的扩展方法 + /// + public static class FlowFunc + { + + + /// + /// 创建节点 + /// + /// 运行环境 + /// 节点类型 + /// 方法描述 + /// + /// + public static NodeModelBase CreateNode(IFlowEnvironment env, NodeControlType nodeControlType, + MethodDetails? methodDetails = null) + { + // 确定创建的节点类型 + Type? nodeType = nodeControlType switch + { + NodeControlType.Action => typeof(SingleActionNode), + NodeControlType.Flipflop => typeof(SingleFlipflopNode), + + NodeControlType.ExpOp => typeof(SingleExpOpNode), + NodeControlType.ExpCondition => typeof(SingleConditionNode), + NodeControlType.ConditionRegion => typeof(CompositeConditionNode), + _ => null + }; + + if (nodeType is null) + { + throw new Exception($"节点类型错误[{nodeControlType}]"); + } + // 生成实例 + var nodeObj = Activator.CreateInstance(nodeType, env); + if (nodeObj is not NodeModelBase nodeModel) + { + throw new Exception($"无法创建目标节点类型的实例[{nodeControlType}]"); + } + + // 配置基础的属性 + nodeModel.ControlType = nodeControlType; + if (methodDetails == null) // 不存在方法描述时,可能是基础节点(表达式节点、条件表达式节点) + { + methodDetails = new MethodDetails(); + } + var md = methodDetails.CloneOfNode(nodeModel.Env, nodeModel); + nodeModel.DisplayName = md.MethodTips; + nodeModel.MethodDetails = md; + + + return nodeModel; + } + + + /// + /// 从节点信息读取节点类型 + /// + /// + /// + /// + public static NodeControlType GetNodeControlType(NodeInfo nodeInfo) + { + // 创建控件实例 + NodeControlType controlType = nodeInfo.Type switch + { + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件 + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件 + + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件 + $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件 + + $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件 + _ => NodeControlType.None, + }; + + return controlType; + } + + /// + /// 程序集封装依赖 + /// + /// + /// + public static Library.Library ToLibrary(this Library.NodeLibrary library) + { + var tmp = library.Assembly.ManifestModule.Name; + return new Library.Library + { + AssemblyName = library.Assembly.GetName().Name, + FileName = library.FileName, + FilePath = library.FilePath, + }; + } + + /// + /// 触发器运行后状态转为对应的后继分支类别 + /// + /// + /// + /// + public static ConnectionType ToContentType(this FlipflopStateType flowStateType) + { + return flowStateType switch + { + FlipflopStateType.Succeed => ConnectionType.IsSucceed, + FlipflopStateType.Fail => ConnectionType.IsFail, + FlipflopStateType.Error => ConnectionType.IsError, + FlipflopStateType.Cancel => ConnectionType.None, + _ => throw new NotImplementedException("未定义的流程状态") + }; + } + + /// + /// 判断 触发器节点 是否存在上游分支 + /// + /// + /// + public static bool NotExitPreviousNode(this SingleFlipflopNode node) + { + ConnectionType[] ct = [ConnectionType.IsSucceed, + ConnectionType.IsFail, + ConnectionType.IsError, + ConnectionType.Upstream]; + foreach (ConnectionType ctType in ct) + { + if (node.PreviousNodes[ctType].Count > 0) + { + return false; + } + } + return true; + } + + + ///// + ///// 从节点类型枚举中转为对应的 Model 类型 + ///// + ///// + ///// + //public static Type? ControlTypeToModel(this NodeControlType nodeControlType) + //{ + // // 确定创建的节点类型 + // Type? nodeType = nodeControlType switch + // { + // NodeControlType.Action => typeof(SingleActionNode), + // NodeControlType.Flipflop => typeof(SingleFlipflopNode), + + // NodeControlType.ExpOp => typeof(SingleExpOpNode), + // NodeControlType.ExpCondition => typeof(SingleConditionNode), + // NodeControlType.ConditionRegion => typeof(CompositeConditionNode), + // _ => null + // }; + // return nodeType; + //} + //public static NodeControlType ModelToControlType(this NodeControlType nodeControlType) + //{ + // var type = nodeControlType.GetType(); + // NodeControlType controlType = type switch + // { + // Type when type == typeof(SingleActionNode) => NodeControlType.Action, + // Type when type == typeof(SingleFlipflopNode) => NodeControlType.Flipflop, + + // Type when type == typeof(SingleExpOpNode) => NodeControlType.ExpOp, + // Type when type == typeof(SingleConditionNode) => NodeControlType.ExpCondition, + // Type when type == typeof(CompositeConditionNode) => NodeControlType.ConditionRegion, + // _ => NodeControlType.None, + // }; + // return controlType; + //} + } + +} diff --git a/NodeFlow/Env/MsgControllerOfClient.cs b/NodeFlow/Env/MsgControllerOfClient.cs new file mode 100644 index 0000000..b035cce --- /dev/null +++ b/NodeFlow/Env/MsgControllerOfClient.cs @@ -0,0 +1,129 @@ +using Serein.Library; +using Serein.Library.Network.WebSocketCommunication; +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Env +{ + + + + /// + /// 客户端的消息管理(用于处理服务端的响应) + /// + + [AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey, DataKey = FlowEnvironment.DataKey)] + public class MsgControllerOfClient : ISocketHandleModule + { + public Guid HandleGuid => new Guid(); + private readonly Func SendCommandAsync; + private readonly RemoteFlowEnvironment remoteFlowEnvironment; + + public MsgControllerOfClient(RemoteFlowEnvironment remoteFlowEnvironment, Func func) + { + this.remoteFlowEnvironment = remoteFlowEnvironment; + SendCommandAsync = func; + } + + + /// + /// 发送请求并等待远程环境响应 + /// + /// + /// 超时触发 + public async Task SendAsync(string signal, object? senddata = null, int debounceTimeInMs = 100) + { + if (!DebounceHelper.CanExecute(signal, debounceTimeInMs)) + { + return; + } + await SendCommandAsync.Invoke(signal, senddata); + } + + /// + /// 发送请求并等待远程环境响应 + /// + /// + /// 超时触发 + public async Task SendAndWaitDataAsync(string signal, object? senddata = null, int debounceTimeInMs = 50) + { + _ = SendCommandAsync.Invoke(signal, senddata); + return await remoteFlowEnvironment.WaitData(signal); +#if DEBUG + + if (DebounceHelper.CanExecute(signal, debounceTimeInMs)) + { + _ = SendCommandAsync.Invoke(signal, senddata); + return await remoteFlowEnvironment.WaitData(signal); + + //(var type, var result) = await remoteFlowEnvironment.WaitDataWithTimeoutAsync(signal, TimeSpan.FromSeconds(150)); + //if (type == TriggerType.Overtime) + //{ + // throw new NotImplementedException("超时触发"); + //} + //else + //{ + // return result; + //} + } + else + { + return default; + } +#endif + + } + + + #region 消息接收 + + /// + /// 远程环境发来项目信息 + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.GetEnvInfo)] + public void GetEnvInfo([UseMsgData] FlowEnvInfo flowEnvInfo) + { + remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.GetEnvInfo, flowEnvInfo); + } + + + [AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateNode)] + public void AddInterruptExpression([UseMsgData] NodeInfo nodeInfo) + { + remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.CreateNode, nodeInfo); + } + + + /// + /// 远程环境发来项目信息 + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)] + public void GetProjectInfo([UseMsgData] SereinProjectData sereinProjectData) + { + remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.GetProjectInfo, sereinProjectData); + } + + [AutoSocketHandle(ThemeValue = EnvMsgTheme.SetNodeInterrupt)] + public void SetNodeInterrupt() + { + remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.GetProjectInfo, null); + } + + [AutoSocketHandle(ThemeValue = EnvMsgTheme.AddInterruptExpression)] + public void AddInterruptExpression() + { + remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.AddInterruptExpression, null); + } + + + #endregion + + } + +} diff --git a/NodeFlow/Env/MsgControllerOfServer.cs b/NodeFlow/Env/MsgControllerOfServer.cs new file mode 100644 index 0000000..72c18e4 --- /dev/null +++ b/NodeFlow/Env/MsgControllerOfServer.cs @@ -0,0 +1,443 @@ +using Newtonsoft.Json.Linq; +using Serein.Library.Api; +using Serein.Library; +using Serein.Library.Network.WebSocketCommunication; +using Serein.Library.Network.WebSocketCommunication.Handle; +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow.Env +{ + /// + /// 服务端的消息管理(用于处理客户端的请求) + /// + [AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey, DataKey = FlowEnvironment.DataKey)] + public class MsgControllerOfServer : ISocketHandleModule + { + /// + /// 受控环境 + /// + public IFlowEnvironment environment; + + /// + /// WebSocket处理 + /// + public Guid HandleGuid { get; } = new Guid(); + + /// + /// 表示是否正在控制远程 + /// Local control remote env + /// + public bool IsLcR { get; set; } + /// + /// 表示是否受到远程控制 + /// Remote control local env + /// + public bool IsRcL { get; set; } + + + /// + /// 流程环境远程管理服务 + /// + private WebSocketServer FlowEnvRemoteWebSocket; + + + /// + /// 启动不带Token验证的远程服务 + /// + public MsgControllerOfServer(IFlowEnvironment environment) + { + this.environment = environment; + FlowEnvRemoteWebSocket ??= new WebSocketServer(); + } + + /// + /// 启动带token验证的远程服务 + /// + /// + public MsgControllerOfServer(IFlowEnvironment environment, string token) + { + if (string.IsNullOrEmpty(token)) + { + Console.WriteLine("当前没有设置token,但使用了token验证的服务端"); + + } + this.environment = environment; + FlowEnvRemoteWebSocket ??= new WebSocketServer(token, OnInspectionAuthorized); + + } + + + #region 基本方法 + /// + /// 启动远程 + /// + /// + /// + public async Task StartRemoteServerAsync(int port = 7525) + { + + FlowEnvRemoteWebSocket.MsgHandleHelper.AddModule(this, + (ex, send) => + { + send(new + { + code = 400, + ex = ex.Message + }); + }); + var url = $"http://*:{port}/"; + try + { + await FlowEnvRemoteWebSocket.StartAsync(url); + } + catch (Exception ex) + { + FlowEnvRemoteWebSocket.MsgHandleHelper.RemoveModule(this); + Console.WriteLine("打开远程管理异常:" + ex); + } + } + + /// + /// 结束远程管理 + /// + [AutoSocketHandle] + public void StopRemoteServer() + { + try + { + FlowEnvRemoteWebSocket.Stop(); + } + catch (Exception ex) + { + Console.WriteLine("结束远程管理异常:" + ex); + } + } + + /// + /// 验证远程token + /// + /// + /// + private async Task OnInspectionAuthorized(dynamic token) + { + if (IsLcR) + { + return false; // 正在远程控制远程环境时,禁止其它客户端远程控制 + } + if (IsRcL) + { + return false; // 正在受到远程控制时,禁止其它客户端远程控制 + } + await Task.Delay(0); + var tokenValue = token.ToString(); + if ("123456".Equals(tokenValue)) + { + // 同时切换远程环境 + return true; + } + else + { + return false; + } + } + + /// + /// 获取发送消息的委托 + /// + /// + private void OnResultSendMsgFunc(Func SendAsync) + { + // 从受控环境向主控环境发送消息。 + Func func = async (theme, data) => + { + JObject sendJson = new JObject + { + [FlowEnvironment.ThemeKey] = theme, + [FlowEnvironment.DataKey] = JObject.FromObject(data), + }; + var msg = sendJson.ToString(); + await SendAsync(msg); + }; + + // var remoteEnv = new RemoteFlowEnvironment(func); // 创建一个远程环境 + // OnSwitchedEnvironment.Invoke(remoteEnv); // 通知前台切换到了远程环境 + } + #endregion + + /// + /// 异步运行 + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.StartFlow)] + private async Task StartAsync() + { + var uiContextOperation = environment.IOC.Get(); + await environment.StartAsync(); + } + + /// + /// 从远程环境运行选定的节点 + /// + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.StartFlowInSelectNode)] + private async Task StartAsyncInSelectNode(string startNodeGuid) + { + await environment.StartAsyncInSelectNode(startNodeGuid); + } + /// + /// 结束流程 + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.ExitFlow)] + private void ExitFlow() + { + environment.ExitFlow(); + + } + + /// + /// 激活全局触发器 + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.ActivateFlipflopNode)] + private void ActivateFlipflopNode(string nodeGuid) + { + environment.ActivateFlipflopNode(nodeGuid); + } + + + /// + /// 关闭全局触发器 + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.TerminateFlipflopNode)] + private void TerminateFlipflopNode(string nodeGuid) + { + environment.TerminateFlipflopNode(nodeGuid); + } + + + /// + /// 获取当前环境信息(远程连接) + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.GetEnvInfo)] + private async Task GetEnvInfoAsync() + { + return await environment.GetEnvInfoAsync(); + } + + /// + /// 加载项目文件 + /// + /// 环境信息 + // [AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)] + private void LoadProject(FlowEnvInfo flowEnvInfo) + { + environment.LoadProject(flowEnvInfo, ""); + } + + + /// + /// 连接远程环境 + /// + /// 远程环境地址 + /// 远程环境端口 + /// 密码 + // [AutoSocketHandle] + public async Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres, int port, string token) + { + return await environment.ConnectRemoteEnv(addres, port, token); + } + + + + /// + /// 退出远程环境 + /// + // [AutoSocketHandle] + public void ExitRemoteEnv() + { + Console.WriteLine("暂未实现远程退出远程环境"); + IsLcR = false; + } + + + /// + /// 序列化当前项目的依赖信息、节点信息 + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)] + public async Task GetProjectInfoAsync() + { + return await environment.GetProjectInfoAsync(); + } + + /// + /// 从文件路径中加载DLL + /// + /// + /// + // [AutoSocketHandle(ThemeValue = EnvMsgTheme)] + public void LoadDll(string dllPath) + { + } + /// + /// 移除DLL + /// + /// + /// + // [AutoSocketHandle(ThemeValue = EnvMsgTheme)] + public bool RemoteDll(string assemblyFullName) + { + return false; + } + + /// + /// 从远程环境创建节点 + /// + /// + /// + /// 如果是表达式节点条件节点,该项为null + [AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateNode,ArgNotNull = false)] + public async Task CreateNode([Needful] string nodeType, [Needful] PositionOfUI position, MethodDetailsInfo? mdInfo = null) + { + if (!EnumHelper.TryConvertEnum(nodeType, out var nodeControlType)) + { + return null; + } + var nodeInfo = await environment.CreateNodeAsync(nodeControlType, position, mdInfo); // 监听到客户端创建节点的请求 + return nodeInfo; + } + /// + /// 从远程环境移除节点 + /// + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveNode)] + public void RemoveNode(string nodeGuid) + { + environment.RemoveNode(nodeGuid); + } + + + /// + /// 连接节点 + /// + /// 起始节点 + /// 目标节点 + /// 连接关系 + [AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectNode)] + public void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + { + environment.ConnectNodeAsync(fromNodeGuid, toNodeGuid, connectionType); + } + + /// + /// 移除连接关系 + /// + /// 起始节点Guid + /// 目标节点Guid + /// 连接关系 + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveConnect)] + public void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + { + environment.RemoveConnect(fromNodeGuid, toNodeGuid, connectionType); + } + + /// + /// 移动了某个节点(远程插件使用) + /// + /// + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.MoveNode)] + public void MoveNode(string nodeGuid, double x, double y) + { + environment.MoveNode(nodeGuid, x, y); + } + + /// + /// 设置起点控件 + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.SetStartNode)] + public void SetStartNode(string nodeGuid) + { + environment.SetStartNode(nodeGuid); + } + + + + /// + /// 中断指定节点,并指定中断等级。 + /// + /// 被中断的目标节点Guid + /// 中断级别 + /// 操作是否成功 + [AutoSocketHandle(ThemeValue = EnvMsgTheme.SetNodeInterrupt)] + public async Task SetNodeInterruptAsync(string nodeGuid, string interruptClass) + { + + if (!EnumHelper.TryConvertEnum(interruptClass, out var @class)) + { + return false; + } + + return await this.environment.SetNodeInterruptAsync(nodeGuid, @class); + + } + + + + /// + /// 添加表达式中断 + /// + /// 如果是节点,传入Guid;如果是对象,传入类型FullName + /// 合法的条件表达式 + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.AddInterruptExpression)] + public async Task AddInterruptExpression(string key, string expression) + { + return await environment.AddInterruptExpressionAsync(key, expression); + } + /// + /// 设置对象的监视状态 + /// + /// 如果是节点,传入Guid;如果是对象,传入类型FullName + /// ture监视对象;false取消对象监视 + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.SetMonitor)] + public void SetMonitorObjState(string key, bool isMonitor) + { + environment.SetMonitorObjState(key, isMonitor); + } + + /// + /// 节点数据更改 + /// + /// + /// + /// + [AutoSocketHandle(ThemeValue = EnvMsgTheme.ValueNotification)] + public async Task ValueNotification(string nodeGuid, string path, string value) + { + await environment.NotificationNodeValueChangeAsync(nodeGuid, path, value); + } + + + + + + + } + + +} diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs new file mode 100644 index 0000000..17d6581 --- /dev/null +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -0,0 +1,578 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.Utils; +using Serein.NodeFlow.Tool; +using System.Collections.Concurrent; +using System.Threading; +using System.Xml.Linq; + +namespace Serein.NodeFlow.Env +{ + + + /// + /// 远程流程环境 + /// + public class RemoteFlowEnvironment : ChannelFlowTrigger, IFlowEnvironment + { + /// + /// 连接到远程环境后切换到的环境接口实现 + /// + /// 连接到远程环境的客户端 + public RemoteFlowEnvironment(RemoteEnvControl RemoteEnvControl) + { + remoteEnvControl = RemoteEnvControl; + msgClient = new MsgControllerOfClient(this, RemoteEnvControl.SendAsync); + RemoteEnvControl.EnvClient.MsgHandleHelper.AddModule(msgClient, (ex, send) => + { + Console.WriteLine(ex); + }); + } + + //private readonly Func SendCommandAsync; + private readonly RemoteEnvControl remoteEnvControl; + private readonly MsgControllerOfClient msgClient; + private readonly ConcurrentDictionary MethodDetailss = []; + + + + + /// + /// 环境加载的节点集合 + /// Node Guid - Node Model + /// + private Dictionary Nodes { get; } = []; + + public event LoadDllHandler OnDllLoad; + public event ProjectLoadedHandler OnProjectLoaded; + public event NodeConnectChangeHandler OnNodeConnectChange; + public event NodeCreateHandler OnNodeCreate; + public event NodeRemoteHandler OnNodeRemote; + public event StartNodeChangeHandler OnStartNodeChange; + public event FlowRunCompleteHandler OnFlowRunComplete; + public event MonitorObjectChangeHandler OnMonitorObjectChange; + public event NodeInterruptStateChangeHandler OnNodeInterruptStateChange; + public event ExpInterruptTriggerHandler OnInterruptTrigger; + public event IOCMembersChangedHandler OnIOCMembersChanged; + public event NodeLocatedHandler OnNodeLocated; + public event NodeMovedHandler OnNodeMoved; + public event EnvOutHandler OnEnvOut; + + public ISereinIOC IOC => throw new NotImplementedException(); + + public string EnvName => FlowEnvironment.SpaceName; + + public bool IsGlobalInterrupt => false; + + public bool IsLcR => true; + + public bool IsRcL => false; + + public RunState FlowState { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public RunState FlipFlopState { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public IFlowEnvironment CurrentEnv => this; + + public void SetConsoleOut() + { + var logTextWriter = new LogTextWriter(msg => + { + OnEnvOut?.Invoke(msg); + }); + Console.SetOut(logTextWriter); + } + + public void WriteLineObjToJson(object obj) + { + Console.WriteLine("远程环境尚未实现的接口:WriteLineObjToJson"); + } + + public async Task StartRemoteServerAsync(int port = 7525) + { + await Console.Out.WriteLineAsync("远程环境尚未实现的接口:StartRemoteServerAsync"); + } + + public void StopRemoteServer() + { + Console.WriteLine("远程环境尚未实现的接口:StopRemoteServer"); + } + + public async Task GetProjectInfoAsync() + { + var prjectInfo = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.GetProjectInfo); // 等待服务器返回项目信息 + return prjectInfo; + } + + public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath) + { + Console.WriteLine("远程环境尚未实现的接口:LoadProject"); + + + // dll面板 + var libmds = flowEnvInfo.LibraryMds; + foreach (var lib in libmds) + { + NodeLibrary nodeLibrary = new NodeLibrary + { + FullName = lib.LibraryName, + FilePath = "Remote", + }; + var mdInfos = lib.Mds.ToList(); + OnDllLoad?.Invoke(new LoadDllEventArgs(nodeLibrary, mdInfos)); // 通知UI创建dll面板显示 + + foreach (var mdInfo in mdInfos) + { + MethodDetailss.TryAdd(mdInfo.MethodName, new MethodDetails(mdInfo)); // 从DLL读取时生成元数据 + } + } + //flowSemaphore. + + var projectData = flowEnvInfo.Project; + + + List<(NodeModelBase, string[])> regionChildNodes = new List<(NodeModelBase, string[])>(); + List<(NodeModelBase, PositionOfUI)> ordinaryNodes = new List<(NodeModelBase, PositionOfUI)>(); + + // 加载节点 + foreach (var nodeInfo in projectData.Nodes) + { + var controlType = FlowFunc.GetNodeControlType(nodeInfo); + if (controlType == NodeControlType.None) + { + continue; + } + else + { + + MethodDetails? methodDetails; + MethodDetailss.TryGetValue(nodeInfo.MethodName, out methodDetails);// 尝试获取方法信息 + + var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载远程项目时创建节点 + nodeModel.LoadInfo(nodeInfo); // 创建节点model + + + if (nodeModel is null) + { + nodeInfo.Guid = string.Empty; + continue; + } + TryAddNode(nodeModel); // 加载项目时将节点加载到环境中 + if (nodeInfo.ChildNodeGuids?.Length > 0) + { + regionChildNodes.Add((nodeModel, nodeInfo.ChildNodeGuids)); + OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position)); + } + else + { + ordinaryNodes.Add((nodeModel, nodeInfo.Position)); + } + } + } + + // 加载区域子项 + foreach ((NodeModelBase region, string[] childNodeGuids) item in regionChildNodes) + { + foreach (var childNodeGuid in item.childNodeGuids) + { + Nodes.TryGetValue(childNodeGuid, out NodeModelBase? childNode); + if (childNode is null) + { + // 节点尚未加载 + continue; + } + // 存在节点 + OnNodeCreate?.Invoke(new NodeCreateEventArgs(childNode, true, item.region.Guid)); + } + } + + // 加载节点 + foreach ((NodeModelBase nodeModel, PositionOfUI position) item in ordinaryNodes) + { + bool IsContinue = false; + foreach ((NodeModelBase region, string[] childNodeGuids) item2 in regionChildNodes) + { + foreach (var childNodeGuid in item2.childNodeGuids) + { + if (item.nodeModel.Guid.Equals(childNodeGuid)) + { + IsContinue = true; + } + } + } + if (IsContinue) continue; + OnNodeCreate?.Invoke(new NodeCreateEventArgs(item.nodeModel, item.position)); + } + + + + // 确定节点之间的连接关系 + _ = Task.Run(async () => + { + await Task.Delay(100); + foreach (var nodeInfo in projectData.Nodes) + { + if (!Nodes.TryGetValue(nodeInfo.Guid, out NodeModelBase? fromNode)) + { + // 不存在对应的起始节点 + continue; + } + + + List<(ConnectionType connectionType, string[] guids)> allToNodes = [(ConnectionType.IsSucceed,nodeInfo.TrueNodes), + (ConnectionType.IsFail, nodeInfo.FalseNodes), + (ConnectionType.IsError, nodeInfo.ErrorNodes), + (ConnectionType.Upstream, nodeInfo.UpstreamNodes)]; + + List<(ConnectionType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0) + .Select(info => (info.connectionType, + info.guids.Where(guid => Nodes.ContainsKey(guid)).Select(guid => Nodes[guid]) + .ToArray())) + .ToList(); + // 遍历每种类型的节点分支(四种) + foreach ((ConnectionType connectionType, NodeModelBase[] toNodes) item in fromNodes) + { + // 遍历当前类型分支的节点(确认连接关系) + foreach (var toNode in item.toNodes) + { + OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, + toNode.Guid, + item.connectionType, + NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI创建节点 + + //ConnectNode(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系 + } + } + } + }); + + SetStartNode(projectData.StartNode); + OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs()); + + } + private bool TryAddNode(NodeModelBase nodeModel) + { + nodeModel.Guid ??= Guid.NewGuid().ToString(); + Nodes[nodeModel.Guid] = nodeModel; + + // 如果是触发器,则需要添加到专属集合中 + //if (nodeModel is SingleFlipflopNode flipflopNode) + //{ + // var guid = flipflopNode.Guid; + // if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid))) + // { + // FlipflopNodes.Add(flipflopNode); + // } + //} + return true; + } + + private void ConnectNode(NodeModelBase fromNode, NodeModelBase toNode, ConnectionType connectionType) + { + if (fromNode is null || toNode is null || fromNode == toNode) + { + return; + } + + var ToExistOnFrom = true; + var FromExistInTo = true; + ConnectionType[] ct = [ConnectionType.IsSucceed, + ConnectionType.IsFail, + ConnectionType.IsError, + ConnectionType.Upstream]; + + + foreach (ConnectionType ctType in ct) + { + var FToTo = fromNode.SuccessorNodes[ctType].Where(it => it.Guid.Equals(toNode.Guid)).ToArray(); + var ToOnF = toNode.PreviousNodes[ctType].Where(it => it.Guid.Equals(fromNode.Guid)).ToArray(); + ToExistOnFrom = FToTo.Length > 0; + FromExistInTo = ToOnF.Length > 0; + if (ToExistOnFrom && FromExistInTo) + { + Console.WriteLine("起始节点已与目标节点存在连接"); + + //return; + } + else + { + // 检查是否可能存在异常 + if (!ToExistOnFrom && FromExistInTo) + { + Console.WriteLine("目标节点不是起始节点的子节点,起始节点却是目标节点的父节点"); + return; + } + else if (ToExistOnFrom && !FromExistInTo) + { + // + Console.WriteLine(" 起始节点不是目标节点的父节点,目标节点却是起始节点的子节点"); + return; + } + else // if (!ToExistOnFrom && !FromExistInTo) + { + // 可以正常连接 + } + } + + + fromNode.SuccessorNodes[connectionType].Add(toNode); // 添加到起始节点的子分支 + toNode.PreviousNodes[connectionType].Add(fromNode); // 添加到目标节点的父分支 + OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNode.Guid, + toNode.Guid, + connectionType, + NodeConnectChangeEventArgs.ConnectChangeType.Create)); // 通知UI + } + + + + } + + + + public async Task GetEnvInfoAsync() + { + + var envInfo = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.GetEnvInfo); + + return envInfo; + } + + + + public async Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres, int port, string token) + { + await Console.Out.WriteLineAsync("远程环境尚未实现的接口:ConnectRemoteEnv"); + return (false, null); + } + + public void ExitRemoteEnv() + { + Console.WriteLine("远程环境尚未实现的接口:ExitRemoteEnv"); + } + + public void LoadDll(string dllPath) + { + // 将dll文件发送到远程环境,由远程环境进行加载 + Console.WriteLine("远程环境尚未实现的接口:LoadDll"); + } + + public bool RemoteDll(string assemblyFullName) + { + // 尝试移除远程环境中的加载了的依赖 + Console.WriteLine("远程环境尚未实现的接口:RemoteDll"); + return false; + } + + public void ClearAll() + { + Console.WriteLine("远程环境尚未实现的接口:ClearAll"); + } + + public async Task StartAsync() + { + // 远程环境下不需要UI上下文 + await msgClient.SendAsync(EnvMsgTheme.StartFlow); + } + + public async Task StartAsyncInSelectNode(string startNodeGuid) + { + await msgClient.SendAsync(EnvMsgTheme.StartFlowInSelectNode, new + { + nodeGuid = startNodeGuid + }); + } + + public async void ExitFlow() + { + await msgClient.SendAsync(EnvMsgTheme.ExitFlow, null); + } + + public void MoveNode(string nodeGuid, double x, double y) + { + OnNodeMoved.Invoke(new NodeMovedEventArgs(nodeGuid, x, y)); + _ = msgClient.SendAsync(EnvMsgTheme.MoveNode, + new + { + nodeGuid, + x, + y + }); + } + + public void SetStartNode(string nodeGuid) + { + _ = msgClient.SendAsync(EnvMsgTheme.SetStartNode, new + { + nodeGuid + }); + } + + public async Task ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + { + //_ = RemoteEnv.SendAsync(EnvMsgTheme.ConnectNode, new + //{ + // fromNodeGuid, + // toNodeGuid, + // connectionType = connectionType.ToString(), + //}); + var result = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.ConnectNode, new + { + fromNodeGuid, + toNodeGuid, + connectionType = connectionType.ToString(), + }); + return result; + } + + public async Task CreateNodeAsync(NodeControlType nodeControlType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null) + { + var nodeInfo = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.CreateNode, new + { + nodeType = nodeControlType.ToString(), + position = position, + mdInfo = methodDetailsInfo, + }); + + MethodDetailss.TryGetValue(methodDetailsInfo.MethodName, out var methodDetails);// 加载项目时尝试获取方法信息 + var nodeModel = FlowFunc.CreateNode(this, nodeControlType, methodDetails); // 远程环境下加载节点 + TryAddNode(nodeModel); + nodeModel.LoadInfo(nodeInfo); + + // 通知UI更改 + OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position)); + return nodeInfo; + } + + public void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) + { + _ = msgClient.SendAsync(EnvMsgTheme.RemoveConnect, new + { + fromNodeGuid, + toNodeGuid, + connectionType = connectionType.ToString(), + }); + } + + public void RemoveNode(string nodeGuid) + { + _ = msgClient.SendAsync(EnvMsgTheme.RemoveNode, new + { + nodeGuid + }); + } + + public void ActivateFlipflopNode(string nodeGuid) + { + _ = msgClient.SendAsync(EnvMsgTheme.ActivateFlipflopNode, new + { + nodeGuid + }); + } + + public void TerminateFlipflopNode(string nodeGuid) + { + _ = msgClient.SendAsync(EnvMsgTheme.TerminateFlipflopNode, new + { + nodeGuid + }); + } + + public async Task SetNodeInterruptAsync(string nodeGuid, InterruptClass interruptClass) + { + var state = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.SetNodeInterrupt, // 设置节点中断 + new + { + nodeGuid, + interruptClass = interruptClass.ToString(), + }); + return state; + } + + + public async Task AddInterruptExpressionAsync(string key, string expression) + { + var state = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.AddInterruptExpression, // 设置节点/对象的中断表达式 + new + { + key, + expression, + }); + return state; + } + + public void SetMonitorObjState(string key, bool isMonitor) + { + Console.WriteLine("远程环境尚未实现的接口:SetMonitorObjState"); + } + + public async Task<(bool, string[])> CheckObjMonitorStateAsync(string key) + { + if (string.IsNullOrEmpty(key)) + { + var exps = Array.Empty(); + return (false, exps); + } + else + { + var result = await msgClient.SendAndWaitDataAsync<(bool, string[])>(EnvMsgTheme.SetNodeInterrupt, // 检查并获取节点/对象是否正在监视、以及监视的表达式 + new + { + key, + }); + return result; + } + + } + + + public async Task GetOrCreateGlobalInterruptAsync() + { + await Console.Out.WriteLineAsync("远程环境尚未实现的接口:GetOrCreateGlobalInterruptAsync"); + return ChannelFlowInterrupt.CancelType.Error; + } + + public bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo) + { + Console.WriteLine("远程环境尚未实现的接口:TryGetMethodDetailsInfo"); + mdInfo = null; + return false; + } + + public bool TryGetDelegateDetails(string methodName, out DelegateDetails del) + { + Console.WriteLine("远程环境尚未实现的接口:TryGetDelegateDetails"); + del = null; + return false; + } + + + + public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType) + { + Console.WriteLine("远程环境尚未实现的接口:MonitorObjectNotification"); + + } + + public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type) + { + Console.WriteLine("远程环境尚未实现的接口:TriggerInterrupt"); + } + + public void NodeLocated(string nodeGuid) + { + Console.WriteLine("远程环境尚未实现的接口:NodeLocated"); + } + + public async Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value) + { + + //Console.WriteLine($"通知远程环境修改节点数据:{nodeGuid},name:{path},value:{value}"); + _ = msgClient.SendAsync(EnvMsgTheme.ValueNotification, new + { + nodeGuid = nodeGuid, + path = path, + value = value.ToString(), + }); + + } + } +} diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index 7f18871..98e37f7 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -1,41 +1,24 @@ -using Serein.Library.Api; -using Serein.Library.Attributes; +using Serein.Library; +using Serein.Library.Api; using Serein.Library.Core.NodeFlow; -using Serein.Library.Entity; -using Serein.Library.Enums; -using Serein.Library.Ex; -using Serein.Library.Utils; +using Serein.Library.Network.WebSocketCommunication; using Serein.Library.Web; -using Serein.NodeFlow.Base; +using Serein.Library; +using Serein.NodeFlow.Env; using Serein.NodeFlow.Model; using System.Collections.Concurrent; -using System.ComponentModel.Design; -using System.Runtime.CompilerServices; -using System.Xml.Linq; -using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.NodeFlow { - - - /// /// 流程启动器 /// public class FlowStarter { - - public FlowStarter() - { - } - - - - /// - /// 控制触发器 + /// 控制全局触发器的结束 /// - private CancellationTokenSource _flipFlopCts = null; + private CancellationTokenSource? _flipFlopCts; /// /// 是否停止启动 @@ -45,11 +28,8 @@ namespace Serein.NodeFlow /// /// 结束运行时需要执行的方法 /// - private Func ExitAction { get; set; } = null; - /// - /// 运行的上下文 - /// - private IDynamicContext Context { get; set; } = null; + private Func? ExitAction { get; set; } + private void CheckStartState() { if (IsStopStart) @@ -62,11 +42,17 @@ namespace Serein.NodeFlow /// /// 从选定的节点开始运行 /// + /// /// /// - public async Task StartFlowInSelectNodeAsync(NodeModelBase startNode) + public async Task StartFlowInSelectNodeAsync(IFlowEnvironment env, NodeModelBase startNode) { - if (Context is null) return; + IDynamicContext Context; +#if NET6_0_OR_GREATER + Context = new Serein.Library.Core.NodeFlow.DynamicContext(env); // 从起始节点启动流程时创建上下文 +#else + Context = new Serein.Library.Framework.NodeFlow.DynamicContext(env); +#endif await startNode.StartFlowAsync(Context); // 开始运行时从选定节点开始运行 } @@ -111,7 +97,8 @@ namespace Serein.NodeFlow #region 选择运行环境的上下文 // 判断使用哪一种流程上下文 -#if NET6_0_OR_GREATER + IDynamicContext Context; +#if NET6_0_OR_GREATER Context = new Serein.Library.Core.NodeFlow.DynamicContext(env); // 从起始节点启动流程时创建上下文 #else Context = new Serein.Library.Framework.NodeFlow.DynamicContext(env); @@ -235,6 +222,9 @@ namespace Serein.NodeFlow env.IOC.Run(web => { web?.Stop(); }); + env.IOC.Run(server => { + server?.Stop(); + }); foreach (MethodDetails? md in exitMethods) { @@ -243,7 +233,6 @@ namespace Serein.NodeFlow throw new Exception("不存在对应委托"); } await dd.InvokeAsync(md.ActingInstance, [Context]); - //((Func)dd.EmitDelegate).Invoke(md.ActingInstance, [Context]); } TerminateAllGlobalFlipflop(); diff --git a/NodeFlow/Model/CompositeActionNode.cs b/NodeFlow/Model/CompositeActionNode.cs index a9858ca..369e911 100644 --- a/NodeFlow/Model/CompositeActionNode.cs +++ b/NodeFlow/Model/CompositeActionNode.cs @@ -1,7 +1,5 @@ -using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.Library.Enums; -using Serein.NodeFlow.Base; +using Serein.Library; +using Serein.Library.Api; namespace Serein.NodeFlow.Model { @@ -12,10 +10,12 @@ namespace Serein.NodeFlow.Model public class CompositeActionNode : NodeModelBase { public List ActionNodes; + + /// /// 组合动作节点(用于动作区域) /// - public CompositeActionNode(List actionNodes) + public CompositeActionNode(IFlowEnvironment environment, List actionNodes):base(environment) { ActionNodes = actionNodes; } @@ -30,6 +30,7 @@ namespace Serein.NodeFlow.Model { return []; } + public override NodeInfo? ToInfo() { if (MethodDetails is null) return null; diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index 100a22a..327ebb4 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -1,7 +1,6 @@ -using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.Library.Enums; -using Serein.NodeFlow.Base; +using Serein.Library; +using Serein.Library.Api; + namespace Serein.NodeFlow.Model { @@ -10,6 +9,10 @@ namespace Serein.NodeFlow.Model /// public class CompositeConditionNode : NodeModelBase { + public CompositeConditionNode(IFlowEnvironment environment):base(environment) + { + + } public List ConditionNodes { get; } = []; diff --git a/NodeFlow/Model/SingleActionNode.cs b/NodeFlow/Model/SingleActionNode.cs index a6ea759..92a9487 100644 --- a/NodeFlow/Model/SingleActionNode.cs +++ b/NodeFlow/Model/SingleActionNode.cs @@ -1,6 +1,5 @@ using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.NodeFlow.Base; +using Serein.Library; namespace Serein.NodeFlow.Model { @@ -9,7 +8,10 @@ namespace Serein.NodeFlow.Model /// public class SingleActionNode : NodeModelBase { - + public SingleActionNode(IFlowEnvironment environment):base(environment) + { + + } public override Parameterdata[] GetParameterdatas() { if (base.MethodDetails.ParameterDetailss.Length > 0) diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 1886b06..a4ad8f5 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -1,9 +1,6 @@ - +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.Library.Enums; -using Serein.NodeFlow.Base; -using Serein.NodeFlow.Tool.SereinExpression; +using Serein.Library.Utils.SereinExpression; namespace Serein.NodeFlow.Model { @@ -12,6 +9,10 @@ namespace Serein.NodeFlow.Model /// public class SingleConditionNode : NodeModelBase { + public SingleConditionNode(IFlowEnvironment environment):base(environment) + { + + } /// /// 是否为自定义参数 diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 079e846..d799c42 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -1,9 +1,6 @@ -using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.Library.Enums; -using Serein.NodeFlow.Base; -using Serein.NodeFlow.Tool.SereinExpression; -using System.Text; +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.Utils.SereinExpression; namespace Serein.NodeFlow.Model { @@ -12,6 +9,10 @@ namespace Serein.NodeFlow.Model /// public class SingleExpOpNode : NodeModelBase { + public SingleExpOpNode(IFlowEnvironment environment) : base(environment) + { + + } /// /// 表达式 /// diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index be3b27f..74465ec 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -1,27 +1,28 @@ using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.Library.Enums; -using Serein.Library.Ex; -using Serein.Library.NodeFlow.Tool; +using Serein.Library; using Serein.Library.Utils; -using Serein.NodeFlow.Base; +using Serein.NodeFlow.Env; using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.NodeFlow.Model { - + /// + /// 触发器节点 + /// public class SingleFlipflopNode : NodeModelBase { - //public override async Task Executing(IDynamicContext context) - //public override Task ExecutingAsync(IDynamicContext context) - //{ - // NextOrientation = Library.Enums.ConnectionType.IsError; - // RuningException = new FlipflopException ("无法以非await/async的形式调用触发器"); - // return null; - //} - + public SingleFlipflopNode(IFlowEnvironment environment) : base(environment) + { + + } + /// + /// 执行触发器进行等待触发 + /// + /// + /// + /// public override async Task ExecutingAsync(IDynamicContext context) { #region 执行前中断 @@ -76,10 +77,11 @@ namespace Serein.NodeFlow.Model // flipflopTask?.Dispose(); } } - public static object GetContextValueDynamic(dynamic context) - { - return context.Value; // dynamic 会在运行时处理类型 - } + + /// + /// 获取触发器参数 + /// + /// public override Parameterdata[] GetParameterdatas() { if (base.MethodDetails.ParameterDetailss.Length > 0) diff --git a/NodeFlow/MoveNodeData.cs b/NodeFlow/MoveNodeData.cs index 0996b63..72f2bf5 100644 --- a/NodeFlow/MoveNodeData.cs +++ b/NodeFlow/MoveNodeData.cs @@ -1,17 +1,5 @@ -using Serein.Library.Entity; -using Serein.Library.Enums; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Serein.NodeFlow +namespace Serein.NodeFlow { - public class MoveNodeData - { - public NodeControlType NodeControlType { get; set; } - // public MethodDetails MethodDetails { get; set; } - public MethodDetailsInfo MethodDetailsInfo { get; set; } - } + + } diff --git a/NodeFlow/Serein.NodeFlow.csproj b/NodeFlow/Serein.NodeFlow.csproj index 2ce5c50..5614056 100644 --- a/NodeFlow/Serein.NodeFlow.csproj +++ b/NodeFlow/Serein.NodeFlow.csproj @@ -1,7 +1,7 @@  - 1.0.13 + 1.0.14 net8.0 enable enable diff --git a/WorkBench/tool/LogTextWriter.cs b/NodeFlow/Tool/LogTextWriter.cs similarity index 70% rename from WorkBench/tool/LogTextWriter.cs rename to NodeFlow/Tool/LogTextWriter.cs index 0d054c2..de7e2bc 100644 --- a/WorkBench/tool/LogTextWriter.cs +++ b/NodeFlow/Tool/LogTextWriter.cs @@ -3,31 +3,37 @@ using System.IO; using System.Text; using System.Threading.Channels; -namespace Serein.Workbench.tool +namespace Serein.NodeFlow.Tool { /// - /// 可以捕获类库输出的打印输出 + /// 捕获Console输出 /// public class LogTextWriter : TextWriter { private readonly Action logAction; // 更新日志UI的委托 private readonly StringWriter stringWriter = new(); // 缓存日志内容 private readonly Channel logChannel = Channel.CreateUnbounded(); // 日志管道 - private readonly Action clearTextBoxAction; // 清空日志UI的委托 - private int writeCount = 0; // 写入计数器 - private const int maxWrites = 500; // 写入最大计数 + //private int writeCount = 0; // 写入计数器 + //private const int maxWrites = 500; // 写入最大计数 - public LogTextWriter(Action logAction, Action clearTextBoxAction) + /// + /// 定义输出委托与清除输出内容委托 + /// + /// + public LogTextWriter(Action logAction) { this.logAction = logAction; - this.clearTextBoxAction = clearTextBoxAction; // 异步启动日志处理任务,不阻塞主线程 Task.Run(ProcessLogQueueAsync); } + /// + /// 编码类型 + /// public override Encoding Encoding => Encoding.UTF8; + public override void Write(char value) { stringWriter.Write(value); @@ -54,31 +60,34 @@ namespace Serein.Workbench.tool EnqueueLog(); } - // 将日志加入通道 + /// + /// 将日志加入通道 + /// private void EnqueueLog() { var log = stringWriter.ToString(); stringWriter.GetStringBuilder().Clear(); - if (!logChannel.Writer.TryWrite(log)) { // 如果写入失败(不太可能),则直接丢弃日志或处理 } } - // 异步处理日志队列 + /// + /// 异步处理日志队列 + /// + /// private async Task ProcessLogQueueAsync() { await foreach (var log in logChannel.Reader.ReadAllAsync()) // 异步读取日志通道 { logAction?.Invoke(log); // 执行日志写入到UI的委托 - writeCount++; - if (writeCount >= maxWrites) - { - clearTextBoxAction?.Invoke(); // 清空文本框 - writeCount = 0; // 重置计数器 - } + //writeCount++; + //if (writeCount >= maxWrites) + //{ + // writeCount = 0; // 重置计数器 + //} } } } diff --git a/NodeFlow/Tool/NodeMethodDetailsHelper.cs b/NodeFlow/Tool/NodeMethodDetailsHelper.cs index c5f37d7..98f964a 100644 --- a/NodeFlow/Tool/NodeMethodDetailsHelper.cs +++ b/NodeFlow/Tool/NodeMethodDetailsHelper.cs @@ -1,13 +1,8 @@ using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Core.NodeFlow; -using Serein.Library.Entity; using Serein.Library.Utils; -using System; +using Serein.Library; using System.Collections.Concurrent; -using System.ComponentModel; using System.Reflection; -using System.Text.RegularExpressions; namespace Serein.NodeFlow.Tool; @@ -77,7 +72,7 @@ public static class NodeMethodDetailsHelper Type? returnType; bool isTask = IsGenericTask(method.ReturnType, out var taskResult); - if (attribute.MethodDynamicType == Library.Enums.NodeType.Flipflop) + if (attribute.MethodDynamicType == Library.NodeType.Flipflop) { if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) { @@ -126,7 +121,7 @@ public static class NodeMethodDetailsHelper - var md = new MethodDetails + var md = new MethodDetails() // 从DLL生成方法描述 { ActingInstanceType = type, // ActingInstance = instance, @@ -137,7 +132,7 @@ public static class NodeMethodDetailsHelper ParameterDetailss = explicitDataOfParameters, ReturnType = returnType, }; - var dd = new DelegateDetails( emitMethodType, methodDelegate) ; + var dd = new DelegateDetails(emitMethodType, methodDelegate) ; return (md, dd); } @@ -270,6 +265,7 @@ public static class NodeMethodDetailsHelper /// private static string GetExplicitTypeName(Type type) { + return type switch { Type t when t.IsEnum => "Select", diff --git a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs new file mode 100644 index 0000000..887212c --- /dev/null +++ b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs @@ -0,0 +1,462 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; + + +namespace Serein.Library.MyGenerator +{ + + + /// + /// 一个增量源生成器,用于为带有自定义 MyClassAttribute 特性的类中的字段生成带有自定义 set 行为的属性。 + /// + [Generator] + public class MyPropertyGenerator : IIncrementalGenerator + { + /// + /// 初始化生成器,定义需要执行的生成逻辑。 + /// + /// 增量生成器的上下文,用于注册生成逻辑。 + public void Initialize(IncrementalGeneratorInitializationContext context) + { + /* + CreateSyntaxProvider : 第一个参数用于筛选特定语法节点,第二个参数则用于转换筛选出来的节点。 + SemanticModel : 通过 语义模型 (SemanticModel) 来解析代码中的符号信息,获取类、方法、属性等更具体的类型和特性信息。例如某个特性属于哪个类型。 + AddSource : 生成器的最终目标是生成代码。使用 AddSource 将生成的代码以字符串形式注入到编译过程当中。通常会通过字符串拼接或 StringBuilder 来构建生成的 C# 代码。 + */ + // 通过 SyntaxProvider 查找所有带有任意特性修饰的类声明语法节点 + var classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider( + // 定义要查找的语法节点类型,这里我们只关心类声明 (ClassDeclarationSyntax) 并且它们有至少一个特性 (Attribute) + (node, _) => node is ClassDeclarationSyntax cds && cds.AttributeLists.Count > 0, + + // 提供一个函数来进一步分析这些类,并且只返回带有 MyClassAttribute 特性的类声明 + (tmpContext, _) => + { + var classDeclaration = (ClassDeclarationSyntax)tmpContext.Node; + var semanticModel = tmpContext.SemanticModel; + + + // 检查类的特性列表,看看是否存在 MyClassAttribute + if (classDeclaration.AttributeLists + .SelectMany(attrList => attrList.Attributes) + .Any(attr => semanticModel.GetSymbolInfo(attr).Symbol?.ContainingType.Name == "AutoPropertyAttribute")) + { + var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration); // 获取类的符号 + var classInfo = classSymbol.BuildCacheOfClass(); + + + + return (classDeclaration, classInfo); + } + return (null, null); + }) + // 过滤掉空结果 + .Where(cds => cds.classDeclaration != null); + + // 注册一个源生成任务,使用找到的类生成代码 + context.RegisterSourceOutput(classDeclarations, (sourceProductionContext, result) => + { + + // 获取 MyDataAttribute 中的 Type 参数,可以获取多个,这里为了记录代码获取了第一个 + //var typeArgument = attributeData.ConstructorArguments.FirstOrDefault(); + //var dataType = typeArgument.Value as INamedTypeSymbol; + //Console.WriteLine(dataType); + if (result.classDeclaration is ClassDeclarationSyntax classSyntax) + { + // 获取类的命名空间和类名 + var namespaceName = GetNamespace(classSyntax); + var className = classSyntax.Identifier.Text; + + // 生成属性代码 + var generatedCode = GenerateProperties(classSyntax, result.classInfo, namespaceName, className); + + // 将生成的代码添加为源文件 + sourceProductionContext.AddSource($"{className}.g.cs", SourceText.From(generatedCode, Encoding.UTF8)); + } + }); + } + + + + /// + /// 为给定的类生成带有自定义 set 行为的属性。 + /// + /// + /// 类的语法树节点。 + /// 类所在的命名空间。 + /// 类的名称。 + /// 生成的 C# 属性代码。 + private string GenerateProperties(ClassDeclarationSyntax classSyntax, + Dictionary> classInfo, + string namespaceName, + string className) + { + var sb = new StringBuilder(); + + // 生成命名空间和类的开始部分 + sb.AppendLine($"using System;"); + sb.AppendLine($"using Serein.Library;"); + sb.AppendLine($"using Serein.Library.Api;"); + sb.AppendLine($""); + sb.AppendLine($"namespace {namespaceName}"); + sb.AppendLine("{"); + sb.AppendLine($" public partial class {className}"); + sb.AppendLine(" {"); + + var path = classInfo["AutoPropertyAttribute"]["ValuePath"]; + + // "ParameterDetails"; + // "MethodDetails"; + + + + + try + { + var expInfo = MyAttributeResolver.BuildCacheOfField(classSyntax.Members.OfType()); + foreach (var fieldKV in expInfo) + { + var field = fieldKV.Key; + if (field.IsReadonly()) + { + continue; + } + + var leadingTrivia = field.GetLeadingTrivia().InsertSummaryComment("(此属性由源生成器生成)").ToString(); // 获取注释 + var fieldName = field.Declaration.Variables.First().Identifier.Text; // 获取字段名称 + var fieldType = field.Declaration.Type.ToString(); // 获取字段类型 + var propertyName = field.ToPropertyName(); // 转为合适的属性名称 + var attributeInfo = fieldKV.Value; // 缓存的特性信息 + + //if (!attributeInfo.TryGetValue("PropertyInfo",out var tmp) || tmp.Count == 0) + //{ + // continue; + //} + + + + // 生成 getter / setter + sb.AppendLine(leadingTrivia); + sb.AppendLine($" public {fieldType} {propertyName}"); + sb.AppendLine(" {"); + sb.AppendLine($" get => {fieldName};"); + sb.AppendLine(" set"); + sb.AppendLine(" {"); + sb.AppendLine($" if ({fieldName} != value)"); + sb.AppendLine(" {"); + if (attributeInfo.Search("PropertyInfo", "IsPrint", "true")) // 是否打印 + { + sb.AddCode(5, $"Console.WriteLine({fieldName});"); + } + if (attributeInfo.Search("PropertyInfo", "IsNotification", "true")) // 是否通知 + { + if (classInfo.ExitsPath("MethodDetails")) + { + sb.AddCode(5, $"nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"MethodDetails.\"+nameof({propertyName}), value);"); + } + else if (classInfo.ExitsPath("ParameterDetails")) + { + sb.AddCode(5, "nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"MethodDetails.ParameterDetailss[\"+$\"{Index}\"+\"]." + $"\"+nameof({propertyName}),value);"); + } + + } + sb.AppendLine($" {fieldName} = value;"); + sb.AppendLine(" }"); + sb.AppendLine(" }"); + sb.AppendLine(" }"); + } + } + finally + { + // 生成类的结束部分 + sb.AppendLine(" }"); + sb.AppendLine("}"); + } + + + + return sb.ToString(); // 返回生成的代码 + } + + /// + /// 获取类所在的命名空间。 + /// + /// 类的语法节点。 + /// 命名空间的名称,或者 "GlobalNamespace" 如果没有命名空间声明。 + private string GetNamespace(SyntaxNode classSyntax) + { + // 查找最近的命名空间声明 + var namespaceDeclaration = classSyntax.Ancestors().OfType().FirstOrDefault(); + return namespaceDeclaration?.Name.ToString() ?? "GlobalNamespace"; + } + + + + } + + public static class DocumentationCommentExtensions + { + + + /// + /// 为 XML 文档注释中的 标签插入指定的文本 + /// + /// 语法节点的 LeadingTrivia 或 TrailingTrivia 列表 + /// 要插入的注释文本 + /// 修改后的 Trivia 列表 + public static SyntaxTriviaList InsertSummaryComment(this SyntaxTriviaList triviaList, string comment) + { + var docCommentTrivia = triviaList.FirstOrDefault(trivia => + trivia.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) || + trivia.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia)); + + if (docCommentTrivia.HasStructure) + { + //var structuredTrivia = docCommentTrivia.GetStructure(); + var structuredTrivia = docCommentTrivia.GetStructure() as StructuredTriviaSyntax; + + // 查找 标签 + var summaryNode = structuredTrivia.DescendantNodes() + .OfType() + .FirstOrDefault(e => e.StartTag.Name.LocalName.Text == "summary"); + + if (summaryNode != null) + { + //// 在 标签内插入指定的注释文本 + //var generatorComment = SyntaxFactory.XmlText(comment).WithLeadingTrivia(SyntaxFactory.Whitespace(" ")); + //var updatedSummaryNode = summaryNode.AddContent(generatorComment); + + //// 用新的 标签替换原来的 + //var updatedStructuredTrivia = structuredTrivia.ReplaceNode(summaryNode, updatedSummaryNode); + + //// 用新的注释替换原来的 + //var updatedTrivia = SyntaxFactory.Trivia(updatedStructuredTrivia); + //triviaList = triviaList.Replace(docCommentTrivia, updatedTrivia); + + // 创建 段落注释 + var paraElement = SyntaxFactory.XmlElement( + SyntaxFactory.XmlElementStartTag(SyntaxFactory.XmlName("para")), // 起始标签 + SyntaxFactory.SingletonList( // 内容 + SyntaxFactory.XmlText(comment).WithLeadingTrivia(SyntaxFactory.Whitespace(" ")) + ), + SyntaxFactory.XmlElementEndTag(SyntaxFactory.XmlName("para")) // 结束标签 + ); + + + // 将 插入到 中 + var updatedSummaryNode = summaryNode.AddContent(paraElement); + // 用新的 标签替换原来的 + var updatedStructuredTrivia = structuredTrivia.ReplaceNode(summaryNode, updatedSummaryNode); + + // 用新的注释替换原来的 (确保转换为 StructuredTriviaSyntax 类型) + var updatedTrivia = SyntaxFactory.Trivia(updatedStructuredTrivia); + triviaList = triviaList.Replace(docCommentTrivia, updatedTrivia); + } + } + + return triviaList; + } + } + + + + public static class MyAttributeResolver + { + public static Dictionary> BuildCacheOfClass(this INamedTypeSymbol classSymbol) + { + Dictionary> attributesOfClass = new Dictionary>(); + var tattribute = classSymbol.GetAttributes(); + foreach (var cad in tattribute) + { + var attributeName = cad.AttributeClass?.Name; + if (!attributesOfClass.TryGetValue(attributeName, out var attributeInfo)) + { + attributeInfo = new Dictionary(); + attributesOfClass.Add(attributeName, attributeInfo); + } + + foreach (var cata in cad.NamedArguments) + { + var key = cata.Key; + var value = cata.Value.Value; + if (!attributeInfo.ContainsKey(key)) + { + attributeInfo.Add(key, value); + } + + + //Console.WriteLine("key:" + cata.Key);// 类特性的属性名 + //Console.WriteLine("value:" + cata.Value.Value); // 类特性的属性值 + } + } + return attributesOfClass; + + } + + + + /// + /// 字段名称转换为属性名称 + /// + /// + /// 遵循属性命名规范的新名称 + public static string ToPropertyName(this FieldDeclarationSyntax field) + { + var fieldName = field.Declaration.Variables.First().Identifier.Text; + var propertyName = fieldName.StartsWith("_") ? char.ToUpper(fieldName[1]) + fieldName.Substring(2) : char.ToUpper(fieldName[0]) + fieldName.Substring(1); // 创建属性名称 + return propertyName; + } + /// + /// 判断字段是否为只读 + /// + /// 字段的语法节点 + /// 如果字段是只读的,返回 true;否则返回 false + public static bool IsReadonly(this FieldDeclarationSyntax fieldDeclaration) + { + // 判断字段是否有 readonly 修饰符 + return fieldDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword); + } + + + /// + /// 构建字段的缓存信息 + /// 第1层:字段名称 - 特性集合 + /// 第2层:特性名称 - 特性属性集合 + /// 第3层:特性属性名称 - 对应的字面量 + /// + /// + /// 关于字段的特性缓存信息 + public static Dictionary>> BuildCacheOfField(IEnumerable fieldDeclarationSyntaxes) + { + Dictionary>> FieldData = new Dictionary>>(); + foreach (var field in fieldDeclarationSyntaxes) + { + // 获取字段名称和类型 + var variable = field.Declaration.Variables.First(); + var fieldName = variable.Identifier.Text; + var fieldType = field.Declaration.Type.ToString(); + + var attributeInfo = new Dictionary>(); // 发现一个新字段 + FieldData.Add(field, attributeInfo); + + + var attributes = field.AttributeLists; + foreach (var attributeList in attributes) + { + + // 解析特性参数 + foreach (var attribute in attributeList.Attributes) + { + var attributeName = attribute.Name.ToString(); // 特性名称 + var arguments = attribute.ArgumentList?.Arguments; + if (arguments == null) + { + continue; + } + var attributeValue = new Dictionary(); + attributeInfo.Add(attributeName, attributeValue); // 找到特性 + + + + // 解析命名属性 + foreach (var argument in arguments) + { + // Console.WriteLine($" - Constructor Argument: {argument.ToString()}"); + if (argument is AttributeArgumentSyntax attributeArgument && attributeArgument.NameEquals != null) + { + var propertyName = attributeArgument.NameEquals.Name.ToString(); + var propertyValue = attributeArgument.Expression.ToString(); + attributeValue.Add(propertyName, propertyValue); // 记录属性 + } + } + } + } + } + return FieldData; + } + + /// + /// 通过条件检查缓存的信息,决定是否添加代码 + /// 首先检查是否存在该特性,如果不存在,返回 false。 + /// 然后检查是否存在属性,如果不存在,返回 false + /// 如果存在属性,则返回属性对应的值与 comparisonValue 进行比较,返回 + /// + /// 若只传入 attributeName 参数,则只会检查是否存在该特性 + /// 若只传入 attributeName与attributePropertyName 参数,则只会检查是否存在该特性的该属性 + /// + /// 缓存的特性信息 + /// 查询的特性名称 + /// 查询的特性属性名称 + /// 比较值 + /// 如果存在查询项,返回 true ,否则返回 false + public static bool Search(this Dictionary> dict, + string attributeName = null, + string attributePropertyName = null, + string comparisonValue = null) + { + if (string.IsNullOrWhiteSpace(attributeName)) + return false; + if (!dict.TryGetValue(attributeName, out var abs)) + return false; + + if (string.IsNullOrWhiteSpace(attributePropertyName)) + return true; + if (!abs.TryGetValue(attributePropertyName, out var absValue)) + return false; + if (string.IsNullOrWhiteSpace(comparisonValue)) + return true; + return absValue.Equals(comparisonValue); + + } + + /// + /// 添加代码 + /// + /// 字符串构建器 + /// 缩进次数(4个空格) + /// 要添加的代码 + /// 字符串构建器本身 + public static StringBuilder AddCode(this StringBuilder sb, + int retractCount = 0, + string code = null) + { + if (!string.IsNullOrWhiteSpace(code)) + { + var retract = new string(' ', retractCount * 4); + sb.AppendLine(retract + code); + } + + return sb; + } + + + public static bool ExitsPath(this Dictionary> classInfo, string valuePath) + { + // var path = classInfo["AutoPropertyAttribute"]["ValuePath"]; + + if (!classInfo.TryGetValue("AutoPropertyAttribute", out var keyValuePairs)) + { + return false; + } + + if (!keyValuePairs.TryGetValue("ValuePath", out var value)) + { + return false; + } + + return value.Equals(valuePath); + } + + + + + } +} diff --git a/Serein.Library.MyGenerator/Properties/launchSettings.json b/Serein.Library.MyGenerator/Properties/launchSettings.json new file mode 100644 index 0000000..93ccb52 --- /dev/null +++ b/Serein.Library.MyGenerator/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "配置文件 2": { + "commandName": "DebugRoslynComponent" + } + } +} \ No newline at end of file diff --git a/Serein.Library.MyGenerator/Serein.Library.MyGenerator.csproj b/Serein.Library.MyGenerator/Serein.Library.MyGenerator.csproj new file mode 100644 index 0000000..f9f1199 --- /dev/null +++ b/Serein.Library.MyGenerator/Serein.Library.MyGenerator.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + true + + + + + + + + diff --git a/SereinFlow.sln b/SereinFlow.sln index 6093d90..7cc108a 100644 --- a/SereinFlow.sln +++ b/SereinFlow.sln @@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Extend.RemoteControl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.FlowStartTool", "FlowStartTool\Serein.FlowStartTool.csproj", "{38D0FA92-5139-4616-A41E-8186AA4C1532}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serein.Library.MyGenerator", "Serein.Library.MyGenerator\Serein.Library.MyGenerator.csproj", "{5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -62,6 +64,10 @@ Global {38D0FA92-5139-4616-A41E-8186AA4C1532}.Debug|Any CPU.Build.0 = Debug|Any CPU {38D0FA92-5139-4616-A41E-8186AA4C1532}.Release|Any CPU.ActiveCfg = Release|Any CPU {38D0FA92-5139-4616-A41E-8186AA4C1532}.Release|Any CPU.Build.0 = Release|Any CPU + {5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index a5769d2..2e8b8b3 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -1,9 +1,7 @@ using Newtonsoft.Json; -using Serein.Library.Entity; -using Serein.NodeFlow; -using Serein.NodeFlow.Tool; -using System.Diagnostics; +using Serein.Library; using System.Windows; +using System.Windows.Threading; namespace Serein.Workbench { @@ -20,6 +18,9 @@ namespace Serein.Workbench public App() { // TestExp(); + + + } protected override void OnExit(ExitEventArgs e) @@ -32,9 +33,11 @@ namespace Serein.Workbench window.Close(); } } - private void Application_Startup(object sender, StartupEventArgs e) { + Application.Current.Dispatcher.Invoke(() => { }); + + // 检查是否传入了参数 if (e.Args.Length == 1) { @@ -63,7 +66,7 @@ namespace Serein.Workbench } #if DEBUG - else if(1== 1) + else if(1 == 1) { //string filePath = @"F:\临时\project\new project.dnf"; diff --git a/WorkBench/LogWindow.xaml.cs b/WorkBench/LogWindow.xaml.cs index 6d5d512..30e730a 100644 --- a/WorkBench/LogWindow.xaml.cs +++ b/WorkBench/LogWindow.xaml.cs @@ -49,12 +49,12 @@ namespace Serein.Workbench // 异步写入日志到文件 // Task.Run(() => File.AppendAllText("log.txt", text)); - + FlushLog(); // 如果日志达到阈值,立即刷新 - if (logBuffer.Length > flushThreshold) - { - FlushLog(); - } + //if (logBuffer.Length > flushThreshold) + //{ + // FlushLog(); + //} } } @@ -75,7 +75,11 @@ namespace Serein.Workbench : logBuffer.ToString(); logBuffer.Remove(0, logContent.Length); // 清空已更新的部分 - LogTextBox.AppendText(logContent); + LogTextBox.Dispatcher.Invoke(() => + { + LogTextBox.AppendText(logContent); + }); + } // 不必每次都修剪日志,当行数超过限制20%时再修剪 diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index afce5eb..2599311 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -1,35 +1,22 @@ using Microsoft.Win32; using Newtonsoft.Json.Linq; +using Serein.Library; using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.Library.Enums; using Serein.Library.Utils; -using Serein.NodeFlow; -using Serein.NodeFlow.Base; -using Serein.NodeFlow.Model; +using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Tool; -using Serein.NodeFlow.Tool.SereinExpression; -using Serein.Workbench.Node; using Serein.Workbench.Node.View; using Serein.Workbench.Node.ViewModel; using Serein.Workbench.Themes; -using Serein.Workbench.tool; -using System; using System.IO; -using System.Reflection; -using System.Text.Json; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; -using System.Windows.Documents; using System.Windows.Input; -using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Animation; -using System.Windows.Media.Media3D; using System.Windows.Shapes; using System.Windows.Threading; -using System.Xml.Linq; using DataObject = System.Windows.DataObject; namespace Serein.Workbench @@ -50,7 +37,6 @@ namespace Serein.Workbench } - /// /// Interaction logic for MainWindow.xaml,第一次用git,不太懂 @@ -60,12 +46,12 @@ namespace Serein.Workbench /// /// 全局捕获Console输出事件,打印在这个窗体里面 /// - private readonly LogWindow logWindow; + private readonly LogWindow LogOutWindow = new LogWindow(); /// - /// 流程运行环境 + /// 流程接口 /// - private IFlowEnvironment FlowEnvironment { get; } + private IFlowEnvironment EnvDecorator { get; } private MainWindowViewModel ViewModel { get; set; } /// @@ -154,18 +140,20 @@ namespace Serein.Workbench private readonly TranslateTransform translateTransform; #endregion + public MainWindow() { InitializeComponent(); - logWindow = InitConsoleOut(); // 重定向 Console 输出 - ViewModel = new MainWindowViewModel(this); - FlowEnvironment = ViewModel.FlowEnvironment; - ViewObjectViewer.FlowEnvironment = FlowEnvironment; - IOCObjectViewer.FlowEnvironment = FlowEnvironment; + EnvDecorator = ViewModel.FlowEnvironment; + ViewObjectViewer.FlowEnvironment = EnvDecorator; + IOCObjectViewer.FlowEnvironment = EnvDecorator; + + //this.FlowEnvironment.SetConsoleOut((msg) => LogOutWindow.AppendText(msg), () => LogOutWindow.Clear()); // 设置输出 InitFlowEnvironmentEvent(); // 配置环境事件 + canvasTransformGroup = new TransformGroup(); scaleTransform = new ScaleTransform(); translateTransform = new TranslateTransform(); @@ -178,65 +166,91 @@ namespace Serein.Workbench if (App.FlowProjectData is not null) { - FlowEnvironment.LoadProject(App.FlowProjectData, App.FileDataPath); // 加载项目 + EnvDecorator.LoadProject(new FlowEnvInfo { Project = App.FlowProjectData }, App.FileDataPath); // 加载项目 } IOCObjectViewer.SelectObj += ViewObjectViewer.LoadObjectInformation; } + /// + /// 初始化环境事件 + /// private void InitFlowEnvironmentEvent() { - FlowEnvironment.OnDllLoad += FlowEnvironment_DllLoadEvent; - // FlowEnvironment.OnLoadNode += FlowEnvironment_NodeLoadEvent; - FlowEnvironment.OnProjectLoaded += FlowEnvironment_OnProjectLoaded; - FlowEnvironment.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent; - FlowEnvironment.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt; - FlowEnvironment.OnNodeCreate += FlowEnvironment_NodeCreateEvent; - FlowEnvironment.OnNodeRemote += FlowEnvironment_NodeRemoteEvent; - FlowEnvironment.OnFlowRunComplete += FlowEnvironment_OnFlowRunComplete; + + // 获取实现类的类型 + var implementationType = EnvDecorator.CurrentEnv.GetType().Name; + EnvDecorator.OnDllLoad += FlowEnvironment_DllLoadEvent; + EnvDecorator.OnProjectLoaded += FlowEnvironment_OnProjectLoaded; + EnvDecorator.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent; + EnvDecorator.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt; + EnvDecorator.OnNodeCreate += FlowEnvironment_NodeCreateEvent; + EnvDecorator.OnNodeRemote += FlowEnvironment_NodeRemoteEvent; + EnvDecorator.OnFlowRunComplete += FlowEnvironment_OnFlowRunComplete; - FlowEnvironment.OnMonitorObjectChange += FlowEnvironment_OnMonitorObjectChange; - FlowEnvironment.OnNodeInterruptStateChange += FlowEnvironment_OnNodeInterruptStateChange; - FlowEnvironment.OnInterruptTrigger += FlowEnvironment_OnInterruptTrigger; - - FlowEnvironment.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChanged; - - FlowEnvironment.OnNodeLocated += FlowEnvironment_OnNodeLocate; - FlowEnvironment.OnNodeMoved += FlowEnvironment_OnNodeMoved; + EnvDecorator.OnMonitorObjectChange += FlowEnvironment_OnMonitorObjectChange; + EnvDecorator.OnNodeInterruptStateChange += FlowEnvironment_OnNodeInterruptStateChange; + EnvDecorator.OnInterruptTrigger += FlowEnvironment_OnInterruptTrigger; + EnvDecorator.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChanged; + + EnvDecorator.OnNodeLocated += FlowEnvironment_OnNodeLocate; + EnvDecorator.OnNodeMoved += FlowEnvironment_OnNodeMoved; + + EnvDecorator.OnEnvOut += FlowEnvironment_OnEnvOut; + this.EnvDecorator.SetConsoleOut(); // 设置输出 } - - - private LogWindow InitConsoleOut() + /// + /// 移除环境事件 + /// + private void ResetFlowEnvironmentEvent() { - var logWindow = new LogWindow(); - //logWindow.Show(); - // 重定向 Console 输出 - var logTextWriter = new LogTextWriter(msg => logWindow.AppendText(msg), () => logWindow.Clear()); ; - Console.SetOut(logTextWriter); - return logWindow; + EnvDecorator.OnDllLoad -= FlowEnvironment_DllLoadEvent; + EnvDecorator.OnProjectLoaded -= FlowEnvironment_OnProjectLoaded; + EnvDecorator.OnStartNodeChange -= FlowEnvironment_StartNodeChangeEvent; + EnvDecorator.OnNodeConnectChange -= FlowEnvironment_NodeConnectChangeEvemt; + EnvDecorator.OnNodeCreate -= FlowEnvironment_NodeCreateEvent; + EnvDecorator.OnNodeRemote -= FlowEnvironment_NodeRemoteEvent; + EnvDecorator.OnFlowRunComplete -= FlowEnvironment_OnFlowRunComplete; + + + EnvDecorator.OnMonitorObjectChange -= FlowEnvironment_OnMonitorObjectChange; + EnvDecorator.OnNodeInterruptStateChange -= FlowEnvironment_OnNodeInterruptStateChange; + EnvDecorator.OnInterruptTrigger -= FlowEnvironment_OnInterruptTrigger; + + EnvDecorator.OnIOCMembersChanged -= FlowEnvironment_OnIOCMembersChanged; + + EnvDecorator.OnNodeLocated -= FlowEnvironment_OnNodeLocate; + EnvDecorator.OnNodeMoved -= FlowEnvironment_OnNodeMoved; + + EnvDecorator.OnEnvOut -= FlowEnvironment_OnEnvOut; + } + + + #region 窗体加载方法 private void Window_Loaded(object sender, RoutedEventArgs e) { } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { - logWindow.Close(); + LogOutWindow.Close(); System.Windows.Application.Current.Shutdown(); } private void Window_ContentRendered(object sender, EventArgs e) { + Console.WriteLine("load project..."); var project = App.FlowProjectData; if (project is null) { return; } - InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Lenght);// 设置画布大小 + InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Height);// 设置画布大小 foreach (var connection in Connections) { connection.Refresh(); @@ -262,6 +276,15 @@ namespace Serein.Workbench #region 运行环境事件 + /// + /// 环境内容输出 + /// + /// + private void FlowEnvironment_OnEnvOut(string value) + { + LogOutWindow.AppendText(value); + } + /// /// 加载完成 /// @@ -278,7 +301,10 @@ namespace Serein.Workbench private void FlowEnvironment_OnFlowRunComplete(FlowEventArgs eventArgs) { Console.WriteLine("-------运行完成---------\r\n"); - IOCObjectViewer.ClearObjItem(); + this.Dispatcher.Invoke(() => + { + IOCObjectViewer.ClearObjItem(); + }); } /// @@ -294,20 +320,25 @@ namespace Serein.Workbench foreach (var methodDetailsInfo in methodDetailss) { - switch (methodDetailsInfo.NodeType) + if(!EnumHelper.TryConvertEnum(methodDetailsInfo.NodeType, out var nodeType)) { - case Library.Enums.NodeType.Action: + continue; + } + switch (nodeType) + { + case Library.NodeType.Action: dllControl.AddAction(methodDetailsInfo); // 添加动作类型到控件 break; - case Library.Enums.NodeType.Flipflop: + case Library.NodeType.Flipflop: dllControl.AddFlipflop(methodDetailsInfo); // 添加触发器方法到控件 break; } + } var menu = new ContextMenu(); menu.Items.Add(CreateMenuItem("卸载", (s,e) => { - if (this.FlowEnvironment.RemoteDll(nodeLibrary.Assembly.FullName)) + if (this.EnvDecorator.RemoteDll(nodeLibrary.FullName)) { DllStackPanel.Children.Remove(dllControl); } @@ -360,6 +391,10 @@ namespace Serein.Workbench ConfigureLineContextMenu(connection); // 设置连接右键事件 Connections.Add(connection); EndConnection(); + connection.Refresh(); + //UpdateConnections(fromNode); + // connection.ArrowPath?.InvalidateVisual(); + // connection.BezierPath?.InvalidateVisual(); }; @@ -415,18 +450,20 @@ namespace Serein.Workbench } } #region 节点树视图 - if (nodeControl is FlipflopNodeControl flipflopControl) // 判断是否为触发器 - { - var node = flipflopControl?.ViewModel?.Node; - if (node is not null) - { - NodeTreeViewer.RemoteGlobalFlipFlop(node); // 从全局触发器树树视图中移除 - } - } + #endregion this.Dispatcher.Invoke(() => { + if (nodeControl is FlipflopNodeControl flipflopControl) // 判断是否为触发器 + { + var node = flipflopControl?.ViewModel?.Node; + if (node is not null) + { + NodeTreeViewer.RemoteGlobalFlipFlop(node); // 从全局触发器树树视图中移除 + } + } + FlowChartCanvas.Children.Remove(nodeControl); NodeControls.Remove(nodeControl.ViewModel.Node.Guid); }); @@ -447,7 +484,7 @@ namespace Serein.Workbench } // MethodDetails methodDetailss = eventArgs.MethodDetailss; - Position position = eventArgs.Position; + PositionOfUI position = eventArgs.Position; // 创建对应控件 NodeControlBase? nodeControl = nodeModelBase.ControlType switch @@ -487,7 +524,7 @@ namespace Serein.Workbench var node = nodeControl?.ViewModel?.Node; if(node is not null) { - NodeTreeViewer.AddGlobalFlipFlop(FlowEnvironment, node); // 新增的触发器节点添加到全局触发器 + NodeTreeViewer.AddGlobalFlipFlop(EnvDecorator, node); // 新增的触发器节点添加到全局触发器 } } #endregion @@ -520,7 +557,7 @@ namespace Serein.Workbench var node = newStartNodeControl?.ViewModel?.Node; if (node is not null) { - NodeTreeViewer.LoadNodeTreeOfStartNode(FlowEnvironment, node); + NodeTreeViewer.LoadNodeTreeOfStartNode(EnvDecorator, node); } }); @@ -541,7 +578,7 @@ namespace Serein.Workbench }; //NodeControlBase nodeControl = GuidToControl(nodeGuid); - ViewObjectViewer.Dispatcher.BeginInvoke(() => { + ViewObjectViewer.Dispatcher.Invoke(() => { if (ViewObjectViewer.MonitorObj is null) // 如果没有加载过对象 { ViewObjectViewer.LoadObjectInformation(monitorKey, eventArgs.NewData); // 加载对象 ViewObjectViewerControl.MonitorType.Obj @@ -566,36 +603,39 @@ namespace Serein.Workbench /// 节点中断状态改变。 /// /// - private void FlowEnvironment_OnNodeInterruptStateChange(NodeInterruptStateChangeEventArgs eventArgs) + private void FlowEnvironment_OnNodeInterruptStateChange(NodeInterruptStateChangeEventArgs eventArgs) { string nodeGuid = eventArgs.NodeGuid; if (!TryGetControl(nodeGuid, out var nodeControl)) return; - if (eventArgs.Class == InterruptClass.None) + this.Dispatcher.Invoke(() => { - nodeControl.ViewModel.IsInterrupt = false; - - } - else - { - nodeControl.ViewModel.IsInterrupt = true; - } - - foreach (var menuItem in nodeControl.ContextMenu.Items) - { - if (menuItem is MenuItem menu) + if (eventArgs.Class == InterruptClass.None) { - if ("取消中断".Equals(menu.Header)) - { - menu.Header = "在此中断"; - } - else if ("在此中断".Equals(menu.Header)) - { - menu.Header = "取消中断"; - } + nodeControl.ViewModel.IsInterrupt = false; } - } + else + { + nodeControl.ViewModel.IsInterrupt = true; + } + + foreach (var menuItem in nodeControl.ContextMenu.Items) + { + if (menuItem is MenuItem menu) + { + if ("取消中断".Equals(menu.Header)) + { + menu.Header = "在此中断"; + } + else if ("在此中断".Equals(menu.Header)) + { + menu.Header = "取消中断"; + } + + } + } + }); } @@ -625,7 +665,10 @@ namespace Serein.Workbench /// private void FlowEnvironment_OnIOCMembersChanged(IOCMembersChangedEventArgs eventArgs) { - IOCObjectViewer.AddDependenciesInstance(eventArgs.Key, eventArgs.Instance); + this.Dispatcher.Invoke(() => + { + IOCObjectViewer.AddDependenciesInstance(eventArgs.Key, eventArgs.Instance); + }); } /// @@ -635,52 +678,56 @@ namespace Serein.Workbench /// private void FlowEnvironment_OnNodeLocate(NodeLocatedEventArgs eventArgs) { - if (!TryGetControl(eventArgs.NodeGuid, out var nodeControl)) return; - //scaleTransform.ScaleX = 1; - //scaleTransform.ScaleY = 1; - // 获取控件在 FlowChartCanvas 上的相对位置 - Rect controlBounds = VisualTreeHelper.GetDescendantBounds(nodeControl); - Point controlPosition = nodeControl.TransformToAncestor(FlowChartCanvas).Transform(new Point(0, 0)); + this.Dispatcher.Invoke(() => + { + if (!TryGetControl(eventArgs.NodeGuid, out var nodeControl)) return; + //scaleTransform.ScaleX = 1; + //scaleTransform.ScaleY = 1; + // 获取控件在 FlowChartCanvas 上的相对位置 + Rect controlBounds = VisualTreeHelper.GetDescendantBounds(nodeControl); + Point controlPosition = nodeControl.TransformToAncestor(FlowChartCanvas).Transform(new Point(0, 0)); - // 获取控件在画布上的中心点 - double controlCenterX = controlPosition.X + controlBounds.Width / 2; - double controlCenterY = controlPosition.Y + controlBounds.Height / 2; + // 获取控件在画布上的中心点 + double controlCenterX = controlPosition.X + controlBounds.Width / 2; + double controlCenterY = controlPosition.Y + controlBounds.Height / 2; + + // 考虑缩放因素计算目标位置的中心点 + double scaledCenterX = controlCenterX * scaleTransform.ScaleX; + double scaledCenterY = controlCenterY * scaleTransform.ScaleY; + + + //// 计算画布的可视区域大小 + //double visibleAreaLeft = scaledCenterX; + //double visibleAreaTop = scaledCenterY; + //double visibleAreaRight = scaledCenterX + FlowChartStackGrid.ActualWidth; + //double visibleAreaBottom = scaledCenterY + FlowChartStackGrid.ActualHeight; + //// 检查控件中心点是否在可视区域内 + //bool isInView = scaledCenterX >= visibleAreaLeft && scaledCenterX <= visibleAreaRight && + // scaledCenterY >= visibleAreaTop && scaledCenterY <= visibleAreaBottom; + + //Console.WriteLine($"isInView :{isInView}"); + + //if (!isInView) + //{ + //} + // 计算平移偏移量,使得控件在可视区域的中心 + double translateX = scaledCenterX - FlowChartStackGrid.ActualWidth / 2; + double translateY = scaledCenterY - FlowChartStackGrid.ActualHeight / 2; + + var translate = this.translateTransform; + // 应用平移变换 + translate.X = 0; + translate.Y = 0; + translate.X -= translateX; + translate.Y -= translateY; + + // 设置RenderTransform以实现移动效果 + TranslateTransform translateTransform = new TranslateTransform(); + nodeControl.RenderTransform = translateTransform; + ElasticAnimation(nodeControl, translateTransform, 4, 1, 0.5); + + }); - // 考虑缩放因素计算目标位置的中心点 - double scaledCenterX = controlCenterX * scaleTransform.ScaleX; - double scaledCenterY = controlCenterY * scaleTransform.ScaleY; - - - //// 计算画布的可视区域大小 - //double visibleAreaLeft = scaledCenterX; - //double visibleAreaTop = scaledCenterY; - //double visibleAreaRight = scaledCenterX + FlowChartStackGrid.ActualWidth; - //double visibleAreaBottom = scaledCenterY + FlowChartStackGrid.ActualHeight; - //// 检查控件中心点是否在可视区域内 - //bool isInView = scaledCenterX >= visibleAreaLeft && scaledCenterX <= visibleAreaRight && - // scaledCenterY >= visibleAreaTop && scaledCenterY <= visibleAreaBottom; - - //Console.WriteLine($"isInView :{isInView}"); - - //if (!isInView) - //{ - //} - // 计算平移偏移量,使得控件在可视区域的中心 - double translateX = scaledCenterX - FlowChartStackGrid.ActualWidth / 2; - double translateY = scaledCenterY - FlowChartStackGrid.ActualHeight / 2; - - var translate = this.translateTransform; - // 应用平移变换 - translate.X = 0; - translate.Y = 0; - translate.X -= translateX; - translate.Y -= translateY; - - // 设置RenderTransform以实现移动效果 - TranslateTransform translateTransform = new TranslateTransform(); - nodeControl.RenderTransform = translateTransform; - ElasticAnimation(nodeControl, translateTransform, 4,1,0.5); - } /// @@ -716,6 +763,11 @@ namespace Serein.Workbench nodeControl.RenderTransform = null; // 或者重新设置为默认值 }; } + + /// + /// 节点移动 + /// + /// private void FlowEnvironment_OnNodeMoved(NodeMovedEventArgs eventArgs) { if (!TryGetControl(eventArgs.NodeGuid, out var nodeControl)) return; @@ -723,16 +775,23 @@ namespace Serein.Workbench var newLeft = eventArgs.X; var newTop = eventArgs.Y; - // 限制控件不超出FlowChartCanvas的边界 - if (newLeft >= 0 && newLeft + nodeControl.ActualWidth <= FlowChartCanvas.ActualWidth) - { - Canvas.SetLeft(nodeControl, newLeft); - } - if (newTop >= 0 && newTop + nodeControl.ActualHeight <= FlowChartCanvas.ActualHeight) - { - Canvas.SetTop(nodeControl, newTop); - } + this.Dispatcher.Invoke(() => { + // 限制控件不超出FlowChartCanvas的边界 + if (newLeft >= 0 && newLeft + nodeControl.ActualWidth <= FlowChartCanvas.ActualWidth) + { + Canvas.SetLeft(nodeControl, newLeft); + } + if (newTop >= 0 && newTop + nodeControl.ActualHeight <= FlowChartCanvas.ActualHeight) + { + Canvas.SetTop(nodeControl, newTop); + + + } + UpdateConnections(nodeControl); + }); + + //Canvas.SetLeft(nodeControl,); //Canvas.SetTop(nodeControl, ); } @@ -773,27 +832,28 @@ namespace Serein.Workbench /// /// /// - private NodeControlBase? CreateNodeControlOfNodeInfo(NodeInfo nodeInfo, MethodDetails methodDetailss) - { - // 创建控件实例 - NodeControlBase nodeControl = nodeInfo.Type switch - { - $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => - CreateNodeControl(methodDetailss),// 动作节点控件 - $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => - CreateNodeControl(methodDetailss), // 触发器节点控件 + //private NodeControlBase? CreateNodeControlOfNodeInfo(NodeInfo nodeInfo, MethodDetails methodDetailss) + //{ + // // 创建控件实例 + // NodeControlBase nodeControl = nodeInfo.Type switch + // { + // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => + // CreateNodeControl(methodDetailss),// 动作节点控件 + // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => + // CreateNodeControl(methodDetailss), // 触发器节点控件 - $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => - CreateNodeControl(), // 条件表达式控件 - $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => - CreateNodeControl(), // 操作表达式控件 + // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => + // CreateNodeControl(), // 条件表达式控件 + // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => + // CreateNodeControl(), // 操作表达式控件 + + // $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => + // CreateNodeControl(), // 条件区域控件 + // _ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.Type}"), + // }; + // return nodeControl; + //} - $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => - CreateNodeControl(), // 条件区域控件 - _ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.Type}"), - }; - return nodeControl; - } /// /// 加载文件时,添加节点到区域中 /// @@ -908,13 +968,13 @@ namespace Serein.Workbench { if (menuItem.Header.ToString() == "启动触发器") { - FlowEnvironment.ActivateFlipflopNode(nodeGuid); + EnvDecorator.ActivateFlipflopNode(nodeGuid); menuItem.Header = "终结触发器"; } else { - FlowEnvironment.TerminateFlipflopNode(nodeGuid); + EnvDecorator.TerminateFlipflopNode(nodeGuid); menuItem.Header = "启动触发器"; } @@ -934,19 +994,19 @@ namespace Serein.Workbench #region 右键菜单功能 - 中断 - contextMenu.Items.Add(CreateMenuItem("在此中断", (s, e) => + contextMenu.Items.Add(CreateMenuItem("在此中断", async (s, e) => { if ((s is MenuItem menuItem) && menuItem is not null) { if (nodeControl?.ViewModel?.Node?.DebugSetting?.InterruptClass == InterruptClass.None) { - FlowEnvironment.SetNodeInterrupt(nodeGuid, InterruptClass.Branch); + await EnvDecorator.SetNodeInterruptAsync(nodeGuid, InterruptClass.Branch); menuItem.Header = "取消中断"; } else { - FlowEnvironment.SetNodeInterrupt(nodeGuid, InterruptClass.None); + await EnvDecorator.SetNodeInterruptAsync(nodeGuid, InterruptClass.None); menuItem.Header = "在此中断"; } @@ -956,8 +1016,8 @@ namespace Serein.Workbench #endregion - contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid))); - contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoveNode(nodeGuid))); + contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => EnvDecorator.SetStartNode(nodeGuid))); + contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => EnvDecorator.RemoveNode(nodeGuid))); contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed))); contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail))); @@ -1034,7 +1094,7 @@ namespace Serein.Workbench // 获取起始节点与终止节点,消除映射关系 var fromNodeGuid = connectionToRemove.Start.ViewModel.Node.Guid; var toNodeGuid = connectionToRemove.End.ViewModel.Node.Guid; - FlowEnvironment.RemoveConnect(fromNodeGuid, toNodeGuid, connection.Type); + EnvDecorator.RemoveConnect(fromNodeGuid, toNodeGuid, connection.Type); } /// @@ -1089,7 +1149,7 @@ namespace Serein.Workbench { if (file.EndsWith(".dll")) { - FlowEnvironment.LoadDll(file); + EnvDecorator.LoadDll(file); } } } @@ -1189,16 +1249,15 @@ namespace Serein.Workbench /// /// /// - private void FlowChartCanvas_Drop(object sender, DragEventArgs e) + private async void FlowChartCanvas_Drop(object sender, DragEventArgs e) { var canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点 - Position position = new Position(canvasDropPosition.X, canvasDropPosition.Y); + PositionOfUI position = new PositionOfUI(canvasDropPosition.X, canvasDropPosition.Y); if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas)) { if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData) { - // 创建DLL文件的节点对象 - FlowEnvironment.CreateNode(nodeData.NodeControlType, position, nodeData.MethodDetailsInfo); + await EnvDecorator.CreateNodeAsync(nodeData.NodeControlType, position, nodeData.MethodDetailsInfo); // 创建DLL文件的节点对象 } } else if (e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas)) @@ -1214,8 +1273,7 @@ namespace Serein.Workbench }; if(nodeControlType != NodeControlType.None) { - // 创建基础节点对象 - FlowEnvironment.CreateNode(nodeControlType, position); + await EnvDecorator.CreateNodeAsync(nodeControlType, position); // 创建基础节点对象 } } } @@ -1228,7 +1286,7 @@ namespace Serein.Workbench /// /// /// - private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, Position position) + private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, PositionOfUI position) { var point = new Point(position.X, position.Y); HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, point); @@ -1337,7 +1395,7 @@ namespace Serein.Workbench var newLeft = oldLeft + deltaX; var newTop = oldTop + deltaY; - this.FlowEnvironment.MoveNode(nodeControlMain.ViewModel.Node.Guid, newLeft, newTop); // 移动节点 + this.EnvDecorator.MoveNode(nodeControlMain.ViewModel.Node.Guid, newLeft, newTop); // 移动节点 // 计算控件实际移动的距离 var actualDeltaX = newLeft - oldLeft; @@ -1350,7 +1408,7 @@ namespace Serein.Workbench { var otherNewLeft = Canvas.GetLeft(nodeControl) + actualDeltaX; var otherNewTop = Canvas.GetTop(nodeControl) + actualDeltaY; - this.FlowEnvironment.MoveNode(nodeControl.ViewModel.Node.Guid, otherNewLeft, otherNewTop); // 移动节点 + this.EnvDecorator.MoveNode(nodeControl.ViewModel.Node.Guid, otherNewLeft, otherNewTop); // 移动节点 } } @@ -1370,7 +1428,7 @@ namespace Serein.Workbench double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量 double newLeft = Canvas.GetLeft(nodeControl) + deltaX; // 新的左边距 double newTop = Canvas.GetTop(nodeControl) + deltaY; // 新的上边距 - this.FlowEnvironment.MoveNode(nodeControl.ViewModel.Node.Guid, newLeft, newTop); // 移动节点 + this.EnvDecorator.MoveNode(nodeControl.ViewModel.Node.Guid, newLeft, newTop); // 移动节点 UpdateConnections(nodeControl); } startControlDragPoint = currentPosition; // 更新起始点位置 @@ -1399,7 +1457,7 @@ namespace Serein.Workbench { if (ViewObjectViewer.MonitorObj is null) { - FlowEnvironment.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI + EnvDecorator.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI return; } if (instance is null) @@ -1413,8 +1471,8 @@ namespace Serein.Workbench } else { - FlowEnvironment.SetMonitorObjState(ViewObjectViewer.MonitorKey,false); // 取消对旧节点的监视 - FlowEnvironment.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI + EnvDecorator.SetMonitorObjState(ViewObjectViewer.MonitorKey,false); // 取消对旧节点的监视 + EnvDecorator.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI } } @@ -1441,7 +1499,7 @@ namespace Serein.Workbench { return; } - FlowEnvironment.ConnectNode(formNodeGuid, toNodeGuid, currentConnectionType); + EnvDecorator.ConnectNodeAsync(formNodeGuid, toNodeGuid, currentConnectionType); } /*else if (IsConnecting) @@ -1859,7 +1917,7 @@ namespace Serein.Workbench var guid = node?.ViewModel?.Node?.Guid; if (!string.IsNullOrEmpty(guid)) { - FlowEnvironment.RemoveNode(guid); + EnvDecorator.RemoveNode(guid); } } } @@ -2231,38 +2289,38 @@ namespace Serein.Workbench } } - private static TControl CreateNodeControl(MethodDetails? methodDetails = null) - where TNode : NodeModelBase - where TControl : NodeControlBase - where TViewModel : NodeControlViewModelBase - { + //private static TControl CreateNodeControl(MethodDetails? methodDetails = null) + // where TNode : NodeModelBase + // where TControl : NodeControlBase + // where TViewModel : NodeControlViewModelBase + //{ - var nodeObj = Activator.CreateInstance(typeof(TNode)); - var nodeBase = nodeObj as NodeModelBase ?? throw new Exception("无法创建节点控件"); + // var nodeObj = Activator.CreateInstance(typeof(TNode)); + // var nodeBase = nodeObj as NodeModelBase ?? throw new Exception("无法创建节点控件"); - if (string.IsNullOrEmpty(nodeBase.Guid)) - { - nodeBase.Guid = Guid.NewGuid().ToString(); - } - if (methodDetails != null) - { - var md = methodDetails.Clone(); - nodeBase.DisplayName = md.MethodTips; - nodeBase.MethodDetails = md; - } + // if (string.IsNullOrEmpty(nodeBase.Guid)) + // { + // nodeBase.Guid = Guid.NewGuid().ToString(); + // } + // if (methodDetails != null) + // { + // var md = methodDetails.Clone(nodeBase); // 首先创建属于节点的方法信息,然后创建属于节点的参数信息 + // nodeBase.DisplayName = md.MethodTips; + // nodeBase.MethodDetails = md; + // } - var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]); - var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]); - if (controlObj is TControl control) - { - return control; - } - else - { - throw new Exception("无法创建节点控件"); - } - } + // var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]); + // var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]); + // if (controlObj is TControl control) + // { + // return control; + // } + // else + // { + // throw new Exception("无法创建节点控件"); + // } + //} /// @@ -2315,7 +2373,7 @@ namespace Serein.Workbench } if (count == 0) { - NodeTreeViewer.AddGlobalFlipFlop(FlowEnvironment, nodeModel); // 添加到全局触发器树树视图 + NodeTreeViewer.AddGlobalFlipFlop(EnvDecorator, nodeModel); // 添加到全局触发器树树视图 } else { @@ -2338,14 +2396,30 @@ namespace Serein.Workbench /// /// /// - private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e) + private void ButtonDebugRun_Click(object sender, RoutedEventArgs e) { - logWindow?.Show(); + LogOutWindow?.Show(); - // await FlowEnvironment.StartAsync(); // 快 - await Task.Run(FlowEnvironment.StartAsync); // 上下文多次切换的场景中慢了1/10,定时器精度丢失 - //await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5,定时器精度丢失 + +#if WINDOWS + //Dispatcher uiDispatcher = Application.Current.MainWindow.Dispatcher; + //SynchronizationContext? uiContext = SynchronizationContext.Current; + //EnvDecorator.IOC.CustomRegisterInstance(typeof(SynchronizationContextk).FullName, uiContext, false); +#endif + + // 获取主线程的 SynchronizationContext + Action uiInvoke = (uiContext, action) => uiContext?.Post(state => action?.Invoke(), null); + + + + Task.Run(async () => + { + await EnvDecorator.StartAsync(); + }); + + // await EnvDecorator.StartAsync(); + //await Task.Factory.StartNew(FlowEnvironment.StartAsync); } /// @@ -2355,7 +2429,7 @@ namespace Serein.Workbench /// private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e) { - FlowEnvironment?.ExitFlow(); // 在运行平台上点击了退出 + EnvDecorator?.ExitFlow(); // 在运行平台上点击了退出 } /// @@ -2375,7 +2449,7 @@ namespace Serein.Workbench } else { - await this.FlowEnvironment.StartAsyncInSelectNode(selectNodeControls[0].ViewModel.Node.Guid); + await this.EnvDecorator.StartAsyncInSelectNode(selectNodeControls[0].ViewModel.Node.Guid); } } @@ -2393,16 +2467,16 @@ namespace Serein.Workbench /// /// /// - private void ButtonSaveFile_Click(object sender, RoutedEventArgs e) + private async void ButtonSaveFile_Click(object sender, RoutedEventArgs e) { - var projectData = FlowEnvironment.GetProjectInfo(); + var projectData = await EnvDecorator.GetProjectInfoAsync(); projectData.Basic = new Basic { Canvas = new FlowCanvas { - Lenght = FlowChartCanvas.Width, - Width = FlowChartCanvas.Height, + Height = FlowChartCanvas.Height, + Width = FlowChartCanvas.Width, ViewX = translateTransform.X, ViewY = translateTransform.Y, ScaleX = scaleTransform.ScaleX, @@ -2411,15 +2485,14 @@ namespace Serein.Workbench Versions = "1", }; - foreach (var node in projectData.Nodes) - { - - 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); - } - } + //foreach (var node in projectData.Nodes) + //{ + // if (NodeControls.TryGetValue(node.Guid, out var nodeControl)) + // { + // Point positionRelativeToParent = nodeControl.TranslatePoint(new Point(0, 0), FlowChartCanvas); + // node.Position = new PositionOfUI(positionRelativeToParent.X, positionRelativeToParent.Y); + // } + //} if (!SaveContentToFile(out string savePath, out Action? savaProjectFile)) { Console.WriteLine("保存项目DLL时返回了意外的文件保存路径"); @@ -2435,22 +2508,25 @@ namespace Serein.Workbench Console.WriteLine(savePath); for (int index = 0; index < projectData.Librarys.Length; index++) { - Library.Entity.Library? library = projectData.Librarys[index]; + Library.Library? library = projectData.Librarys[index]; try { - string targetPath = System.IO.Path.Combine(librarySavePath, System.IO.Path.GetFileName(library.Path)); + string targetPath = System.IO.Path.Combine(librarySavePath, library.FileName); // 目标文件夹 //Console.WriteLine("targetPath:" + targetPath); - - string sourceFile = new Uri(library.Path).LocalPath; +#if WINDOWS + //library.Path + string sourceFile = library.FilePath; // 源文件夹 //Console.WriteLine("sourceFile:" + sourceFile); - +#else + string sourceFile = new Uri(library.Path).LocalPath; +#endif // 复制文件到目标目录 File.Copy(sourceFile, targetPath, true); // 获取相对路径 string relativePath = System.IO.Path.GetRelativePath(savePath, targetPath); //Console.WriteLine("Relative Path: " + relativePath); - projectData.Librarys[index].Path = relativePath; + projectData.Librarys[index].FilePath = relativePath; } catch (Exception ex) { @@ -2499,7 +2575,7 @@ namespace Serein.Workbench - #endregion +#endregion #region 顶部菜单栏 - 视图管理 /// @@ -2516,7 +2592,7 @@ namespace Serein.Workbench } private void ButtonOpenConsoleOutWindow_Click(object sender, RoutedEventArgs e) { - logWindow?.Show(); + LogOutWindow?.Show(); } #endregion @@ -2524,8 +2600,9 @@ namespace Serein.Workbench #region 顶部菜单栏 - 远程管理 private async void ButtonStartRemoteServer_Click(object sender, RoutedEventArgs e) { - await this.FlowEnvironment.StartRemoteServerAsync(); + await this.EnvDecorator.StartRemoteServerAsync(); } + /// /// 连接远程运行环境 /// @@ -2533,9 +2610,17 @@ namespace Serein.Workbench /// private void ButtonConnectionRemoteEnv_Click(object sender, RoutedEventArgs e) { - var windowEnvRemoteLoginView = new WindowEnvRemoteLoginView((addres, port, token) => + var windowEnvRemoteLoginView = new WindowEnvRemoteLoginView(async (addres, port, token) => { - this.FlowEnvironment.LoadRemoteProject(addres, port, token); + ResetFlowEnvironmentEvent();// 移除事件 + (var isConnect, RemoteEnvControl remoteEnvControl) = await this.EnvDecorator.ConnectRemoteEnv(addres, port, token); + InitFlowEnvironmentEvent(); // 重新添加时间(如果没有连接成功,那么依然是原本的环境) + if (isConnect) + { + // 连接成功,加载远程项目 + var flowEnvInfo = await EnvDecorator.GetEnvInfoAsync(); + EnvDecorator.LoadProject(flowEnvInfo, string.Empty);// 加载远程环境的项目 + } }); windowEnvRemoteLoginView.Show(); @@ -2628,7 +2713,7 @@ namespace Serein.Workbench /// private void UnloadAllButton_Click(object sender, RoutedEventArgs e) { - FlowEnvironment.ClearAll(); + EnvDecorator.ClearAll(); } diff --git a/WorkBench/MainWindowViewModel.cs b/WorkBench/MainWindowViewModel.cs index 03c64e0..e9565db 100644 --- a/WorkBench/MainWindowViewModel.cs +++ b/WorkBench/MainWindowViewModel.cs @@ -1,17 +1,6 @@ using Serein.Library.Api; -using Serein.Library.Attributes; -using Serein.Library.Entity; using Serein.Library.Utils; -using Serein.NodeFlow; -using Serein.NodeFlow.Tool; -using Serein.Workbench.Node.View; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; +using Serein.NodeFlow.Env; using System.Windows; namespace Serein.Workbench @@ -34,8 +23,25 @@ namespace Serein.Workbench /// public MainWindowViewModel(MainWindow window) { - FlowEnvironment = new FlowEnvironment(); - this.window = window; + UIContextOperation? uIContextOperation = null; + Application.Current.Dispatcher.Invoke(() => + { + SynchronizationContext? uiContext = SynchronizationContext.Current; // 在UI线程上获取UI线程上下文信息 + if (uiContext != null) + { + uIContextOperation = new UIContextOperation(uiContext); // 封装一个调用UI线程的工具类 + } + }); + + if (uIContextOperation is null) + { + throw new Exception("无法封装 UIContextOperation "); + } + else + { + FlowEnvironment = new FlowEnvironmentDecorator(uIContextOperation); + this.window = window; + } } diff --git a/WorkBench/Node/NodeControlViewModelBase.cs b/WorkBench/Node/NodeControlViewModelBase.cs index 5d0c534..4c61e17 100644 --- a/WorkBench/Node/NodeControlViewModelBase.cs +++ b/WorkBench/Node/NodeControlViewModelBase.cs @@ -1,12 +1,6 @@ -using Serein.Library.Entity; -using Serein.NodeFlow.Base; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; +using System.ComponentModel; +using Serein.Library; using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; namespace Serein.Workbench.Node.ViewModel { @@ -38,7 +32,9 @@ namespace Serein.Workbench.Node.ViewModel } } - + /// + /// 使节点获得中断能力(以及是否启用节点) + /// public NodeDebugSetting DebugSetting { get => Node.DebugSetting; @@ -47,11 +43,14 @@ namespace Serein.Workbench.Node.ViewModel if (value != null) { Node.DebugSetting = value; - OnPropertyChanged(/*nameof(DebugSetting)*/); + OnPropertyChanged(); } } } + /// + /// 使节点能够表达方法信息 + /// public MethodDetails MethodDetails { get => Node.MethodDetails; @@ -60,63 +59,49 @@ namespace Serein.Workbench.Node.ViewModel if(value != null) { Node.MethodDetails = value; - OnPropertyChanged(/*nameof(MethodDetails)*/); + OnPropertyChanged(); } } } private bool isInterrupt; + /// + /// 控制中断状态的视觉效果 + /// public bool IsInterrupt { get => isInterrupt; set { isInterrupt = value; - OnPropertyChanged(/*nameof(IsInterrupt)*/); + OnPropertyChanged(); } } - - //public bool IsInterrupt - //{ - // get => Node.DebugSetting.IsInterrupt; - // set - // { - // if (value) - // { - // Node.Interrupt(); - // } - // else - // { - // Node.CancelInterrupt(); - // } - // OnPropertyChanged(nameof(IsInterrupt)); - // } - //} - - //public bool IsProtectionParameter - //{ - // get => MethodDetails.IsProtectionParameter; - // set - // { - // MethodDetails.IsProtectionParameter = value; - // OnPropertyChanged(nameof(IsInterrupt)); - // } - //} - + /// + /// + /// public event PropertyChangedEventHandler? PropertyChanged; + /// + /// + /// + /// protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - + /// + /// + /// public void Selected() { IsSelect = true; } - + /// + /// + /// public void CancelSelect() { IsSelect = false; diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml index 633c67e..f6762b4 100644 --- a/WorkBench/Node/View/ActionNodeControl.xaml +++ b/WorkBench/Node/View/ActionNodeControl.xaml @@ -7,7 +7,10 @@ xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel" xmlns:Converters="clr-namespace:Serein.Workbench.Tool.Converters" xmlns:themes="clr-namespace:Serein.Workbench.Themes" + d:DataContext="{d:DesignInstance vm:ActionNodeControlViewModel}" + mc:Ignorable="d" MaxWidth="300"> + @@ -15,63 +18,63 @@ - - - - - - - - - + + + + - - - - - - + + + + - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + - - - + + + + + diff --git a/WorkBench/Node/View/ActionRegionControl.xaml.cs b/WorkBench/Node/View/ActionRegionControl.xaml.cs index 7fb733f..a71e5c2 100644 --- a/WorkBench/Node/View/ActionRegionControl.xaml.cs +++ b/WorkBench/Node/View/ActionRegionControl.xaml.cs @@ -1,9 +1,6 @@ -using Serein.NodeFlow; -using Serein.NodeFlow.Model; -using Serein.Workbench.Node.View; +using Serein.Library; using System.Windows; using System.Windows.Controls; -using System.Windows.Controls.Primitives; using System.Windows.Input; namespace Serein.Workbench.Node.View @@ -111,7 +108,7 @@ namespace Serein.Workbench.Node.View { MoveNodeData moveNodeData = new MoveNodeData { - NodeControlType = Library.Enums.NodeControlType.ConditionRegion + NodeControlType = Library.NodeControlType.ConditionRegion }; // 创建一个 DataObject 用于拖拽操作,并设置拖拽效果 diff --git a/WorkBench/Node/View/ConditionNodeControl.xaml b/WorkBench/Node/View/ConditionNodeControl.xaml index c5eb687..727963e 100644 --- a/WorkBench/Node/View/ConditionNodeControl.xaml +++ b/WorkBench/Node/View/ConditionNodeControl.xaml @@ -6,6 +6,8 @@ xmlns:local="clr-namespace:Serein.Workbench.Node.View" xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel" xmlns:themes="clr-namespace:Serein.Workbench.Themes" + d:DataContext="{d:DesignInstance vm:ConditionNodeControlViewModel}" + mc:Ignorable="d" MaxWidth="300"> diff --git a/WorkBench/Node/View/ConditionNodeControl.xaml.cs b/WorkBench/Node/View/ConditionNodeControl.xaml.cs index d327a11..08e56df 100644 --- a/WorkBench/Node/View/ConditionNodeControl.xaml.cs +++ b/WorkBench/Node/View/ConditionNodeControl.xaml.cs @@ -11,7 +11,7 @@ namespace Serein.Workbench.Node.View public ConditionNodeControl() : base() { // 窗体初始化需要 - ViewModel = new ConditionNodeControlViewModel (new SingleConditionNode()); + ViewModel = new ConditionNodeControlViewModel (new SingleConditionNode(null)); DataContext = ViewModel; InitializeComponent(); } diff --git a/WorkBench/Node/View/ConditionRegionControl.xaml b/WorkBench/Node/View/ConditionRegionControl.xaml index 761523d..c7932cc 100644 --- a/WorkBench/Node/View/ConditionRegionControl.xaml +++ b/WorkBench/Node/View/ConditionRegionControl.xaml @@ -3,7 +3,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:Serein.Workbench.Node.View" + xmlns:local="clr-namespace:Serein.Workbench.Node.View" + xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel" + d:DataContext="{d:DesignInstance vm:ConditionRegionNodeControlViewModel}" + mc:Ignorable="d" MaxWidth="300"> diff --git a/WorkBench/Node/View/DllControlControl.xaml.cs b/WorkBench/Node/View/DllControlControl.xaml.cs index 45dd6c6..8eb72ef 100644 --- a/WorkBench/Node/View/DllControlControl.xaml.cs +++ b/WorkBench/Node/View/DllControlControl.xaml.cs @@ -1,10 +1,6 @@ -using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.Library.Enums; -using Serein.NodeFlow; -using System.Reflection; +using Serein.Library; +using Serein.Library.Utils; using System.Windows; -using System.Windows.Automation; using System.Windows.Controls; using System.Windows.Input; @@ -30,7 +26,7 @@ namespace Serein.Workbench.Node.View public DllControl(NodeLibrary nodeLibrary) { this.nodeLibrary = nodeLibrary; - Header = "DLL name : " + nodeLibrary.Assembly.GetName().Name; + Header = "DLL name : " + nodeLibrary.FullName; InitializeComponent(); } @@ -127,9 +123,15 @@ namespace Serein.Workbench.Node.View if (sender is TextBlock typeText && typeText.Tag is MethodDetailsInfo mdInfo) { + if (!EnumHelper.TryConvertEnum(mdInfo.NodeType, out var nodeType)) + { + return; + } + MoveNodeData moveNodeData = new MoveNodeData { - NodeControlType = mdInfo.NodeType switch + + NodeControlType = nodeType switch { NodeType.Action => NodeControlType.Action, NodeType.Flipflop => NodeControlType.Flipflop, diff --git a/WorkBench/Node/View/ExpOpNodeControl.xaml b/WorkBench/Node/View/ExpOpNodeControl.xaml index ae94507..ae7c1ba 100644 --- a/WorkBench/Node/View/ExpOpNodeControl.xaml +++ b/WorkBench/Node/View/ExpOpNodeControl.xaml @@ -1,9 +1,12 @@  diff --git a/WorkBench/Node/View/ExpOpNodeControl.xaml.cs b/WorkBench/Node/View/ExpOpNodeControl.xaml.cs index e98ee55..f564533 100644 --- a/WorkBench/Node/View/ExpOpNodeControl.xaml.cs +++ b/WorkBench/Node/View/ExpOpNodeControl.xaml.cs @@ -11,7 +11,7 @@ namespace Serein.Workbench.Node.View public ExpOpNodeControl() : base() { // 窗体初始化需要 - ViewModel = new ExpOpNodeViewModel(new SingleExpOpNode()); + ViewModel = new ExpOpNodeViewModel(new SingleExpOpNode(null)); DataContext = ViewModel; InitializeComponent(); } diff --git a/WorkBench/Node/View/FlipflopNodeControl.xaml b/WorkBench/Node/View/FlipflopNodeControl.xaml index 7843bc8..0867bb9 100644 --- a/WorkBench/Node/View/FlipflopNodeControl.xaml +++ b/WorkBench/Node/View/FlipflopNodeControl.xaml @@ -2,13 +2,14 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:Converters="clr-namespace:Serein.Workbench.Tool.Converters" + xmlns:Converters="clr-namespace:Serein.Workbench.Tool.Converters" xmlns:local="clr-namespace:Serein.Workbench.Node.View" xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel" xmlns:themes="clr-namespace:Serein.Workbench.Themes" - MaxWidth="300"> + d:DataContext="{d:DesignInstance vm:FlipflopNodeControlViewModel}" + mc:Ignorable="d" + MaxWidth="300"> @@ -33,7 +34,7 @@ - + diff --git a/WorkBench/Node/View/NodeControlBase.cs b/WorkBench/Node/View/NodeControlBase.cs index 6595941..7061ef8 100644 --- a/WorkBench/Node/View/NodeControlBase.cs +++ b/WorkBench/Node/View/NodeControlBase.cs @@ -1,11 +1,5 @@ using Serein.Library.Api; -using Serein.Library.Entity; -using Serein.NodeFlow.Base; using Serein.Workbench.Node.ViewModel; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Runtime.CompilerServices; using System.Windows.Controls; using System.Windows.Media; diff --git a/WorkBench/Serein.WorkBench.csproj b/WorkBench/Serein.WorkBench.csproj index 122e3c6..05b2ac0 100644 --- a/WorkBench/Serein.WorkBench.csproj +++ b/WorkBench/Serein.WorkBench.csproj @@ -9,6 +9,7 @@ D:\Project\C#\DynamicControl\SereinFlow\.Output MIT true + diff --git a/WorkBench/Themes/MethodDetailsControl.xaml b/WorkBench/Themes/MethodDetailsControl.xaml index 936b11c..f73835e 100644 --- a/WorkBench/Themes/MethodDetailsControl.xaml +++ b/WorkBench/Themes/MethodDetailsControl.xaml @@ -2,7 +2,8 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Serein.Workbench.Themes" - xmlns:sys="clr-namespace:System;assembly=mscorlib"> + xmlns:sys="clr-namespace:System;assembly=mscorlib" + > @@ -12,7 +13,7 @@ - + @@ -21,6 +22,7 @@