mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-03 00:00:49 +08:00
尝试使用源生成器规范NodeModel代码逻辑
This commit is contained in:
@@ -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<WebSocketServer>(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<SereinProjectData> GetProjectInfo()
|
||||
{
|
||||
await Task.Delay(0);
|
||||
return environment.GetProjectInfo();
|
||||
return await environment.GetProjectInfoAsync();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0|AnyCPU'">
|
||||
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library.Utils;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library.NodeFlow.Tool;
|
||||
|
||||
namespace Serein.Library.Core.NodeFlow
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.0.13</Version>
|
||||
<Version>1.0.14</Version>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -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
|
||||
/// <returns></returns>
|
||||
public object GetFlowData(string nodeGuid)
|
||||
{
|
||||
|
||||
|
||||
if (dictNodeFlowData.TryGetValue(nodeGuid, out var data))
|
||||
{
|
||||
return data;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library.NodeFlow.Tool;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
@@ -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("")]
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\.Output\Debug\net8.0\</OutputPath>
|
||||
<OutputPath>..\.Output\Debug\librarynet462\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
@@ -26,10 +26,11 @@
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<OutputPath>..\.Output\Release\librarynet462\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>..\.Output\Release\librarynet462\Serein.Library.Framework.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
@@ -58,7 +59,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Library\Serein.Library.csproj">
|
||||
<Project>{5E19D0F2-913A-4D1C-A6F8-1E1227BAA0E3}</Project>
|
||||
<Project>{5e19d0f2-913a-4d1c-a6f8-1e1227baa0e3}</Project>
|
||||
<Name>Serein.Library</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library.NodeFlow.Tool;
|
||||
using Serein.Library;
|
||||
|
||||
|
||||
namespace Serein.Library.Api
|
||||
{
|
||||
|
||||
@@ -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
|
||||
/// <param name="eventArgs"></param>
|
||||
public delegate void NodeMovedHandler(NodeMovedEventArgs eventArgs);
|
||||
|
||||
/// <summary>
|
||||
/// 远程环境内容输出
|
||||
/// </summary>
|
||||
/// <param name="value">输出的文本信息</param>
|
||||
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对象,目前需要手动转换对应的类型
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// DLL中NodeAction特性的方法描述的所有原始副本
|
||||
/// <para>表示是否正在控制远程</para>
|
||||
/// <para>Local control remote env</para>
|
||||
/// </summary>
|
||||
// ConcurrentDictionary<string, MethodDetails> MethodDetailss { get; }
|
||||
bool IsLcR { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>表示是否受到远程控制</para>
|
||||
/// <para>Remote control local env</para>
|
||||
/// </summary>
|
||||
bool IsRcL { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 流程运行状态
|
||||
/// </summary>
|
||||
RunState FlowState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 全局触发器运行状态
|
||||
/// </summary>
|
||||
RunState FlipFlopState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 拓展功能时,如需订阅事件,则需要使用该属性
|
||||
/// </summary>
|
||||
IFlowEnvironment CurrentEnv { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件
|
||||
@@ -535,28 +551,27 @@ namespace Serein.Library.Api
|
||||
/// </summary>
|
||||
event NodeMovedHandler OnNodeMoved;
|
||||
|
||||
/// <summary>
|
||||
/// 运行环境输出
|
||||
/// </summary>
|
||||
event EnvOutHandler OnEnvOut;
|
||||
#endregion
|
||||
|
||||
#region 接口
|
||||
|
||||
/// <summary>
|
||||
/// 获取方法描述信息
|
||||
/// 设置输出
|
||||
/// </summary>
|
||||
/// <param name="methodName">方法描述</param>
|
||||
/// <param name="mdInfo">方法信息</param>
|
||||
/// <returns></returns>
|
||||
bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo);
|
||||
// <param name="output"></param>
|
||||
// <param name="clearMsg"></param>
|
||||
void SetConsoleOut(); // Action<string> output, Action clearMsg
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定方法的Emit委托
|
||||
/// 使用JSON处理库输出对象信息
|
||||
/// </summary>
|
||||
/// <param name="methodName"></param>
|
||||
/// <param name="del"></param>
|
||||
/// <returns></returns>
|
||||
bool TryGetDelegateDetails(string methodName, out DelegateDetails del);
|
||||
/// <param name="obj"></param>
|
||||
void WriteLineObjToJson(object obj);
|
||||
|
||||
//bool TryGetNodeData(string methodName, out NodeData node);
|
||||
|
||||
#region 环境基础接口
|
||||
/// <summary>
|
||||
/// 启动远程服务
|
||||
/// </summary>
|
||||
@@ -571,20 +586,25 @@ namespace Serein.Library.Api
|
||||
/// 保存当前项目
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
SereinProjectData GetProjectInfo();
|
||||
Task<SereinProjectData> GetProjectInfoAsync();
|
||||
/// <summary>
|
||||
/// 加载项目文件
|
||||
/// </summary>
|
||||
/// <param name="projectFile"></param>
|
||||
/// <param name="flowEnvInfo">包含项目信息的远程环境</param>
|
||||
/// <param name="filePath"></param>
|
||||
void LoadProject(SereinProjectData projectFile, string filePath);
|
||||
void LoadProject(FlowEnvInfo flowEnvInfo, string filePath);
|
||||
/// <summary>
|
||||
/// 加载远程项目
|
||||
/// 加载远程环境
|
||||
/// </summary>
|
||||
/// <param name="addres">远程项目地址</param>
|
||||
/// <param name="port">远程项目端口</param>
|
||||
/// <param name="addres">远程环境地址</param>
|
||||
/// <param name="port">远程环境端口</param>
|
||||
/// <param name="token">密码</param>
|
||||
void LoadRemoteProject(string addres,int port, string token);
|
||||
Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres,int port, string token);
|
||||
|
||||
/// <summary>
|
||||
/// 退出远程环境
|
||||
/// </summary>
|
||||
void ExitRemoteEnv();
|
||||
|
||||
/// <summary>
|
||||
/// 从文件中加载Dll
|
||||
@@ -638,15 +658,15 @@ namespace Serein.Library.Api
|
||||
/// <param name="fromNodeGuid">起始节点Guid</param>
|
||||
/// <param name="toNodeGuid">目标节点Guid</param>
|
||||
/// <param name="connectionType">连接类型</param>
|
||||
void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType);
|
||||
Task<bool> ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType);
|
||||
|
||||
/// <summary>
|
||||
/// 创建节点/区域/基础控件
|
||||
/// </summary>
|
||||
/// <param name="nodeBase">节点/区域/基础控件</param>
|
||||
/// <param name="nodeType">节点/区域/基础控件类型</param>
|
||||
/// <param name="position">节点在画布上的位置(</param>
|
||||
/// <param name="methodDetailsInfo">节点绑定的方法说明(</param>
|
||||
void CreateNode(NodeControlType nodeBase, Position position, MethodDetailsInfo methodDetailsInfo = null);
|
||||
Task<NodeInfo> CreateNodeAsync(NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null);
|
||||
|
||||
/// <summary>
|
||||
/// 移除两个节点之间的连接关系
|
||||
@@ -681,7 +701,7 @@ namespace Serein.Library.Api
|
||||
/// <param name="nodeGuid">被中断的节点Guid</param>
|
||||
/// <param name="interruptClass">新的中断级别</param>
|
||||
/// <returns></returns>
|
||||
bool SetNodeInterrupt(string nodeGuid, InterruptClass interruptClass);
|
||||
Task<bool> SetNodeInterruptAsync(string nodeGuid, InterruptClass interruptClass);
|
||||
|
||||
/// <summary>
|
||||
/// 添加作用于某个对象的中断表达式
|
||||
@@ -689,7 +709,7 @@ namespace Serein.Library.Api
|
||||
/// <param name="key"></param>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
bool AddInterruptExpression(string key, string expression);
|
||||
Task<bool> AddInterruptExpressionAsync(string key, string expression);
|
||||
|
||||
/// <summary>
|
||||
/// 监视指定对象
|
||||
@@ -701,10 +721,9 @@ namespace Serein.Library.Api
|
||||
/// <summary>
|
||||
/// 检查一个对象是否处于监听状态,如果是,则传出与该对象相关的表达式(用于中断),如果不是,则返回false。
|
||||
/// </summary>
|
||||
/// <param name="obj">判断的对象</param>
|
||||
/// <param name="exps">表达式</param>
|
||||
/// <param name="key">判断的对象</param>
|
||||
/// <returns></returns>
|
||||
bool CheckObjMonitorState(string key, out List<string> exps);
|
||||
Task<(bool, string[])> CheckObjMonitorStateAsync(string key);
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -715,13 +734,39 @@ namespace Serein.Library.Api
|
||||
/// <returns></returns>
|
||||
Task<CancelType> GetOrCreateGlobalInterruptAsync();
|
||||
|
||||
/// <summary>
|
||||
/// (用于远程)通知节点属性变更
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid">节点Guid</param>
|
||||
/// <param name="path">属性路径</param>
|
||||
/// <param name="value">属性值</param>
|
||||
/// <returns></returns>
|
||||
Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取方法描述信息
|
||||
/// </summary>
|
||||
/// <param name="methodName">方法描述</param>
|
||||
/// <param name="mdInfo">方法信息</param>
|
||||
/// <returns></returns>
|
||||
bool TryGetMethodDetailsInfo(string methodName, out MethodDetailsInfo mdInfo);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定方法的Emit委托
|
||||
/// </summary>
|
||||
/// <param name="methodName"></param>
|
||||
/// <param name="del"></param>
|
||||
/// <returns></returns>
|
||||
bool TryGetDelegateDetails(string methodName, out DelegateDetails del);
|
||||
|
||||
|
||||
#region 远程相关
|
||||
/// <summary>
|
||||
/// (适用于远程连接后获取环境的运行状态)获取当前环境的信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
object GetEnvInfo();
|
||||
Task<FlowEnvInfo> GetEnvInfoAsync();
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Serein.Library.Utils.EmitHelper;
|
||||
|
||||
namespace Serein.Library.Entity
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 更新委托方法
|
||||
@@ -37,42 +37,42 @@ namespace Serein.Library.Entity
|
||||
private Delegate _emitDelegate;
|
||||
private EmitMethodType _emitMethodType;
|
||||
|
||||
/// <summary>
|
||||
/// <para>普通方法:Func<object,object[],object></para>
|
||||
/// <para>异步方法:Func<object,object[],Task></para>
|
||||
/// <para>异步有返回值方法:Func<object,object[],Task<object>></para>
|
||||
/// </summary>
|
||||
public Delegate EmitDelegate { get => _emitDelegate; }
|
||||
/// <summary>
|
||||
/// 表示Emit构造的委托类型
|
||||
/// </summary>
|
||||
public EmitMethodType EmitMethodType { get => _emitMethodType; }
|
||||
///// <summary>
|
||||
///// <para>普通方法:Func<object,object[],object></para>
|
||||
///// <para>异步方法:Func<object,object[],Task></para>
|
||||
///// <para>异步有返回值方法:Func<object,object[],Task<object>></para>
|
||||
///// </summary>
|
||||
//public Delegate EmitDelegate { get => _emitDelegate; }
|
||||
///// <summary>
|
||||
///// 表示Emit构造的委托类型
|
||||
///// </summary>
|
||||
//public EmitMethodType EmitMethodType { get => _emitMethodType; }
|
||||
|
||||
/// <summary>
|
||||
/// <para>使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。</para>
|
||||
/// </summary>
|
||||
/// <param name="instance">实例</param>
|
||||
/// <param name="args">入参</param>
|
||||
/// <param name="instance">拥有符合委托签名的方法信息的实例</param>
|
||||
/// <param name="args">如果方法没有入参,也需要传入一个空数组</param>
|
||||
/// <returns>void方法自动返回null</returns>
|
||||
public async Task<object> InvokeAsync(object instance, object[] args)
|
||||
{
|
||||
if(args is null)
|
||||
{
|
||||
args = new object[0];
|
||||
args = Array.Empty<object>();
|
||||
}
|
||||
object result = null;
|
||||
try
|
||||
{
|
||||
if (EmitMethodType == EmitMethodType.HasResultTask && EmitDelegate is Func<object, object[], Task<object>> hasResultTask)
|
||||
if (_emitMethodType == EmitMethodType.HasResultTask && _emitDelegate is Func<object, object[], Task<object>> hasResultTask)
|
||||
{
|
||||
result = await hasResultTask(instance, args);
|
||||
}
|
||||
else if (EmitMethodType == EmitMethodType.Task && EmitDelegate is Func<object, object[], Task> task)
|
||||
else if (_emitMethodType == EmitMethodType.Task && _emitDelegate is Func<object, object[], Task> task)
|
||||
{
|
||||
await task.Invoke(instance, args);
|
||||
result = null;
|
||||
}
|
||||
else if (EmitMethodType == EmitMethodType.Func && EmitDelegate is Func<object, object[], object> func)
|
||||
else if (_emitMethodType == EmitMethodType.Func && _emitDelegate is Func<object, object[], object> func)
|
||||
{
|
||||
result = func.Invoke(instance, args);
|
||||
}
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Enums;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Serein.Library.Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 方法描述信息
|
||||
/// </summary>
|
||||
public class MethodDetailsInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 属于哪个DLL文件
|
||||
/// </summary>
|
||||
public string LibraryName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法名称
|
||||
/// </summary>
|
||||
public string MethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 节点类型
|
||||
/// </summary>
|
||||
public NodeType NodeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法说明
|
||||
/// </summary>
|
||||
public string MethodTips { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数内容
|
||||
/// </summary>
|
||||
|
||||
public ParameterDetailsInfo[] ParameterDetailsInfos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出参类型
|
||||
/// </summary>
|
||||
public string ReturnTypeFullName { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 每个节点有独自的MethodDetails实例
|
||||
/// </summary>
|
||||
public class MethodDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// 转为信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public MethodDetailsInfo ToInfo()
|
||||
{
|
||||
return new MethodDetailsInfo
|
||||
{
|
||||
MethodName = MethodName,
|
||||
MethodTips = MethodTips,
|
||||
NodeType = MethodDynamicType,
|
||||
ParameterDetailsInfos = this.ParameterDetailss.Select(p => p.ToInfo()).ToArray(),
|
||||
ReturnTypeFullName = ReturnType.FullName,
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从DLL拖动出来时拷贝新的实例
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否保护参数(仅视觉效果参数,不影响运行实现)
|
||||
/// </summary>
|
||||
public bool IsProtectionParameter { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 作用实例的类型(多个相同的节点将拥有相同的类型)
|
||||
/// </summary>
|
||||
public Type ActingInstanceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 作用实例(多个相同的节点将会共享同一个实例)
|
||||
/// </summary>
|
||||
public object ActingInstance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法名称
|
||||
/// </summary>
|
||||
public string MethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 节点类型
|
||||
/// </summary>
|
||||
public NodeType MethodDynamicType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 锁名称(暂未实现)
|
||||
/// </summary>
|
||||
public string MethodLockName { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 方法说明
|
||||
/// </summary>
|
||||
public string MethodTips { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 参数描述
|
||||
/// </summary>
|
||||
|
||||
public ParameterDetails[] ParameterDetailss { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出参类型
|
||||
/// </summary>
|
||||
|
||||
public Type ReturnType { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
18
Library/Entity/MoveNodeData.cs
Normal file
18
Library/Entity/MoveNodeData.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 拖拽创建节点使用的数据
|
||||
/// </summary>
|
||||
public class MoveNodeData
|
||||
{
|
||||
public NodeControlType NodeControlType { get; set; }
|
||||
public MethodDetailsInfo MethodDetailsInfo { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Serein.Library.Utils.ChannelFlowInterrupt;
|
||||
|
||||
namespace Serein.Library.Entity
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点调试设置,用于中断节点的运行
|
||||
@@ -12,7 +12,7 @@ namespace Serein.Library.Entity
|
||||
public class NodeDebugSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否使能(调试中断功能)
|
||||
/// 是否使能
|
||||
/// </summary>
|
||||
public bool IsEnable { get; set; } = true;
|
||||
|
||||
@@ -48,10 +48,6 @@ namespace Serein.Library.Entity
|
||||
/// </summary>
|
||||
Branch,
|
||||
/// <summary>
|
||||
/// 分组中断,中断进入指定节点分组的分支。(暂未实现相关)
|
||||
/// </summary>
|
||||
// Group,
|
||||
/// <summary>
|
||||
/// 全局中断,中断全局所有节点的运行。(暂未实现相关)
|
||||
/// </summary>
|
||||
Global,
|
||||
|
||||
@@ -3,19 +3,31 @@ using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Serein.Library.Entity
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点DLL依赖类,如果一个项目中引入了多个DLL,需要放置在同一个文件夹中
|
||||
/// </summary>
|
||||
public class NodeLibrary
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件名
|
||||
/// </summary>
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
|
||||
public string Name{ get; set; }
|
||||
/// <summary>
|
||||
/// 依赖类的名称
|
||||
/// </summary>
|
||||
public string FullName{ get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 对应的程序集
|
||||
/// </summary>
|
||||
public Assembly Assembly { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Serein.Library.Entity
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 方法入参描述
|
||||
/// </summary>
|
||||
public class ParameterDetailsInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 参数索引
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法需要的类型
|
||||
/// </summary>
|
||||
public string DataTypeFullName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法入参参数名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点入参参数详情
|
||||
/// </summary>
|
||||
public class ParameterDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// 转为描述
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ParameterDetailsInfo ToInfo()
|
||||
{
|
||||
return new ParameterDetailsInfo
|
||||
{
|
||||
Index = Index,
|
||||
DataTypeFullName = DataType.FullName,
|
||||
Name = Name
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拷贝新的对象。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 参数索引
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
/// <summary>
|
||||
/// 是否为显式参数(固定值/表达式)
|
||||
/// </summary>
|
||||
public bool IsExplicitData { get; set; }
|
||||
/// <summary>
|
||||
/// 转换器 IEnumConvertor<,>
|
||||
/// </summary>
|
||||
public Func<object, object> Convertor { get; set; }
|
||||
/// <summary>
|
||||
/// 显式类型
|
||||
/// </summary>
|
||||
public Type ExplicitType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目前存在三种状态:Select/Bool/Value
|
||||
/// <para>Select : 枚举值</para>
|
||||
/// <para>Bool : 布尔类型</para>
|
||||
/// <para>Value : 除以上类型之外的任意参数</para>
|
||||
/// </summary>
|
||||
public string ExplicitTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法需要的类型
|
||||
/// </summary>
|
||||
public Type DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法入参参数名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 入参值(在UI上输入的文本内容)
|
||||
/// </summary>
|
||||
|
||||
public string DataValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 如果是引用类型,拷贝时不会发生改变。
|
||||
/// </summary>
|
||||
public object[] Items { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Serein.Library.Enums
|
||||
namespace Serein.Library
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,10 +4,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Enums
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发器说明
|
||||
/// 触发器状态
|
||||
/// </summary>
|
||||
public enum FlipflopStateType
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Enums
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 用来判断该方法属于什么节点,使运行环境决定方法的运行逻辑
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Enums
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 流程运行状态
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.CodeDom;
|
||||
|
||||
namespace Serein.Library.Ex
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发器异常
|
||||
|
||||
25
Library/FlowNode/Attribute.cs
Normal file
25
Library/FlowNode/Attribute.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动生成环境的属性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = true)]
|
||||
internal sealed class PropertyInfoAttribute : Attribute
|
||||
{
|
||||
public bool IsNotification = false;
|
||||
public bool IsPrint = false;
|
||||
}
|
||||
|
||||
}
|
||||
210
Library/FlowNode/MethodDetails.cs
Normal file
210
Library/FlowNode/MethodDetails.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Serein.Library
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 每个节点有独自的MethodDetails实例
|
||||
/// </summary>
|
||||
[AutoProperty(ValuePath = nameof(MethodDetails))]
|
||||
public partial class MethodDetails
|
||||
{
|
||||
private readonly IFlowEnvironment env;
|
||||
private readonly NodeModelBase nodeModel;
|
||||
/// <summary>
|
||||
/// 是否保护参数(目前仅视觉效果参数,不影响运行实现,后续将设置作用在运行逻辑中)
|
||||
/// </summary>
|
||||
[PropertyInfo(IsNotification = true)]
|
||||
private bool _isProtectionParameter;
|
||||
|
||||
/// <summary>
|
||||
/// 作用实例的类型(多个相同的节点将拥有相同的类型)
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private Type _actingInstanceType;
|
||||
|
||||
/// <summary>
|
||||
/// 作用实例(多个相同的节点将会共享同一个实例)
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private object _actingInstance;
|
||||
|
||||
/// <summary>
|
||||
/// 方法名称
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private string _methodName;
|
||||
|
||||
/// <summary>
|
||||
/// 节点类型
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private NodeType _methodDynamicType;
|
||||
|
||||
/// <summary>
|
||||
/// 锁名称(暂未实现)
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private string _methodLockName;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 方法说明
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private string _methodTips;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 参数描述
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private ParameterDetails[] _parameterDetailss;
|
||||
|
||||
/// <summary>
|
||||
/// 出参类型
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private Type _returnType;
|
||||
}
|
||||
|
||||
|
||||
public partial class MethodDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// 不包含方法信息的基础节点(后续可能要改为DLL引入基础节点)
|
||||
/// </summary>
|
||||
public MethodDetails()
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 生成元数据
|
||||
/// </summary>
|
||||
/// <param name="env">节点运行的环境</param>
|
||||
/// <param name="nodeModel">标识属于哪个节点</param>
|
||||
public MethodDetails(IFlowEnvironment env, NodeModelBase nodeModel)
|
||||
{
|
||||
this.nodeModel = nodeModel;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从方法信息中读取
|
||||
/// </summary>
|
||||
/// <param name="Info"></param>
|
||||
public MethodDetails(MethodDetailsInfo Info)
|
||||
{
|
||||
if (!Info.NodeType.TryConvertEnum<NodeType>(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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转为信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public MethodDetailsInfo ToInfo()
|
||||
{
|
||||
return new MethodDetailsInfo
|
||||
{
|
||||
MethodName = MethodName,
|
||||
MethodTips = MethodTips,
|
||||
NodeType = MethodDynamicType.ToString(),
|
||||
ParameterDetailsInfos = ParameterDetailss.Select(p => p.ToInfo()).ToArray(),
|
||||
ReturnTypeFullName = ReturnType.FullName,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从DLL拖动出来时拷贝属于节点的实例
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// 每个节点有独自的MethodDetails实例
|
||||
///// </summary>
|
||||
//public partial class TmpMethodDetails
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// 是否保护参数(目前仅视觉效果参数,不影响运行实现,后续将设置作用在运行逻辑中)
|
||||
// /// </summary>
|
||||
// public bool IsProtectionParameter { get; set; } = false;
|
||||
|
||||
// /// <summary>
|
||||
// /// 作用实例的类型(多个相同的节点将拥有相同的类型)
|
||||
// /// </summary>
|
||||
// public Type ActingInstanceType { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// 作用实例(多个相同的节点将会共享同一个实例)
|
||||
// /// </summary>
|
||||
// public object ActingInstance { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// 方法名称
|
||||
// /// </summary>
|
||||
// public string MethodName { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// 节点类型
|
||||
// /// </summary>
|
||||
// public NodeType MethodDynamicType { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// 锁名称(暂未实现)
|
||||
// /// </summary>
|
||||
// public string MethodLockName { get; set; }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// 方法说明
|
||||
// /// </summary>
|
||||
// public string MethodTips { get; set; }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// 参数描述
|
||||
// /// </summary>
|
||||
|
||||
// public ParameterDetails[] ParameterDetailss { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// 出参类型
|
||||
// /// </summary>
|
||||
|
||||
// public Type ReturnType { get; set; }
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
46
Library/FlowNode/MethodDetailsInfo.cs
Normal file
46
Library/FlowNode/MethodDetailsInfo.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 方法描述信息
|
||||
/// </summary>
|
||||
public class MethodDetailsInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 属于哪个DLL文件
|
||||
/// </summary>
|
||||
public string LibraryName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法名称
|
||||
/// </summary>
|
||||
public string MethodName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 节点类型
|
||||
/// </summary>
|
||||
public string NodeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法说明
|
||||
/// </summary>
|
||||
public string MethodTips { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数内容
|
||||
/// </summary>
|
||||
|
||||
public ParameterDetailsInfo[] ParameterDetailsInfos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出参类型
|
||||
/// </summary>
|
||||
public string ReturnTypeFullName { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点基类(数据):条件控件,动作控件,条件区域,动作区域
|
||||
@@ -13,7 +12,7 @@ namespace Serein.NodeFlow.Base
|
||||
public abstract partial class NodeModelBase : IDynamicFlowNode
|
||||
{
|
||||
|
||||
public NodeModelBase()
|
||||
public NodeModelBase(IFlowEnvironment environment)
|
||||
{
|
||||
PreviousNodes = new Dictionary<ConnectionType, List<NodeModelBase>>();
|
||||
SuccessorNodes = new Dictionary<ConnectionType, List<NodeModelBase>>();
|
||||
@@ -23,26 +22,36 @@ namespace Serein.NodeFlow.Base
|
||||
SuccessorNodes[ctType] = new List<NodeModelBase>();
|
||||
}
|
||||
DebugSetting = new NodeDebugSetting();
|
||||
this.Env = environment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点保留对环境的引用,因为需要在属性更改时通知
|
||||
/// </summary>
|
||||
public IFlowEnvironment Env { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 调试功能
|
||||
/// 在画布中的位置
|
||||
/// </summary>
|
||||
public PositionOfUI Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加的调试功能
|
||||
/// </summary>
|
||||
public NodeDebugSetting DebugSetting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 节点对应的控件类型
|
||||
/// 描述节点对应的控件类型
|
||||
/// </summary>
|
||||
public NodeControlType ControlType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法描述,对应DLL的方法
|
||||
/// 方法描述但不包含Method与委托,需要通过MethodName从环境中获取委托进行调用。
|
||||
/// </summary>
|
||||
public MethodDetails MethodDetails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 节点guid
|
||||
/// 标识节点对象全局唯一
|
||||
/// </summary>
|
||||
public string Guid { get; set; }
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
@@ -46,7 +43,16 @@ namespace Serein.NodeFlow.Base
|
||||
|
||||
#region 导出/导入项目文件节点信息
|
||||
|
||||
/// <summary>
|
||||
/// 获取节点参数
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract Parameterdata[] GetParameterdatas();
|
||||
|
||||
/// <summary>
|
||||
/// 导出为节点信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从节点信息加载节点
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
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<NodeModelBase> stack = new Stack<NodeModelBase>();
|
||||
HashSet<NodeModelBase> processedNodes = new HashSet<NodeModelBase>(); // 用于记录已处理上游节点的节点
|
||||
stack.Push(this);
|
||||
var flowCts = context.Env.IOC.Get<CancellationTokenSource>(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<string> 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();
|
||||
220
Library/FlowNode/ParameterDetails.cs
Normal file
220
Library/FlowNode/ParameterDetails.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 节点入参参数详情
|
||||
/// </summary>
|
||||
[AutoProperty(ValuePath = nameof(ParameterDetails))]
|
||||
public partial class ParameterDetails
|
||||
{
|
||||
private readonly IFlowEnvironment env;
|
||||
private readonly NodeModelBase nodeModel;
|
||||
/// <summary>
|
||||
/// 参数索引
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private int _index;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为显式参数(固定值/表达式)
|
||||
/// </summary>
|
||||
[PropertyInfo(IsNotification = true)]
|
||||
private bool _isExplicitData ;
|
||||
|
||||
/// <summary>
|
||||
/// 转换器 IEnumConvertor<,>
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private Func<object, object> _convertor ;
|
||||
|
||||
/// <summary>
|
||||
/// 显式类型
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private Type _explicitType ;
|
||||
|
||||
/// <summary>
|
||||
/// 目前存在三种状态:Select/Bool/Value
|
||||
/// <para>Select : 枚举值</para>
|
||||
/// <para>Bool : 布尔类型</para>
|
||||
/// <para>Value : 除以上类型之外的任意参数</para>
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private string _explicitTypeName ;
|
||||
|
||||
/// <summary>
|
||||
/// 方法需要的类型
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private Type _dataType ;
|
||||
|
||||
/// <summary>
|
||||
/// 方法入参参数名称
|
||||
/// </summary>
|
||||
[PropertyInfo]
|
||||
private string _name ;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义的方法入参数据
|
||||
/// </summary>
|
||||
[PropertyInfo(IsNotification = true)] // IsPrint = true
|
||||
private string _dataValue;
|
||||
|
||||
/// <summary>
|
||||
/// 如果是引用类型,拷贝时不会发生改变。
|
||||
/// </summary>
|
||||
[PropertyInfo(IsNotification = true)]
|
||||
private string[] _items ;
|
||||
}
|
||||
|
||||
|
||||
public partial class ParameterDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// 为节点实例化新的入参描述
|
||||
/// </summary>
|
||||
public ParameterDetails(IFlowEnvironment env, NodeModelBase nodeModel)
|
||||
{
|
||||
this.env = env;
|
||||
this.nodeModel = nodeModel;
|
||||
}
|
||||
/// <summary>
|
||||
/// 通过参数信息加载实体,用于加载项目文件、远程连接的场景
|
||||
/// </summary>
|
||||
/// <param name="info">参数信息</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于创建元数据
|
||||
/// </summary>
|
||||
/// <param name="info">方法参数信息</param>
|
||||
public ParameterDetails()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转为描述
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ParameterDetailsInfo ToInfo()
|
||||
{
|
||||
return new ParameterDetailsInfo
|
||||
{
|
||||
Index = Index,
|
||||
DataTypeFullName = DataType.FullName,
|
||||
Name = Name,
|
||||
ExplicitTypeFullName = ExplicitType.FullName,
|
||||
ExplicitTypeName = ExplicitTypeName,
|
||||
Items = Items,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为某个节点拷贝方法描述的入参描述
|
||||
/// </summary>
|
||||
/// <param name="env">运行环境</param>
|
||||
/// <param name="nodeGuid">运行环境</param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// 节点入参参数详情
|
||||
///// </summary>
|
||||
|
||||
//public partial class TempParameterDetails
|
||||
//{
|
||||
// private readonly MethodDetails methodDetails;
|
||||
|
||||
// /// <summary>
|
||||
// /// 参数索引
|
||||
// /// </summary>
|
||||
// public int Index { get; set; }
|
||||
// /// <summary>
|
||||
// /// 是否为显式参数(固定值/表达式)
|
||||
// /// </summary>
|
||||
// public bool IsExplicitData { get; set; }
|
||||
// /// <summary>
|
||||
// /// 转换器 IEnumConvertor<,>
|
||||
// /// </summary>
|
||||
// public Func<object, object> Convertor { get; set; }
|
||||
// /// <summary>
|
||||
// /// 显式类型
|
||||
// /// </summary>
|
||||
// public Type ExplicitType { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// 目前存在三种状态:Select/Bool/Value
|
||||
// /// <para>Select : 枚举值</para>
|
||||
// /// <para>Bool : 布尔类型</para>
|
||||
// /// <para>Value : 除以上类型之外的任意参数</para>
|
||||
// /// </summary>
|
||||
// public string ExplicitTypeName { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// 方法需要的类型
|
||||
// /// </summary>
|
||||
// public Type DataType { get; set; }
|
||||
|
||||
// /// <summary>
|
||||
// /// 方法入参参数名称
|
||||
// /// </summary>
|
||||
// public string Name { get; set; }
|
||||
|
||||
|
||||
// private string _dataValue;
|
||||
// /// <summary>
|
||||
// /// 入参值(在UI上输入的文本内容)
|
||||
// /// </summary>
|
||||
|
||||
// public string DataValue
|
||||
// {
|
||||
// get => _dataValue; set
|
||||
// {
|
||||
// _dataValue = value;
|
||||
// Console.WriteLine($"更改了{value}");
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 如果是引用类型,拷贝时不会发生改变。
|
||||
// /// </summary>
|
||||
// public string[] Items { get; set; }
|
||||
//}
|
||||
|
||||
}
|
||||
47
Library/FlowNode/ParameterDetailsInfo.cs
Normal file
47
Library/FlowNode/ParameterDetailsInfo.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 方法入参描述
|
||||
/// </summary>
|
||||
public class ParameterDetailsInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 参数索引
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法需要的类型
|
||||
/// </summary>
|
||||
public string DataTypeFullName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 方法入参参数名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// 显式类型
|
||||
/// </summary>
|
||||
public string ExplicitTypeFullName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 目前存在三种状态:Select/Bool/Value
|
||||
/// <para>Select : 枚举值</para>
|
||||
/// <para>Bool : 布尔类型</para>
|
||||
/// <para>Value : 除以上类型之外的任意参数</para>
|
||||
/// </summary>
|
||||
public string ExplicitTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数选择器
|
||||
/// </summary>
|
||||
public string[] Items { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,37 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Entity
|
||||
namespace Serein.Library
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 环境信息(远程控制用)
|
||||
/// </summary>
|
||||
public class FlowEnvInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 环境方法信息
|
||||
/// </summary>
|
||||
public LibraryMds[] LibraryMds { get; set; }
|
||||
/// <summary>
|
||||
/// 项目信息
|
||||
/// </summary>
|
||||
public SereinProjectData Project { get; set; }
|
||||
|
||||
// IOC节点对象信息
|
||||
}
|
||||
|
||||
public class LibraryMds
|
||||
{
|
||||
public string LibraryName { get; set; }
|
||||
|
||||
public MethodDetailsInfo[] Mds { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 项目保存文件
|
||||
/// </summary>
|
||||
@@ -70,7 +98,7 @@ namespace Serein.Library.Entity
|
||||
/// <summary>
|
||||
/// 高度
|
||||
/// </summary>
|
||||
public double Lenght { get; set; }
|
||||
public double Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 预览位置X
|
||||
@@ -99,19 +127,22 @@ namespace Serein.Library.Entity
|
||||
public class Library
|
||||
{
|
||||
/// <summary>
|
||||
/// DLL名称
|
||||
/// 文件名称
|
||||
/// </summary>
|
||||
|
||||
public string Name { get; set; }
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// 文件路径
|
||||
/// </summary>
|
||||
public string FilePath { get; set; }
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 程序集名称
|
||||
/// </summary>
|
||||
public string AssemblyName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点
|
||||
/// </summary>
|
||||
@@ -175,7 +206,7 @@ namespace Serein.Library.Entity
|
||||
/// 于画布中的位置
|
||||
/// </summary>
|
||||
|
||||
public Position Position { get; set; }
|
||||
public PositionOfUI Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否选中(暂时无效)
|
||||
@@ -207,14 +238,20 @@ namespace Serein.Library.Entity
|
||||
/// <summary>
|
||||
/// 节点于画布中的位置
|
||||
/// </summary>
|
||||
public class Position
|
||||
{
|
||||
public class PositionOfUI
|
||||
{ /// <summary>
|
||||
/// 构造一个坐标
|
||||
/// </summary>
|
||||
public PositionOfUI()
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 构造一个坐标
|
||||
/// </summary>
|
||||
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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,9 +31,10 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <para>作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理。</para>
|
||||
/// <para>如果没有显式设置ThemeValue,将默认使用方法名称作为ThemeValue。</para>
|
||||
/// <para>如果没有显式设置IsReturnValue标记为false,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去</para>
|
||||
/// <para>作用:WebSocket中处理Json时,将通过Json中ThemeKey 对应的内容(ThemeValue)自动路由到相应方法进行处理,同时要求Data中必须存在对应入参。</para>
|
||||
/// <para>如果没有显式设置 ThemeValue,将默认使用方法名称作为ThemeValue。</para>
|
||||
/// <para>如果没有显式设置 IsReturnValue 标记为 false ,当方法顺利完成(没有抛出异常,且返回对象非null),会自动转为json文本发送回去</para>
|
||||
/// <para>如果没有显式设置 ArgNotNull 标记为 false ,当外部尝试调用时,若 Json Data 不包含响应的数据,将会被忽略此次调用</para>
|
||||
/// <para>如果返回类型为Task或Task<TResult>,将会自动等待异步完成并获取结果(无法处理Task<Task<TResult>>的情况)。</para>
|
||||
/// <para>如果返回了值类型,会自动装箱为引用对象。</para>
|
||||
/// <para>如果有方法执行过程中发送消息的需求,请在入参中声明以下类型的成员,调用时将传入发送消息的委托。</para>
|
||||
@@ -61,9 +62,24 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
/// <para>会进行异步等待,当Task结束后,自动获取TResult进行发送(请避免Task<Task<TResult>>诸如此类的Task泛型嵌套)</para>
|
||||
/// </summary>
|
||||
public bool IsReturnValue = true;
|
||||
/// <summary>
|
||||
/// <para>表示该方法所有入参不能为空(所需的参数在请求Json的Data不存在)</para>
|
||||
/// <para>若有一个参数无法从data获取,则不会进行调用该方法</para>
|
||||
/// <para>如果设置该属性为 false ,但某些入参不能为空,而不希望在代码中进行检查,请为入参添加[NotNull]/[Needful]特性</para>
|
||||
/// </summary>
|
||||
public bool ArgNotNull = true;
|
||||
}
|
||||
|
||||
internal class SocketHandleModel
|
||||
|
||||
/// <summary>
|
||||
/// 使用消息DataKey整体数据
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class UseMsgDataAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
internal class SocketHandleModule
|
||||
{
|
||||
public string ThemeValue { get; set; } = string.Empty;
|
||||
public bool IsReturnValue { get; set; } = true;
|
||||
|
||||
15
Library/Network/WebSocket/Handle/Attribute.cs
Normal file
15
Library/Network/WebSocket/Handle/Attribute.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示参数可以为空(Net462不能使用NutNull的情况)
|
||||
/// </summary>
|
||||
public sealed class NeedfulAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public class JsonMsgHandleConfig
|
||||
{
|
||||
private readonly Delegate EmitDelegate;
|
||||
private readonly EmitHelper.EmitMethodType EmitMethodType;
|
||||
public Guid HandleGuid { get; }
|
||||
|
||||
private Action<Exception, Action<object>> OnExceptionTracking;
|
||||
|
||||
internal JsonMsgHandleConfig(SocketHandleModel model,ISocketHandleModule instance, MethodInfo methodInfo, Action<Exception, Action<object>> onExceptionTracking)
|
||||
internal JsonMsgHandleConfig(SocketHandleModule model,
|
||||
ISocketHandleModule instance,
|
||||
MethodInfo methodInfo,
|
||||
Action<Exception, Action<object>> 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<UseMsgDataAttribute>() != null).ToArray();
|
||||
#if NET5_0_OR_GREATER
|
||||
this.IsCheckArgNotNull = parameterInfos.Select(p => p.GetCustomAttribute<NotNullAttribute>() != null).ToArray();
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
if(IsCheckArgNotNull is null)
|
||||
{
|
||||
IsCheckArgNotNull = parameterInfos.Select(p => p.GetCustomAttribute<NeedfulAttribute>() != null).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 兼容两种非空特性的写法
|
||||
var argNotNull = parameterInfos.Select(p => p.GetCustomAttribute<NeedfulAttribute>() != 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;
|
||||
/// <summary>
|
||||
/// 参数不能为空
|
||||
/// </summary>
|
||||
private bool ArgNotNull;
|
||||
|
||||
/// <summary>
|
||||
/// Emit委托
|
||||
/// </summary>
|
||||
private readonly Delegate EmitDelegate;
|
||||
/// <summary>
|
||||
/// Emit委托类型
|
||||
/// </summary>
|
||||
private readonly EmitHelper.EmitMethodType EmitMethodType;
|
||||
/// <summary>
|
||||
/// 未捕获的异常跟踪
|
||||
/// </summary>
|
||||
private readonly Action<Exception, Action<object>> OnExceptionTracking;
|
||||
/// <summary>
|
||||
/// 所在的模块
|
||||
/// </summary>
|
||||
private readonly SocketHandleModule Module;
|
||||
/// <summary>
|
||||
/// 所使用的实例
|
||||
/// </summary>
|
||||
private readonly ISocketHandleModule Instance;
|
||||
/// <summary>
|
||||
/// 参数名称
|
||||
/// </summary>
|
||||
private readonly string[] ParameterName;
|
||||
/// <summary>
|
||||
/// 参数类型
|
||||
/// </summary>
|
||||
private readonly Type[] ParameterType;
|
||||
/// <summary>
|
||||
/// 是否使用整体data参数
|
||||
/// </summary>
|
||||
private readonly bool[] useMsgData;
|
||||
/// <summary>
|
||||
/// 是否检查变量为空
|
||||
/// </summary>
|
||||
private readonly bool[] IsCheckArgNotNull;
|
||||
|
||||
|
||||
public async void Handle(Func<string, Task> RecoverAsync, JObject jsonObject)
|
||||
|
||||
public async void Handle(Func<object, Task> 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<object, Task>)))
|
||||
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<object, Task>)))
|
||||
{
|
||||
args[i] = new Func<object, Task>(async data =>
|
||||
{
|
||||
var jsonText = JsonConvert.SerializeObject(data);
|
||||
await RecoverAsync.Invoke(jsonText);
|
||||
await SendAsync.Invoke(jsonText);
|
||||
});
|
||||
}
|
||||
else if (type.IsAssignableFrom(typeof(Func<string, Task>)))
|
||||
{
|
||||
args[i] = new Func<string, Task>(async data =>
|
||||
{
|
||||
await RecoverAsync.Invoke(data);
|
||||
await SendAsync.Invoke(data);
|
||||
});
|
||||
}
|
||||
else if (type.IsAssignableFrom(typeof(Action<object>)))
|
||||
@@ -74,7 +187,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
args[i] = new Action<object>(async data =>
|
||||
{
|
||||
var jsonText = JsonConvert.SerializeObject(data);
|
||||
await RecoverAsync.Invoke(jsonText);
|
||||
await SendAsync.Invoke(jsonText);
|
||||
});
|
||||
}
|
||||
else if (type.IsAssignableFrom(typeof(Action<string>)))
|
||||
@@ -82,28 +195,17 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
|
||||
args[i] = new Action<string>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<string, JsonMsgHandleConfig> MyHandleConfigs = new ConcurrentDictionary<string, JsonMsgHandleConfig>();
|
||||
|
||||
internal void AddHandleConfigs(SocketHandleModel model, ISocketHandleModule instance, MethodInfo methodInfo
|
||||
, Action<Exception, Action<object>> onExceptionTracking)
|
||||
/// <summary>
|
||||
/// 添加处理配置
|
||||
/// </summary>
|
||||
/// <param name="module">处理模块</param>
|
||||
/// <param name="jsonMsgHandleConfig">处理配置</param>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理JSON数据
|
||||
/// </summary>
|
||||
/// <param name="RecoverAsync"></param>
|
||||
/// <param name="tSendAsync"></param>
|
||||
/// <param name="jsonObject"></param>
|
||||
public void HandleSocketMsg(Func<string, Task> RecoverAsync, JObject jsonObject)
|
||||
public void HandleSocketMsg(Func<string, Task> 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<object, Task> 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<JObject>();
|
||||
handldConfig.Handle(SendAsync, dataObj);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"error in ws : {ex.Message}{Environment.NewLine}json value:{jsonObject}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<AutoSocketHandleAttribute>();
|
||||
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
|
||||
/// <summary>
|
||||
/// 异步处理消息
|
||||
/// </summary>
|
||||
/// <param name="RecoverAsync"></param>
|
||||
/// <param name="SendAsync"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public async Task HandleMsgAsync(Func<string, Task> RecoverAsync, string message)
|
||||
public async Task HandleMsgAsync(Func<string, Task> 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);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// WebSocket客户端
|
||||
/// </summary>
|
||||
[AutoRegister]
|
||||
public class WebSocketClient
|
||||
{
|
||||
/// <summary>
|
||||
@@ -25,7 +19,7 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
/// </summary>
|
||||
public WebSocketClient()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -40,10 +34,20 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
public async Task ConnectAsync(string uri)
|
||||
public async Task<bool> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<byte>(buffer), CancellationToken.None);
|
||||
WebSocketReceiveResult result;
|
||||
|
||||
do
|
||||
{
|
||||
result = await _client.ReceiveAsync(new ArraySegment<byte>(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<string, HandldConfig> ThemeConfigs = new ConcurrentDictionary<string, HandldConfig>();
|
||||
|
||||
/* #region 消息处理
|
||||
private readonly string ThemeField;
|
||||
private readonly ConcurrentDictionary<string, HandldConfig> ThemeConfigs = new ConcurrentDictionary<string, HandldConfig>();
|
||||
|
||||
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*/
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public string AddresPort { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已经鉴权
|
||||
/// </summary>
|
||||
public bool IsAuthorized { get => isAuthorized; } //set => isAuthorized = value;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已经鉴权
|
||||
/// </summary>
|
||||
private bool isAuthorized;
|
||||
|
||||
/// <summary>
|
||||
/// 授权字段
|
||||
@@ -61,34 +48,21 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
/// 处理消息授权
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public async Task HandleAuthorized(string message)
|
||||
public async Task<bool> 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<string, WebSocketAuthorizedHelper>();
|
||||
this.InspectionAuthorizedFunc = (tokenObj) => Task.FromResult(true);
|
||||
this.IsNeedInspectionAuthorized = false;
|
||||
this.IsCheckToken = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -127,7 +101,7 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
this.TokenKey = tokenKey;
|
||||
this.AuthorizedClients = new ConcurrentDictionary<string, WebSocketAuthorizedHelper>();
|
||||
this.InspectionAuthorizedFunc = inspectionAuthorizedFunc;
|
||||
this.IsNeedInspectionAuthorized = true;
|
||||
this.IsCheckToken = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -136,7 +110,7 @@ namespace Serein.Library.Network.WebSocketCommunication
|
||||
public ConcurrentDictionary<string, WebSocketAuthorizedHelper> AuthorizedClients;
|
||||
private readonly string TokenKey;
|
||||
private readonly Func<dynamic, Task<bool>> InspectionAuthorizedFunc;
|
||||
private bool IsNeedInspectionAuthorized = false;
|
||||
private bool IsCheckToken = false;
|
||||
/// <summary>
|
||||
/// 进行监听服务
|
||||
/// </summary>
|
||||
@@ -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<string, Task> 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<byte>(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<byte>(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()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Serein.Library.Enums;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Serein.Library.Attributes
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示该属性为自动注入依赖项。</para>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.0.13</Version>
|
||||
<Version>1.0.15</Version>
|
||||
<TargetFrameworks>net8.0;net462</TargetFrameworks>
|
||||
<!--<TargetFrameworks>net8.0</TargetFrameworks>-->
|
||||
<BaseOutputPath>D:\Project\C#\DynamicControl\SereinFlow\.Output</BaseOutputPath>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<Title>SereinFow</Title>
|
||||
@@ -12,6 +13,9 @@
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>.\obj\g</CompilerGeneratedFilesOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -27,6 +31,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<ProjectReference Include="..\Serein.Library.MyGenerator\Serein.Library.MyGenerator.csproj" OutputItemType="Analyzer"/>
|
||||
<!--ReferenceOutputAssembly="false"-->
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Serein.Library.Utils
|
||||
/// </summary>
|
||||
/// <param name="signal">信号标识符</param>
|
||||
/// <param name="timeout">超时时间</param>
|
||||
public CancelType CreateChannelWithTimeoutSync(string signal, TimeSpan timeout)
|
||||
public async Task<CancelType> 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;
|
||||
|
||||
}
|
||||
|
||||
103
Library/Utils/ChannelFlowTrigger.cs
Normal file
103
Library/Utils/ChannelFlowTrigger.cs
Normal file
@@ -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<TSignal>
|
||||
{
|
||||
// 使用并发字典管理每个枚举信号对应的 Channel
|
||||
private readonly ConcurrentDictionary<TSignal, Channel<(TriggerType,object)>> _channels = new ConcurrentDictionary<TSignal, Channel<(TriggerType, object)>>();
|
||||
|
||||
/// <summary>
|
||||
/// 创建信号并指定超时时间,到期后自动触发(异步方法)
|
||||
/// </summary>
|
||||
/// <param name="signal">枚举信号标识符</param>
|
||||
/// <param name="outTime">超时时间</param>
|
||||
/// <returns>等待任务</returns>
|
||||
public async Task<(TriggerType, TResult)> WaitDataWithTimeoutAsync<TResult>(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<TResult>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建信号,直到触发
|
||||
/// </summary>
|
||||
/// <param name="signal">枚举信号标识符</param>
|
||||
/// <returns>等待任务</returns>
|
||||
public async Task<TResult> WaitData<TResult>(TSignal signal)
|
||||
{
|
||||
var channel = GetOrCreateChannel(signal);
|
||||
// 等待信号传入(超时或手动触发)
|
||||
(var type, var result) = await channel.Reader.ReadAsync();
|
||||
return result.ToConvert<TResult>();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 触发信号
|
||||
/// </summary>
|
||||
/// <param name="signal">枚举信号标识符</param>
|
||||
/// <returns>是否成功触发</returns>
|
||||
public bool TriggerSignal(TSignal signal, object value)
|
||||
{
|
||||
if (_channels.TryGetValue(signal, out var channel))
|
||||
{
|
||||
// 手动触发信号
|
||||
channel.Writer.TryWrite((TriggerType.External,value));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消所有任务
|
||||
/// </summary>
|
||||
public void CancelAllTasks()
|
||||
{
|
||||
foreach (var channel in _channels.Values)
|
||||
{
|
||||
channel.Writer.Complete();
|
||||
}
|
||||
_channels.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建指定信号的 Channel
|
||||
/// </summary>
|
||||
/// <param name="signal">枚举信号标识符</param>
|
||||
/// <returns>对应的 Channel</returns>
|
||||
private Channel<(TriggerType, object)> GetOrCreateChannel(TSignal signal)
|
||||
{
|
||||
return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded<(TriggerType, object)>());
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Library/Utils/DebounceHelper.cs
Normal file
48
Library/Utils/DebounceHelper.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息防抖
|
||||
/// </summary>
|
||||
public static class DebounceHelper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, DateTime> _lastExecutionTimes = new ConcurrentDictionary<string, DateTime>();
|
||||
private static readonly object _lockObject = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以执行操作,根据传入的 key 和 debounceTime 来决定是否允许执行
|
||||
/// </summary>
|
||||
/// <param name="key">操作的唯一标识</param>
|
||||
/// <param name="debounceTimeInMs">防抖时间,单位为毫秒</param>
|
||||
/// <returns>如果可以执行操作,返回 true;否则返回 false</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -312,9 +312,13 @@ namespace Serein.Library.Utils
|
||||
string cacheKey = $"{type.FullName}.{methodInfo.Name}.MethodCallerAsync";
|
||||
return Cache.GetOrAdd(cacheKey, _ => CreateMethodCallerDelegateAsync(type, methodInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建无参数,有返回值(Task<object>)的方法(触发器)
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="methodInfo"></param>
|
||||
/// <returns></returns>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表达式树构建多个参数,有返回值(Task<object>)的方法(触发器)
|
||||
/// </summary>
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.NodeFlow.Tool
|
||||
namespace Serein.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发类型
|
||||
|
||||
61
Library/Utils/MessageIdGenerator.cs
Normal file
61
Library/Utils/MessageIdGenerator.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息ID生成工具
|
||||
/// </summary>
|
||||
public class MessageIdGenerator
|
||||
{
|
||||
private static readonly object _lock = new object();
|
||||
private static int _counter = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 生成一个不重复的标识
|
||||
/// </summary>
|
||||
/// <param name="theme"></param>
|
||||
/// <returns></returns>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
121
Library/Utils/RemoteEnvControl.cs
Normal file
121
Library/Utils/RemoteEnvControl.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 管理远程环境,具备连接、发送消息、停止的功能
|
||||
/// </summary>
|
||||
public class RemoteEnvControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置远程连接IP端口
|
||||
/// </summary>
|
||||
public RemoteEnvControl(string addres, int port, object token)
|
||||
{
|
||||
this.Addres = addres;
|
||||
this.Port = port;
|
||||
this.Token = token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 远程环境的网络地址
|
||||
/// </summary>
|
||||
public string Addres { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 远程环境的对外端口
|
||||
/// </summary>
|
||||
public int Port { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录远程环境必须携带的token(可以为可序列化的JSON对象)
|
||||
/// </summary>
|
||||
public object Token { get; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 连接到远程的客户端
|
||||
/// </summary>
|
||||
public WebSocketClient EnvClient { get; } = new WebSocketClient();
|
||||
|
||||
/// <summary>
|
||||
/// 是否连接到了远程环境
|
||||
/// </summary>
|
||||
public bool IsConnectdRemoteEnv { get => isConnectdRemoteEnv; }
|
||||
private bool isConnectdRemoteEnv = false;
|
||||
|
||||
/// <summary>
|
||||
/// 尝试连接到远程环境
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
/// <param name="theme"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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<T> : SereinConditionResolver where T : struct, IComparable<T>
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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<T> : SereinConditionResolver where T : struct, IComparable<T>
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 字符串工具类
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Serein.NodeFlow.Tool.SereinExpression
|
||||
namespace Serein.Library.Utils.SereinExpression
|
||||
{
|
||||
/// <summary>
|
||||
/// 条件解析抽象类
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
|
||||
namespace Serein.NodeFlow.Tool.SereinExpression
|
||||
namespace Serein.Library.Utils.SereinExpression
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用表达式操作/获取 对象的值
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// IOC管理容器
|
||||
/// </summary>
|
||||
@@ -195,56 +190,60 @@ namespace Serein.Library.Utils
|
||||
public Type Type { get; set; }
|
||||
}
|
||||
private const string FlowBaseClassName = "<>$FlowBaseClass!@#";
|
||||
|
||||
|
||||
public Dictionary<string, List<string>> BuildDependencyTree()
|
||||
{
|
||||
var dependencyMap = new Dictionary<string, List<string>>();
|
||||
//var tmpTypeFullName = new HashSet<string>();
|
||||
//var tmpTypeFullName2 = new HashSet<string>();
|
||||
dependencyMap[FlowBaseClassName] = new List<string>();
|
||||
var dependencyMap = new Dictionary<string, HashSet<string>>();
|
||||
dependencyMap[FlowBaseClassName] = new HashSet<string>();
|
||||
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<string>();
|
||||
// }
|
||||
// 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<string>();
|
||||
dependencyMap[FlowBaseClassName].Add(type.FullName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从类型的有参构造函数中提取类型
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
if (!dependencyMap.TryGetValue(param.FullName, out var hashSet))
|
||||
{
|
||||
hashSet = new HashSet<string>();
|
||||
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<string> GetCreationOrder(Dictionary<string, List<string>> 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); // 完成创建后注入实例需要的特性依赖项
|
||||
|
||||
64
Library/Utils/UIContextOperation.cs
Normal file
64
Library/Utils/UIContextOperation.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 为类库提供了在UI线程上下文操作的方法
|
||||
/// </summary>
|
||||
public class UIContextOperation
|
||||
{
|
||||
private readonly SynchronizationContext context;
|
||||
|
||||
/// <summary>
|
||||
/// 传入UI线程上下文
|
||||
/// </summary>
|
||||
/// <param name="synchronizationContext">线程上下文</param>
|
||||
public UIContextOperation(SynchronizationContext synchronizationContext)
|
||||
{
|
||||
this.context = synchronizationContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步方式进行调用方法
|
||||
/// </summary>
|
||||
/// <param name="uiAction">要执行的UI操作</param>
|
||||
public void Invoke(Action uiAction)
|
||||
{
|
||||
context?.Post(state =>
|
||||
{
|
||||
uiAction?.Invoke();
|
||||
}, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步方式进行调用
|
||||
/// </summary>
|
||||
/// <param name="uiAction">要执行的UI操作</param>
|
||||
/// <returns></returns>
|
||||
public Task InvokeAsync(Action uiAction)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
context?.Post(state =>
|
||||
{
|
||||
try
|
||||
{
|
||||
uiAction?.Invoke();
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.SetException(ex);
|
||||
}
|
||||
}, null);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Model\PlcVarModel.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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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<CommandSignal>
|
||||
{
|
||||
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<Form> forms = new List<Form>();
|
||||
@@ -30,20 +32,53 @@ namespace Net462DllTest.Trigger
|
||||
/// <param name="isTop">是否置顶</param>
|
||||
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<SynchronizationContext>(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);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
88
NodeFlow/Env/EnvMsgTheme.cs
Normal file
88
NodeFlow/Env/EnvMsgTheme.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.NodeFlow.Env
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息主题
|
||||
/// </summary>
|
||||
public static class EnvMsgTheme
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取远程环境信息
|
||||
/// </summary>
|
||||
public const string GetEnvInfo = nameof(GetEnvInfo);
|
||||
/// <summary>
|
||||
/// 尝试开始流程
|
||||
/// </summary>
|
||||
public const string StartFlow = nameof(StartFlow);
|
||||
/// <summary>
|
||||
/// 尝试从指定节点开始运行
|
||||
/// </summary>
|
||||
public const string StartFlowInSelectNode = nameof(StartFlowInSelectNode);
|
||||
/// <summary>
|
||||
/// 尝试结束流程运行
|
||||
/// </summary>
|
||||
public const string ExitFlow = nameof(ExitFlow);
|
||||
/// <summary>
|
||||
/// 尝试移动某个节点
|
||||
/// </summary>
|
||||
public const string MoveNode = nameof(MoveNode);
|
||||
/// <summary>
|
||||
/// 尝试设置流程起点
|
||||
/// </summary>
|
||||
public const string SetStartNode = nameof(SetStartNode);
|
||||
/// <summary>
|
||||
/// 尝试连接两个节点
|
||||
/// </summary>
|
||||
public const string ConnectNode = nameof(ConnectNode);
|
||||
/// <summary>
|
||||
/// 尝试创建节点
|
||||
/// </summary>
|
||||
public const string CreateNode = nameof(CreateNode);
|
||||
/// <summary>
|
||||
/// 尝试移除节点之间的连接关系
|
||||
/// </summary>
|
||||
public const string RemoveConnect = nameof(RemoveConnect);
|
||||
/// <summary>
|
||||
/// 尝试移除节点
|
||||
/// </summary>
|
||||
public const string RemoveNode = nameof(RemoveNode);
|
||||
/// <summary>
|
||||
/// 激活一个触发器
|
||||
/// </summary>
|
||||
public const string ActivateFlipflopNode = nameof(ActivateFlipflopNode);
|
||||
/// <summary>
|
||||
/// 终结一个触发器
|
||||
/// </summary>
|
||||
public const string TerminateFlipflopNode = nameof(TerminateFlipflopNode);
|
||||
|
||||
/// <summary>
|
||||
/// 属性通知
|
||||
/// </summary>
|
||||
public const string ValueNotification = nameof(ValueNotification);
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取项目信息
|
||||
/// </summary>
|
||||
public const string GetProjectInfo = nameof(GetProjectInfo);
|
||||
/// <summary>
|
||||
/// 尝试设置节点中断
|
||||
/// </summary>
|
||||
public const string SetNodeInterrupt = nameof(SetNodeInterrupt);
|
||||
/// <summary>
|
||||
/// 尝试添加中断表达式
|
||||
/// </summary>
|
||||
public const string AddInterruptExpression = nameof(AddInterruptExpression);
|
||||
/// <summary>
|
||||
/// 尝试设置节点/对象监视状态
|
||||
/// </summary>
|
||||
public const string SetMonitor = nameof(SetMonitor);
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
470
NodeFlow/Env/FlowEnvironmentDecorator.cs
Normal file
470
NodeFlow/Env/FlowEnvironmentDecorator.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动管理本地与远程的环境
|
||||
/// </summary>
|
||||
public class FlowEnvironmentDecorator : IFlowEnvironment, ISereinIOC
|
||||
{
|
||||
public FlowEnvironmentDecorator(UIContextOperation uiContextOperation)
|
||||
{
|
||||
flowEnvironment = new FlowEnvironment(uiContextOperation);
|
||||
// 默认使用本地环境
|
||||
currentFlowEnvironment = flowEnvironment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本地环境
|
||||
/// </summary>
|
||||
private readonly FlowEnvironment flowEnvironment;
|
||||
|
||||
/// <summary>
|
||||
/// 远程环境
|
||||
/// </summary>
|
||||
private RemoteFlowEnvironment remoteFlowEnvironment;
|
||||
|
||||
/// <summary>
|
||||
/// 管理当前环境
|
||||
/// </summary>
|
||||
|
||||
private IFlowEnvironment currentFlowEnvironment;
|
||||
|
||||
|
||||
private int _flag = 0; // 使用原子自增代替锁
|
||||
/// <summary>
|
||||
/// 传入false时,将停止数据通知。传入true时,
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public void SetFlag(bool value)
|
||||
{
|
||||
Interlocked.Exchange(ref _flag, value ? 1 : 0);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsFlagSet()
|
||||
{
|
||||
return Interlocked.CompareExchange(ref _flag, 1, 1) == 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 当前环境,用于切换远程与本地环境
|
||||
/// </summary>
|
||||
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<bool> 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<bool> 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<NodeInfo> 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<FlowEnvInfo> GetEnvInfoAsync()
|
||||
{
|
||||
return await currentFlowEnvironment.GetEnvInfoAsync();
|
||||
}
|
||||
|
||||
public async Task<ChannelFlowInterrupt.CancelType> GetOrCreateGlobalInterruptAsync()
|
||||
{
|
||||
return await currentFlowEnvironment.GetOrCreateGlobalInterruptAsync();
|
||||
}
|
||||
|
||||
public async Task<SereinProjectData> 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<bool> 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<T>()
|
||||
{
|
||||
return IOC.Get<T>();
|
||||
}
|
||||
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
return IOC.Get<T>(key);
|
||||
}
|
||||
|
||||
public object Instantiate(Type type)
|
||||
{
|
||||
return IOC.Instantiate(type);
|
||||
}
|
||||
|
||||
public T Instantiate<T>()
|
||||
{
|
||||
return IOC.Instantiate<T>();
|
||||
}
|
||||
|
||||
public ISereinIOC Register(Type type, params object[] parameters)
|
||||
{
|
||||
return IOC.Register(type, parameters);
|
||||
}
|
||||
|
||||
public ISereinIOC Register<T>(params object[] parameters)
|
||||
{
|
||||
return IOC.Register<T>(parameters);
|
||||
}
|
||||
|
||||
public ISereinIOC Register<TService, TImplementation>(params object[] parameters) where TImplementation : TService
|
||||
{
|
||||
return IOC.Register<TService, TImplementation>(parameters);
|
||||
}
|
||||
|
||||
public ISereinIOC Reset()
|
||||
{
|
||||
return IOC.Reset();
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T>(Action<T> action)
|
||||
{
|
||||
return IOC.Run(action);
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2>(Action<T1, T2> action)
|
||||
{
|
||||
return IOC.Run(action);
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3>(Action<T1, T2, T3> action)
|
||||
{
|
||||
return IOC.Run(action);
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action)
|
||||
{
|
||||
return IOC.Run(action);
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4, T5>(Action<T1, T2, T3, T4, T5> action)
|
||||
{
|
||||
return IOC.Run(action);
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4, T5, T6>(Action<T1, T2, T3, T4, T5, T6> action)
|
||||
{
|
||||
return IOC.Run(action);
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4, T5, T6, T7>(Action<T1, T2, T3, T4, T5, T6, T7> action)
|
||||
{
|
||||
return IOC.Run(action);
|
||||
}
|
||||
|
||||
public ISereinIOC Run<T1, T2, T3, T4, T5, T6, T7, T8>(Action<T1, T2, T3, T4, T5, T6, T7, T8> action)
|
||||
{
|
||||
return IOC.Run(action);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
187
NodeFlow/Env/FlowFunc.cs
Normal file
187
NodeFlow/Env/FlowFunc.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 流程环境需要的扩展方法
|
||||
/// </summary>
|
||||
public static class FlowFunc
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建节点
|
||||
/// </summary>
|
||||
/// <param name="env">运行环境</param>
|
||||
/// <param name="nodeControlType">节点类型</param>
|
||||
/// <param name="methodDetails">方法描述</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从节点信息读取节点类型
|
||||
/// </summary>
|
||||
/// <param name="nodeInfo"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序集封装依赖
|
||||
/// </summary>
|
||||
/// <param name="library"></param>
|
||||
/// <returns></returns>
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发器运行后状态转为对应的后继分支类别
|
||||
/// </summary>
|
||||
/// <param name="flowStateType"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
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("未定义的流程状态")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断 触发器节点 是否存在上游分支
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// 从节点类型枚举中转为对应的 Model 类型
|
||||
///// </summary>
|
||||
///// <param name="nodeControlType"></param>
|
||||
///// <returns></returns>
|
||||
//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;
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
||||
129
NodeFlow/Env/MsgControllerOfClient.cs
Normal file
129
NodeFlow/Env/MsgControllerOfClient.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 客户端的消息管理(用于处理服务端的响应)
|
||||
/// </summary>
|
||||
|
||||
[AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey, DataKey = FlowEnvironment.DataKey)]
|
||||
public class MsgControllerOfClient : ISocketHandleModule
|
||||
{
|
||||
public Guid HandleGuid => new Guid();
|
||||
private readonly Func<string, object?, Task> SendCommandAsync;
|
||||
private readonly RemoteFlowEnvironment remoteFlowEnvironment;
|
||||
|
||||
public MsgControllerOfClient(RemoteFlowEnvironment remoteFlowEnvironment, Func<string, object?, Task> func)
|
||||
{
|
||||
this.remoteFlowEnvironment = remoteFlowEnvironment;
|
||||
SendCommandAsync = func;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 发送请求并等待远程环境响应
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException">超时触发</exception>
|
||||
public async Task SendAsync(string signal, object? senddata = null, int debounceTimeInMs = 100)
|
||||
{
|
||||
if (!DebounceHelper.CanExecute(signal, debounceTimeInMs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
await SendCommandAsync.Invoke(signal, senddata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送请求并等待远程环境响应
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException">超时触发</exception>
|
||||
public async Task<TResult> SendAndWaitDataAsync<TResult>(string signal, object? senddata = null, int debounceTimeInMs = 50)
|
||||
{
|
||||
_ = SendCommandAsync.Invoke(signal, senddata);
|
||||
return await remoteFlowEnvironment.WaitData<TResult>(signal);
|
||||
#if DEBUG
|
||||
|
||||
if (DebounceHelper.CanExecute(signal, debounceTimeInMs))
|
||||
{
|
||||
_ = SendCommandAsync.Invoke(signal, senddata);
|
||||
return await remoteFlowEnvironment.WaitData<TResult>(signal);
|
||||
|
||||
//(var type, var result) = await remoteFlowEnvironment.WaitDataWithTimeoutAsync<TResult>(signal, TimeSpan.FromSeconds(150));
|
||||
//if (type == TriggerType.Overtime)
|
||||
//{
|
||||
// throw new NotImplementedException("超时触发");
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// return result;
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
return default;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
#region 消息接收
|
||||
|
||||
/// <summary>
|
||||
/// 远程环境发来项目信息
|
||||
/// </summary>
|
||||
/// <param name="flowEnvInfo"></param>
|
||||
[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);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 远程环境发来项目信息
|
||||
/// </summary>
|
||||
/// <param name="sereinProjectData"></param>
|
||||
[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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
443
NodeFlow/Env/MsgControllerOfServer.cs
Normal file
443
NodeFlow/Env/MsgControllerOfServer.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务端的消息管理(用于处理客户端的请求)
|
||||
/// </summary>
|
||||
[AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey, DataKey = FlowEnvironment.DataKey)]
|
||||
public class MsgControllerOfServer : ISocketHandleModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 受控环境
|
||||
/// </summary>
|
||||
public IFlowEnvironment environment;
|
||||
|
||||
/// <summary>
|
||||
/// WebSocket处理
|
||||
/// </summary>
|
||||
public Guid HandleGuid { get; } = new Guid();
|
||||
|
||||
/// <summary>
|
||||
/// <para>表示是否正在控制远程</para>
|
||||
/// <para>Local control remote env</para>
|
||||
/// </summary>
|
||||
public bool IsLcR { get; set; }
|
||||
/// <summary>
|
||||
/// <para>表示是否受到远程控制</para>
|
||||
/// <para>Remote control local env</para>
|
||||
/// </summary>
|
||||
public bool IsRcL { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 流程环境远程管理服务
|
||||
/// </summary>
|
||||
private WebSocketServer FlowEnvRemoteWebSocket;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 启动不带Token验证的远程服务
|
||||
/// </summary>
|
||||
public MsgControllerOfServer(IFlowEnvironment environment)
|
||||
{
|
||||
this.environment = environment;
|
||||
FlowEnvRemoteWebSocket ??= new WebSocketServer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动带token验证的远程服务
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
public MsgControllerOfServer(IFlowEnvironment environment, string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
Console.WriteLine("当前没有设置token,但使用了token验证的服务端");
|
||||
|
||||
}
|
||||
this.environment = environment;
|
||||
FlowEnvRemoteWebSocket ??= new WebSocketServer(token, OnInspectionAuthorized);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#region 基本方法
|
||||
/// <summary>
|
||||
/// 启动远程
|
||||
/// </summary>
|
||||
/// <param name="port"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束远程管理
|
||||
/// </summary>
|
||||
[AutoSocketHandle]
|
||||
public void StopRemoteServer()
|
||||
{
|
||||
try
|
||||
{
|
||||
FlowEnvRemoteWebSocket.Stop();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("结束远程管理异常:" + ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证远程token
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取发送消息的委托
|
||||
/// </summary>
|
||||
/// <param name="SendAsync"></param>
|
||||
private void OnResultSendMsgFunc(Func<string, Task> SendAsync)
|
||||
{
|
||||
// 从受控环境向主控环境发送消息。
|
||||
Func<string, object, Task> 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
|
||||
|
||||
/// <summary>
|
||||
/// 异步运行
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.StartFlow)]
|
||||
private async Task StartAsync()
|
||||
{
|
||||
var uiContextOperation = environment.IOC.Get<UIContextOperation>();
|
||||
await environment.StartAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从远程环境运行选定的节点
|
||||
/// </summary>
|
||||
/// <param name="startNodeGuid"></param>
|
||||
/// <returns></returns>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.StartFlowInSelectNode)]
|
||||
private async Task StartAsyncInSelectNode(string startNodeGuid)
|
||||
{
|
||||
await environment.StartAsyncInSelectNode(startNodeGuid);
|
||||
}
|
||||
/// <summary>
|
||||
/// 结束流程
|
||||
/// </summary>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ExitFlow)]
|
||||
private void ExitFlow()
|
||||
{
|
||||
environment.ExitFlow();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 激活全局触发器
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ActivateFlipflopNode)]
|
||||
private void ActivateFlipflopNode(string nodeGuid)
|
||||
{
|
||||
environment.ActivateFlipflopNode(nodeGuid);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 关闭全局触发器
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.TerminateFlipflopNode)]
|
||||
private void TerminateFlipflopNode(string nodeGuid)
|
||||
{
|
||||
environment.TerminateFlipflopNode(nodeGuid);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前环境信息(远程连接)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetEnvInfo)]
|
||||
private async Task<FlowEnvInfo> GetEnvInfoAsync()
|
||||
{
|
||||
return await environment.GetEnvInfoAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载项目文件
|
||||
/// </summary>
|
||||
/// <param name="flowEnvInfo">环境信息</param>
|
||||
// [AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)]
|
||||
private void LoadProject(FlowEnvInfo flowEnvInfo)
|
||||
{
|
||||
environment.LoadProject(flowEnvInfo, "");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 连接远程环境
|
||||
/// </summary>
|
||||
/// <param name="addres">远程环境地址</param>
|
||||
/// <param name="port">远程环境端口</param>
|
||||
/// <param name="token">密码</param>
|
||||
// [AutoSocketHandle]
|
||||
public async Task<(bool, RemoteEnvControl)> ConnectRemoteEnv(string addres, int port, string token)
|
||||
{
|
||||
return await environment.ConnectRemoteEnv(addres, port, token);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 退出远程环境
|
||||
/// </summary>
|
||||
// [AutoSocketHandle]
|
||||
public void ExitRemoteEnv()
|
||||
{
|
||||
Console.WriteLine("暂未实现远程退出远程环境");
|
||||
IsLcR = false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 序列化当前项目的依赖信息、节点信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)]
|
||||
public async Task<SereinProjectData> GetProjectInfoAsync()
|
||||
{
|
||||
return await environment.GetProjectInfoAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件路径中加载DLL
|
||||
/// </summary>
|
||||
/// <param name="dllPath"></param>
|
||||
/// <returns></returns>
|
||||
// [AutoSocketHandle(ThemeValue = EnvMsgTheme)]
|
||||
public void LoadDll(string dllPath)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// 移除DLL
|
||||
/// </summary>
|
||||
/// <param name="assemblyFullName"></param>
|
||||
/// <returns></returns>
|
||||
// [AutoSocketHandle(ThemeValue = EnvMsgTheme)]
|
||||
public bool RemoteDll(string assemblyFullName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从远程环境创建节点
|
||||
/// </summary>
|
||||
/// <param name="nodeType"></param>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="mdInfo">如果是表达式节点条件节点,该项为null</param>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateNode,ArgNotNull = false)]
|
||||
public async Task<NodeInfo> CreateNode([Needful] string nodeType, [Needful] PositionOfUI position, MethodDetailsInfo? mdInfo = null)
|
||||
{
|
||||
if (!EnumHelper.TryConvertEnum<NodeControlType>(nodeType, out var nodeControlType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var nodeInfo = await environment.CreateNodeAsync(nodeControlType, position, mdInfo); // 监听到客户端创建节点的请求
|
||||
return nodeInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从远程环境移除节点
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveNode)]
|
||||
public void RemoveNode(string nodeGuid)
|
||||
{
|
||||
environment.RemoveNode(nodeGuid);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 连接节点
|
||||
/// </summary>
|
||||
/// <param name="fromNodeGuid">起始节点</param>
|
||||
/// <param name="toNodeGuid">目标节点</param>
|
||||
/// <param name="connectionType">连接关系</param>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectNode)]
|
||||
public void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
|
||||
{
|
||||
environment.ConnectNodeAsync(fromNodeGuid, toNodeGuid, connectionType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除连接关系
|
||||
/// </summary>
|
||||
/// <param name="fromNodeGuid">起始节点Guid</param>
|
||||
/// <param name="toNodeGuid">目标节点Guid</param>
|
||||
/// <param name="connectionType">连接关系</param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveConnect)]
|
||||
public void RemoveConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
|
||||
{
|
||||
environment.RemoveConnect(fromNodeGuid, toNodeGuid, connectionType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移动了某个节点(远程插件使用)
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.MoveNode)]
|
||||
public void MoveNode(string nodeGuid, double x, double y)
|
||||
{
|
||||
environment.MoveNode(nodeGuid, x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置起点控件
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetStartNode)]
|
||||
public void SetStartNode(string nodeGuid)
|
||||
{
|
||||
environment.SetStartNode(nodeGuid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 中断指定节点,并指定中断等级。
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid">被中断的目标节点Guid</param>
|
||||
/// <param name="interruptClass">中断级别</param>
|
||||
/// <returns>操作是否成功</returns>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetNodeInterrupt)]
|
||||
public async Task<bool> SetNodeInterruptAsync(string nodeGuid, string interruptClass)
|
||||
{
|
||||
|
||||
if (!EnumHelper.TryConvertEnum<InterruptClass>(interruptClass, out var @class))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return await this.environment.SetNodeInterruptAsync(nodeGuid, @class);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 添加表达式中断
|
||||
/// </summary>
|
||||
/// <param name="key">如果是节点,传入Guid;如果是对象,传入类型FullName</param>
|
||||
/// <param name="expression">合法的条件表达式</param>
|
||||
/// <returns></returns>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.AddInterruptExpression)]
|
||||
public async Task<bool> AddInterruptExpression(string key, string expression)
|
||||
{
|
||||
return await environment.AddInterruptExpressionAsync(key, expression);
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置对象的监视状态
|
||||
/// </summary>
|
||||
/// <param name="key">如果是节点,传入Guid;如果是对象,传入类型FullName</param>
|
||||
/// <param name="isMonitor">ture监视对象;false取消对象监视</param>
|
||||
/// <returns></returns>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetMonitor)]
|
||||
public void SetMonitorObjState(string key, bool isMonitor)
|
||||
{
|
||||
environment.SetMonitorObjState(key, isMonitor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点数据更改
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="value"></param>
|
||||
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ValueNotification)]
|
||||
public async Task ValueNotification(string nodeGuid, string path, string value)
|
||||
{
|
||||
await environment.NotificationNodeValueChangeAsync(nodeGuid, path, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
578
NodeFlow/Env/RemoteFlowEnvironment.cs
Normal file
578
NodeFlow/Env/RemoteFlowEnvironment.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 远程流程环境
|
||||
/// </summary>
|
||||
public class RemoteFlowEnvironment : ChannelFlowTrigger<string>, IFlowEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接到远程环境后切换到的环境接口实现
|
||||
/// </summary>
|
||||
/// <param name="webSocketClient">连接到远程环境的客户端</param>
|
||||
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<string, object?, Task> SendCommandAsync;
|
||||
private readonly RemoteEnvControl remoteEnvControl;
|
||||
private readonly MsgControllerOfClient msgClient;
|
||||
private readonly ConcurrentDictionary<string, MethodDetails> MethodDetailss = [];
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 环境加载的节点集合
|
||||
/// Node Guid - Node Model
|
||||
/// </summary>
|
||||
private Dictionary<string, NodeModelBase> 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<SereinProjectData> GetProjectInfoAsync()
|
||||
{
|
||||
var prjectInfo = await msgClient.SendAndWaitDataAsync<SereinProjectData>(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<FlowEnvInfo> GetEnvInfoAsync()
|
||||
{
|
||||
|
||||
var envInfo = await msgClient.SendAndWaitDataAsync<FlowEnvInfo>(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<bool> ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
|
||||
{
|
||||
//_ = RemoteEnv.SendAsync(EnvMsgTheme.ConnectNode, new
|
||||
//{
|
||||
// fromNodeGuid,
|
||||
// toNodeGuid,
|
||||
// connectionType = connectionType.ToString(),
|
||||
//});
|
||||
var result = await msgClient.SendAndWaitDataAsync<bool>(EnvMsgTheme.ConnectNode, new
|
||||
{
|
||||
fromNodeGuid,
|
||||
toNodeGuid,
|
||||
connectionType = connectionType.ToString(),
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<NodeInfo> CreateNodeAsync(NodeControlType nodeControlType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null)
|
||||
{
|
||||
var nodeInfo = await msgClient.SendAndWaitDataAsync<NodeInfo>(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<bool> SetNodeInterruptAsync(string nodeGuid, InterruptClass interruptClass)
|
||||
{
|
||||
var state = await msgClient.SendAndWaitDataAsync<bool>(EnvMsgTheme.SetNodeInterrupt, // 设置节点中断
|
||||
new
|
||||
{
|
||||
nodeGuid,
|
||||
interruptClass = interruptClass.ToString(),
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> AddInterruptExpressionAsync(string key, string expression)
|
||||
{
|
||||
var state = await msgClient.SendAndWaitDataAsync<bool>(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<string>();
|
||||
return (false, exps);
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await msgClient.SendAndWaitDataAsync<(bool, string[])>(EnvMsgTheme.SetNodeInterrupt, // 检查并获取节点/对象是否正在监视、以及监视的表达式
|
||||
new
|
||||
{
|
||||
key,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Task<ChannelFlowInterrupt.CancelType> 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(),
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 流程启动器
|
||||
/// </summary>
|
||||
public class FlowStarter
|
||||
{
|
||||
|
||||
public FlowStarter()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 控制触发器
|
||||
/// 控制全局触发器的结束
|
||||
/// </summary>
|
||||
private CancellationTokenSource _flipFlopCts = null;
|
||||
private CancellationTokenSource? _flipFlopCts;
|
||||
|
||||
/// <summary>
|
||||
/// 是否停止启动
|
||||
@@ -45,11 +28,8 @@ namespace Serein.NodeFlow
|
||||
/// <summary>
|
||||
/// 结束运行时需要执行的方法
|
||||
/// </summary>
|
||||
private Func<Task> ExitAction { get; set; } = null;
|
||||
/// <summary>
|
||||
/// 运行的上下文
|
||||
/// </summary>
|
||||
private IDynamicContext Context { get; set; } = null;
|
||||
private Func<Task>? ExitAction { get; set; }
|
||||
|
||||
private void CheckStartState()
|
||||
{
|
||||
if (IsStopStart)
|
||||
@@ -62,11 +42,17 @@ namespace Serein.NodeFlow
|
||||
/// <summary>
|
||||
/// 从选定的节点开始运行
|
||||
/// </summary>
|
||||
/// <param name="env"></param>
|
||||
/// <param name="startNode"></param>
|
||||
/// <returns></returns>
|
||||
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<WebApiServer>(web => {
|
||||
web?.Stop();
|
||||
});
|
||||
env.IOC.Run<WebSocketServer>(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<object, object[], object>)dd.EmitDelegate).Invoke(md.ActingInstance, [Context]);
|
||||
}
|
||||
|
||||
TerminateAllGlobalFlipflop();
|
||||
|
||||
@@ -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<SingleActionNode> ActionNodes;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 组合动作节点(用于动作区域)
|
||||
/// </summary>
|
||||
public CompositeActionNode(List<SingleActionNode> actionNodes)
|
||||
public CompositeActionNode(IFlowEnvironment environment, List<SingleActionNode> actionNodes):base(environment)
|
||||
{
|
||||
ActionNodes = actionNodes;
|
||||
}
|
||||
@@ -30,6 +30,7 @@ namespace Serein.NodeFlow.Model
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public override NodeInfo? ToInfo()
|
||||
{
|
||||
if (MethodDetails is null) return null;
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public class CompositeConditionNode : NodeModelBase
|
||||
{
|
||||
public CompositeConditionNode(IFlowEnvironment environment):base(environment)
|
||||
{
|
||||
|
||||
}
|
||||
public List<SingleConditionNode> ConditionNodes { get; } = [];
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public class SingleActionNode : NodeModelBase
|
||||
{
|
||||
|
||||
public SingleActionNode(IFlowEnvironment environment):base(environment)
|
||||
{
|
||||
|
||||
}
|
||||
public override Parameterdata[] GetParameterdatas()
|
||||
{
|
||||
if (base.MethodDetails.ParameterDetailss.Length > 0)
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public class SingleConditionNode : NodeModelBase
|
||||
{
|
||||
public SingleConditionNode(IFlowEnvironment environment):base(environment)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为自定义参数
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public class SingleExpOpNode : NodeModelBase
|
||||
{
|
||||
public SingleExpOpNode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 表达式
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 触发器节点
|
||||
/// </summary>
|
||||
public class SingleFlipflopNode : NodeModelBase
|
||||
{
|
||||
//public override async Task<object?> Executing(IDynamicContext context)
|
||||
//public override Task<object?> ExecutingAsync(IDynamicContext context)
|
||||
//{
|
||||
// NextOrientation = Library.Enums.ConnectionType.IsError;
|
||||
// RuningException = new FlipflopException ("无法以非await/async的形式调用触发器");
|
||||
// return null;
|
||||
//}
|
||||
|
||||
public SingleFlipflopNode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 执行触发器进行等待触发
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public override async Task<object?> ExecutingAsync(IDynamicContext context)
|
||||
{
|
||||
#region 执行前中断
|
||||
@@ -76,10 +77,11 @@ namespace Serein.NodeFlow.Model
|
||||
// flipflopTask?.Dispose();
|
||||
}
|
||||
}
|
||||
public static object GetContextValueDynamic(dynamic context)
|
||||
{
|
||||
return context.Value; // dynamic 会在运行时处理类型
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取触发器参数
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override Parameterdata[] GetParameterdatas()
|
||||
{
|
||||
if (base.MethodDetails.ParameterDetailss.Length > 0)
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.0.13</Version>
|
||||
<Version>1.0.14</Version>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -3,31 +3,37 @@ using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace Serein.Workbench.tool
|
||||
namespace Serein.NodeFlow.Tool
|
||||
{
|
||||
/// <summary>
|
||||
/// 可以捕获类库输出的打印输出
|
||||
/// 捕获Console输出
|
||||
/// </summary>
|
||||
public class LogTextWriter : TextWriter
|
||||
{
|
||||
private readonly Action<string> logAction; // 更新日志UI的委托
|
||||
private readonly StringWriter stringWriter = new(); // 缓存日志内容
|
||||
private readonly Channel<string> logChannel = Channel.CreateUnbounded<string>(); // 日志管道
|
||||
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<string> logAction, Action clearTextBoxAction)
|
||||
/// <summary>
|
||||
/// 定义输出委托与清除输出内容委托
|
||||
/// </summary>
|
||||
/// <param name="logAction"></param>
|
||||
public LogTextWriter(Action<string> logAction)
|
||||
{
|
||||
this.logAction = logAction;
|
||||
this.clearTextBoxAction = clearTextBoxAction;
|
||||
|
||||
// 异步启动日志处理任务,不阻塞主线程
|
||||
Task.Run(ProcessLogQueueAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编码类型
|
||||
/// </summary>
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
|
||||
|
||||
public override void Write(char value)
|
||||
{
|
||||
stringWriter.Write(value);
|
||||
@@ -54,31 +60,34 @@ namespace Serein.Workbench.tool
|
||||
EnqueueLog();
|
||||
}
|
||||
|
||||
// 将日志加入通道
|
||||
/// <summary>
|
||||
/// 将日志加入通道
|
||||
/// </summary>
|
||||
private void EnqueueLog()
|
||||
{
|
||||
var log = stringWriter.ToString();
|
||||
stringWriter.GetStringBuilder().Clear();
|
||||
|
||||
if (!logChannel.Writer.TryWrite(log))
|
||||
{
|
||||
// 如果写入失败(不太可能),则直接丢弃日志或处理
|
||||
}
|
||||
}
|
||||
|
||||
// 异步处理日志队列
|
||||
/// <summary>
|
||||
/// 异步处理日志队列
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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; // 重置计数器
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
/// <returns></returns>
|
||||
private static string GetExplicitTypeName(Type type)
|
||||
{
|
||||
|
||||
return type switch
|
||||
{
|
||||
Type t when t.IsEnum => "Select",
|
||||
|
||||
462
Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs
Normal file
462
Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 一个增量源生成器,用于为带有自定义 MyClassAttribute 特性的类中的字段生成带有自定义 set 行为的属性。
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class MyPropertyGenerator : IIncrementalGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化生成器,定义需要执行的生成逻辑。
|
||||
/// </summary>
|
||||
/// <param name="context">增量生成器的上下文,用于注册生成逻辑。</param>
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 为给定的类生成带有自定义 set 行为的属性。
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="classSyntax">类的语法树节点。</param>
|
||||
/// <param name="namespaceName">类所在的命名空间。</param>
|
||||
/// <param name="className">类的名称。</param>
|
||||
/// <returns>生成的 C# 属性代码。</returns>
|
||||
private string GenerateProperties(ClassDeclarationSyntax classSyntax,
|
||||
Dictionary<string, Dictionary<string, object>> 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<FieldDeclarationSyntax>());
|
||||
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(); // 返回生成的代码
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类所在的命名空间。
|
||||
/// </summary>
|
||||
/// <param name="classSyntax">类的语法节点。</param>
|
||||
/// <returns>命名空间的名称,或者 "GlobalNamespace" 如果没有命名空间声明。</returns>
|
||||
private string GetNamespace(SyntaxNode classSyntax)
|
||||
{
|
||||
// 查找最近的命名空间声明
|
||||
var namespaceDeclaration = classSyntax.Ancestors().OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
|
||||
return namespaceDeclaration?.Name.ToString() ?? "GlobalNamespace";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class DocumentationCommentExtensions
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 为 XML 文档注释中的 <summary> 标签插入指定的文本
|
||||
/// </summary>
|
||||
/// <param name="triviaList">语法节点的 LeadingTrivia 或 TrailingTrivia 列表</param>
|
||||
/// <param name="comment">要插入的注释文本</param>
|
||||
/// <returns>修改后的 Trivia 列表</returns>
|
||||
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;
|
||||
|
||||
// 查找 <summary> 标签
|
||||
var summaryNode = structuredTrivia.DescendantNodes()
|
||||
.OfType<XmlElementSyntax>()
|
||||
.FirstOrDefault(e => e.StartTag.Name.LocalName.Text == "summary");
|
||||
|
||||
if (summaryNode != null)
|
||||
{
|
||||
//// 在 <summary> 标签内插入指定的注释文本
|
||||
//var generatorComment = SyntaxFactory.XmlText(comment).WithLeadingTrivia(SyntaxFactory.Whitespace(" "));
|
||||
//var updatedSummaryNode = summaryNode.AddContent(generatorComment);
|
||||
|
||||
//// 用新的 <summary> 标签替换原来的
|
||||
//var updatedStructuredTrivia = structuredTrivia.ReplaceNode(summaryNode, updatedSummaryNode);
|
||||
|
||||
//// 用新的注释替换原来的
|
||||
//var updatedTrivia = SyntaxFactory.Trivia(updatedStructuredTrivia);
|
||||
//triviaList = triviaList.Replace(docCommentTrivia, updatedTrivia);
|
||||
|
||||
// 创建 <para> 段落注释
|
||||
var paraElement = SyntaxFactory.XmlElement(
|
||||
SyntaxFactory.XmlElementStartTag(SyntaxFactory.XmlName("para")), // 起始标签 <para>
|
||||
SyntaxFactory.SingletonList<XmlNodeSyntax>( // 内容
|
||||
SyntaxFactory.XmlText(comment).WithLeadingTrivia(SyntaxFactory.Whitespace(" "))
|
||||
),
|
||||
SyntaxFactory.XmlElementEndTag(SyntaxFactory.XmlName("para")) // 结束标签 </para>
|
||||
);
|
||||
|
||||
|
||||
// 将 <para> 插入到 <summary> 中
|
||||
var updatedSummaryNode = summaryNode.AddContent(paraElement);
|
||||
// 用新的 <summary> 标签替换原来的
|
||||
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<string, Dictionary<string, object>> BuildCacheOfClass(this INamedTypeSymbol classSymbol)
|
||||
{
|
||||
Dictionary<string, Dictionary<string, object>> attributesOfClass = new Dictionary<string, Dictionary<string, object>>();
|
||||
var tattribute = classSymbol.GetAttributes();
|
||||
foreach (var cad in tattribute)
|
||||
{
|
||||
var attributeName = cad.AttributeClass?.Name;
|
||||
if (!attributesOfClass.TryGetValue(attributeName, out var attributeInfo))
|
||||
{
|
||||
attributeInfo = new Dictionary<string, object>();
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 字段名称转换为属性名称
|
||||
/// </summary>
|
||||
/// <param name="field"></param>
|
||||
/// <returns>遵循属性命名规范的新名称</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 判断字段是否为只读
|
||||
/// </summary>
|
||||
/// <param name="fieldDeclaration">字段的语法节点</param>
|
||||
/// <returns>如果字段是只读的,返回 true;否则返回 false</returns>
|
||||
public static bool IsReadonly(this FieldDeclarationSyntax fieldDeclaration)
|
||||
{
|
||||
// 判断字段是否有 readonly 修饰符
|
||||
return fieldDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <para>构建字段的缓存信息</para>
|
||||
/// <para>第1层:字段名称 - 特性集合</para>
|
||||
/// <para>第2层:特性名称 - 特性属性集合</para>
|
||||
/// <para>第3层:特性属性名称 - 对应的字面量</para>
|
||||
/// </summary>
|
||||
/// <param name="fieldDeclarationSyntaxes"></param>
|
||||
/// <returns>关于字段的特性缓存信息</returns>
|
||||
public static Dictionary<FieldDeclarationSyntax, Dictionary<string, Dictionary<string, string>>> BuildCacheOfField(IEnumerable<FieldDeclarationSyntax> fieldDeclarationSyntaxes)
|
||||
{
|
||||
Dictionary<FieldDeclarationSyntax, Dictionary<string, Dictionary<string, string>>> FieldData = new Dictionary<FieldDeclarationSyntax, Dictionary<string, Dictionary<string, string>>>();
|
||||
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<string, Dictionary<string, string>>(); // 发现一个新字段
|
||||
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<string, string>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>通过条件检查缓存的信息,决定是否添加代码</para>
|
||||
/// <para>首先检查是否存在该特性,如果不存在,返回 false。</para>
|
||||
/// <para>然后检查是否存在属性,如果不存在,返回 false</para>
|
||||
/// <para>如果存在属性,则返回属性对应的值与 comparisonValue 进行比较,返回
|
||||
/// <para></para>
|
||||
/// <para>若只传入 attributeName 参数,则只会检查是否存在该特性</para>
|
||||
/// <para>若只传入 attributeName与attributePropertyName 参数,则只会检查是否存在该特性的该属性</para>
|
||||
/// </summary>
|
||||
/// <param name="dict">缓存的特性信息</param>
|
||||
/// <param name="attributeName">查询的特性名称</param>
|
||||
/// <param name="attributePropertyName">查询的特性属性名称</param>
|
||||
/// <param name="comparisonValue">比较值</param>
|
||||
/// <returns>如果存在查询项,返回 true ,否则返回 false</returns>
|
||||
public static bool Search(this Dictionary<string, Dictionary<string, string>> 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);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加代码
|
||||
/// </summary>
|
||||
/// <param name="sb">字符串构建器</param>
|
||||
/// <param name="retractCount">缩进次数(4个空格)</param>
|
||||
/// <param name="code">要添加的代码</param>
|
||||
/// <returns>字符串构建器本身</returns>
|
||||
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<string, Dictionary<string, object>> 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user