From 29f2be5c8086aaf3940e6b528333a4fa5ea0cc3d Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Sat, 26 Jul 2025 19:36:54 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=9A=E8=BF=87Emit=E4=BC=98=E5=8C=96Script?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E7=9A=84=E8=A7=A3=E9=87=8A=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=EF=BC=9B=E5=87=BA=E4=BA=8E=E5=90=8E=E6=9C=9F=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E5=90=91=EF=BC=8C=E6=9A=82=E6=97=B6=E9=9A=90?= =?UTF-8?q?=E8=97=8F=E8=A1=A8=E8=BE=BE=E5=BC=8F=E8=8A=82=E7=82=B9=E3=80=81?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E8=A1=A8=E8=BE=BE=E5=BC=8F=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E3=80=81=E5=85=A8=E5=B1=80=E6=95=B0=E6=8D=AE=E8=8A=82=E7=82=B9?= =?UTF-8?q?=EF=BC=9B=E6=B5=81=E7=A8=8B=E5=9B=BE=E8=BD=ACc#=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=96=B0=E5=A2=9E=E5=AF=B9=E4=BA=8EScript=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E7=9A=84=E6=94=AF=E6=8C=81=EF=BC=8CScript=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E7=8E=B0=E5=9C=A8=E5=8F=AF=E4=BB=A5=E5=8E=9F=E7=94=9F?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E4=B8=BAC#=E4=BB=A3=E7=A0=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Extension/FlowModelExtension.cs | 2 +- Library/FlowNode/DelegateDetails.cs | 239 +++++++-- Library/FlowNode/ParameterDetails.cs | 25 +- Library/Network/Modbus/HexExtensions.cs | 17 + Library/Network/Modbus/IModbusClient.cs | 61 +++ Library/Network/Modbus/ModbusClientFactory.cs | 119 +++++ Library/Network/Modbus/ModbusException.cs | 35 ++ Library/Network/Modbus/ModbusRequest.cs | 25 + Library/Network/Modbus/ModbusRtuClient.cs | 323 ++++++++++++ Library/Network/Modbus/ModbusRtuRequest.cs | 19 + Library/Network/Modbus/ModbusTcpClient.cs | 51 +- Library/Network/Modbus/ModbusTcpRequest.cs | 26 +- Library/Network/Modbus/ModbusUdpClient.cs | 260 ++++++++++ Library/Serein.Library.csproj | 3 +- Library/Utils/EmitHelper.cs | 307 +++++++++++- .../SereinExpression/SereinConditionParser.cs | 119 +++-- NodeFlow/Model/Node/NodeModelBaseFunc.cs | 51 +- NodeFlow/Model/Node/SingleScriptNode.cs | 76 ++- NodeFlow/Serein.NodeFlow.csproj | 1 + NodeFlow/Services/FlowCoreGenerateService.cs | 460 +++++++++++++++--- README.md | 74 --- Serein.Script/SereinSciptException.cs | 17 - Serein.Script/SereinSciptParserException.cs | 32 ++ Serein.Script/SereinScript.cs | 10 +- Serein.Script/SereinScriptInterpreter.cs | 155 ++++-- Serein.Script/SereinScriptMethodInfo.cs | 22 + Serein.Script/SereinScriptToCsharpScript.cs | 51 +- Workbench/Node/View/ActionNodeControl.xaml.cs | 4 - Workbench/Node/View/ScriptNodeControl.xaml | 1 + .../ViewModel/ScriptNodeControlViewModel.cs | 14 + Workbench/ViewModels/MainMenuBarViewModel.cs | 2 +- Workbench/Views/BaseNodesView.xaml | 6 +- 32 files changed, 2175 insertions(+), 432 deletions(-) create mode 100644 Library/Network/Modbus/HexExtensions.cs create mode 100644 Library/Network/Modbus/IModbusClient.cs create mode 100644 Library/Network/Modbus/ModbusClientFactory.cs create mode 100644 Library/Network/Modbus/ModbusException.cs create mode 100644 Library/Network/Modbus/ModbusRequest.cs create mode 100644 Library/Network/Modbus/ModbusRtuClient.cs create mode 100644 Library/Network/Modbus/ModbusRtuRequest.cs create mode 100644 Library/Network/Modbus/ModbusUdpClient.cs delete mode 100644 Serein.Script/SereinSciptException.cs create mode 100644 Serein.Script/SereinSciptParserException.cs create mode 100644 Serein.Script/SereinScriptMethodInfo.cs diff --git a/Library/Extension/FlowModelExtension.cs b/Library/Extension/FlowModelExtension.cs index 6467e5d..d71b314 100644 --- a/Library/Extension/FlowModelExtension.cs +++ b/Library/Extension/FlowModelExtension.cs @@ -342,7 +342,7 @@ namespace Serein.Library args[i] = mainArgTasks[i].Result; } - // 并发处理 params 参数 + // 并发处理 params 类型的入参参数 if (paramsArgs != null) { int paramsLength = paramsArgs.Length; diff --git a/Library/FlowNode/DelegateDetails.cs b/Library/FlowNode/DelegateDetails.cs index 0b9841f..65e1dbb 100644 --- a/Library/FlowNode/DelegateDetails.cs +++ b/Library/FlowNode/DelegateDetails.cs @@ -11,41 +11,81 @@ using static Serein.Library.Utils.EmitHelper; namespace Serein.Library { /// - /// Emit创建的委托描述,用于WebApi、WebSocket、NodeFlow动态调用方法的场景。 + /// 通过 Emit 创建委托,代替反射调用方法,实现高性能的动态调用。 /// 一般情况下你无须内部细节,只需要调用 Invoke() 方法即可。 /// public class DelegateDetails { + private readonly EmitType emitType = EmitType.None; + + /// + /// 创建的委托类型 + /// + public enum EmitType + { + /// + /// 默认 + /// + None, + /// + /// 方法调用 + /// + MethodInvoke, + /// + /// 字段赋值 + /// + FieldSetter, + /// + /// 字段取值 + /// + FieldGetter, + /// + /// 属性赋值 + /// + PropertySetter, + /// + /// 属性取值 + /// + PropertyGetter, + /// + /// 集合取值 + /// + CollectionGetter, + /// + /// 集合赋值 + /// + CollectionSetter + } + + public enum GSType + { + Get, + Set, + } + + /// /// 根据方法信息构建Emit委托 /// /// public DelegateDetails(MethodInfo methodInfo) { + emitType = EmitType.MethodInvoke; var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var emitDelegate); _emitMethodInfo = emitMethodType; _emitDelegate = emitDelegate; - - SetFunc(); - } - - - private void SetFunc() - { + methodType = _emitMethodInfo.EmitMethodType; if (_emitDelegate is Func> hasResultTask) { - this.hasResultTask = hasResultTask; - funcType = 2; + this.methodHasResultTask = hasResultTask; } else if (_emitDelegate is Func task) { - this.task = task; - funcType = 1; + this.methodTask = task; } else if (_emitDelegate is Func func) { - this.func = func; - funcType = 0; + this.methodInvoke = func; } else { @@ -54,9 +94,97 @@ namespace Serein.Library } - public Func> hasResultTask; - public Func task; - public Func func; + + /// + /// 根据字段信息构建Emit取/赋值委托 + /// + /// 字段信息 + /// 是否为 get,如果不是,则为 set + public DelegateDetails(FieldInfo fieldInfo, GSType gsType) + { + if (gsType == GSType.Get) + { + emitType = EmitType.FieldGetter; + getter = EmitHelper.CreateFieldGetter(fieldInfo); + + } + else if (gsType == GSType.Set) + { + emitType = EmitType.FieldSetter; + setter = EmitHelper.CreateFieldSetter(fieldInfo); + } + else + { + throw new NotSupportedException("错误的构建类型"); + } + + } + + /// + /// 根据字段信息构建Emit取/赋值委托 + /// + /// 字段信息 + /// 是否为 get,如果不是,则为 set + public DelegateDetails(PropertyInfo propertyInfo, GSType gsType) + { + if (gsType == GSType.Get) + { + emitType = EmitType.PropertyGetter; + getter = EmitHelper.CreatePropertyGetter(propertyInfo); + + } + else if (gsType == GSType.Set) + { + emitType = EmitType.PropertySetter; + setter = EmitHelper.CreatePropertySetter(propertyInfo); + } + else + { + throw new NotSupportedException("错误的构建类型"); + } + + } + + /// + /// 目前提供了创建集合取值/赋值委托 + /// + /// 类型信息 + /// 操作类型 + public DelegateDetails(Type type, EmitType emitType) + { + if (emitType == EmitType.CollectionSetter) + { + emitType = EmitType.CollectionSetter; + collectionSetter = EmitHelper.CreateCollectionSetter(type); + + } + else if (emitType == EmitType.CollectionGetter) + { + emitType = EmitType.CollectionGetter; + collectionGetter = EmitHelper.CreateCollectionGetter(type); + } + else + { + throw new NotSupportedException("错误的构建类型"); + } + + } + + + + + + private Func collectionGetter= null; + private Action collectionSetter = null; + + + private Func getter = null; + private Action setter = null; + + + private Func> methodHasResultTask = null; + private Func methodTask = null; + private Func methodInvoke = null; /*/// @@ -72,12 +200,9 @@ namespace Serein.Library private Delegate _emitDelegate; private EmitMethodInfo _emitMethodInfo; - /// - /// 0是普通 - /// 1是异步无返回值 - /// 2是异步有返回值 - /// - private int funcType; + + + private EmitMethodType methodType; /// /// 该Emit委托的相应信息 @@ -96,50 +221,65 @@ namespace Serein.Library //public EmitMethodType EmitMethodType { get => _emitMethodType; } - - public async Task AutoInvokeAsync(object[] args) - { - if (_emitMethodInfo.IsStatic) - { - return await InvokeAsync(null, args); - } - else - { - var obj = Activator.CreateInstance(_emitMethodInfo.DeclaringType); - return await InvokeAsync(obj, args); - } - throw new Exception("Not static method"); - } - /// /// 使用的实例必须能够正确调用该委托,传入的参数也必须符合方法入参信息。 /// - /// 拥有符合委托签名的方法信息的实例 - /// 如果方法没有入参,也需要传入一个空数组 - /// void方法自动返回null + /// 拥有符合委托签名的实例 + /// 如果不需要入参,也需要传入一个空数组,而不能为 null + /// void方法、setter自动返回null public async Task InvokeAsync(object instance, object[] args) + { + if (emitType == EmitType.MethodInvoke) + { + return await MethodInvoke(instance, args); + } + else if (emitType == EmitType.PropertyGetter || emitType == EmitType.FieldGetter) + { + return getter(instance); + } + else if (emitType == EmitType.PropertySetter || emitType == EmitType.FieldSetter) + { + setter(instance, args[0]); + return null; + } + else if (emitType == EmitType.CollectionGetter) + { + return collectionGetter(instance, args[0]); + } + else if (emitType == EmitType.CollectionSetter) + { + collectionSetter(instance, args[0], args[1]); + return null; + } + else + { + throw new NotSupportedException("当前委托类型不支持 InvokeAsync 方法。请使用其他方法调用。"); + } + } + + private async Task MethodInvoke(object instance, object[] args) { if (args is null) { args = Array.Empty(); } - if(_emitMethodInfo.IsStatic) + if (_emitMethodInfo.IsStatic) { instance = null; } object result = null; - if(funcType == 0) + if (methodType == EmitMethodType.Func) { - result = func.Invoke(instance, args); + result = methodInvoke.Invoke(instance, args); } - else if (funcType == 2) + else if (methodType == EmitMethodType.TaskHasResult) { - result = await hasResultTask(instance, args); - } - else if (funcType == 1) + result = await methodHasResultTask(instance, args); + } + else if (methodType == EmitMethodType.Task) { - await task(instance, args); + await methodTask(instance, args); result = null; } else @@ -148,5 +288,6 @@ namespace Serein.Library } return result; } + } } diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 2f391e6..91a896d 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -35,7 +35,7 @@ namespace Serein.Library /// /// 是否为显式参数(固定值/表达式) - /// 如果为 true ,则使用UI输入的文本值作为入参数据。 + /// 如果为 true ,则使用输入的文本值作为入参数据。 /// 如果为 false ,则在当前流程上下文中,根据 ArgDataSourceNodeGuid 查找到对应节点,并根据 ArgDataSourceNodeGuid 判断如何获取其返回的数据,以此作为入参数据。 /// [PropertyInfo(IsNotification = true)] @@ -224,23 +224,14 @@ namespace Serein.Library if (IsExplicitData && !DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase)) return DataValue.ToConvert(DataType); - /* // 4. 枚举绑定类型 - if (ExplicitType is not null && ExplicitType.IsEnum && DataType != ExplicitType) - { - var resultEnum = Enum.Parse(ExplicitType, DataValue); - var boundType = EnumHelper.GetBoundValue(ExplicitType, resultEnum, attr => attr.Value) as Type; - if (boundType != null) - return NodeModel.Env.IOC.CreateObject(boundType); - }*/ - - // 5. 来自其他节点 + // 4. 来自其他节点 object inputParameter = null; var env = NodeModel.Env; if (ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) { - var prevNode = context.GetPreviousNode(NodeModel.Guid); - inputParameter = prevNode != null ? context.GetFlowData(prevNode)?.Value : null; + var prevNodeGuid = context.GetPreviousNode(NodeModel.Guid); + inputParameter = prevNodeGuid != null ? context.GetFlowData(prevNodeGuid)?.Value : null; } else { @@ -250,7 +241,7 @@ namespace Serein.Library if (!env.TryGetNodeModel(ArgDataSourceNodeGuid, out var sourceNode)) throw new Exception($"[arg{Index}] 节点[{ArgDataSourceNodeGuid}]不存在"); - if (sourceNode.IsPublic + if (sourceNode.IsPublic // 如果运行上一节点是[FlowCall]节点(则从该节点获取入参) && env.TryGetNodeModel(prevNodeGuid, out var prevNode) && prevNode.ControlType == NodeControlType.FlowCall && env.TryGetNodeModel(context.GetPreviousNode(NodeModel.Guid), out var sourceNodeTemp) @@ -259,13 +250,13 @@ namespace Serein.Library if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) inputParameter = context.GetFlowData(sourceNode.Guid)?.Value; - else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) + else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) // 立刻执行目标节点获取参数 inputParameter = (await sourceNode.ExecutingAsync(context, CancellationToken.None)).Value; else throw new Exception("无效的 ArgDataSourceType"); } - // 6. 表达式处理 + // 5. 表达式处理 if (IsExplicitData && DataValue.StartsWith("@", StringComparison.OrdinalIgnoreCase)) { var lower = DataValue.ToLowerInvariant(); @@ -275,7 +266,7 @@ namespace Serein.Library } } - // 7. 类型转换 + // 6. 类型转换 if (!DataType.IsValueType && inputParameter is null) throw new Exception($"[arg{Index}] 参数不能为null"); diff --git a/Library/Network/Modbus/HexExtensions.cs b/Library/Network/Modbus/HexExtensions.cs new file mode 100644 index 0000000..505c687 --- /dev/null +++ b/Library/Network/Modbus/HexExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Network.Modbus +{ + public static class HexExtensions + { + public static string ToHexString(this byte[] data, string separator = " ") + { + if (data == null) return string.Empty; + return BitConverter.ToString(data).Replace("-", separator); + } + } +} diff --git a/Library/Network/Modbus/IModbusClient.cs b/Library/Network/Modbus/IModbusClient.cs new file mode 100644 index 0000000..0751c68 --- /dev/null +++ b/Library/Network/Modbus/IModbusClient.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; + +namespace Serein.Library.Network.Modbus +{ + /// + /// Modbus 客户端通用接口 (TCP/RTU 通用) + /// + public interface IModbusClient : IDisposable + { + /// + /// 报文发送时 + /// + Action OnTx { get; set; } + + /// + /// 接收到报文时 + /// + Action OnRx { get; set; } + + /// + /// 读取线圈状态 (0x01) + /// + Task ReadCoils(ushort startAddress, ushort quantity); + + /// + /// 读取离散输入状态 (0x02) + /// + Task ReadDiscreteInputs(ushort startAddress, ushort quantity); + + /// + /// 读取保持寄存器 (0x03) + /// + Task ReadHoldingRegisters(ushort startAddress, ushort quantity); + + /// + /// 读取输入寄存器 (0x04) + /// + Task ReadInputRegisters(ushort startAddress, ushort quantity); + + /// + /// 写单个线圈 (0x05) + /// + Task WriteSingleCoil(ushort address, bool value); + + /// + /// 写单个寄存器 (0x06) + /// + Task WriteSingleRegister(ushort address, ushort value); + + /// + /// 写多个线圈 (0x0F) + /// + Task WriteMultipleCoils(ushort startAddress, bool[] values); + + /// + /// 写多个寄存器 (0x10) + /// + Task WriteMultipleRegisters(ushort startAddress, ushort[] values); + } +} diff --git a/Library/Network/Modbus/ModbusClientFactory.cs b/Library/Network/Modbus/ModbusClientFactory.cs new file mode 100644 index 0000000..bab5118 --- /dev/null +++ b/Library/Network/Modbus/ModbusClientFactory.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Network.Modbus +{ + public static class ModbusClientFactory + { + private static readonly char[] separator = new[] { ':' }; + + /// + /// 创建 Modbus 客户端实例 + /// + /// + /// 连接字符串格式: + /// TCP示例:"tcp:192.168.1.100:502" + /// UCP示例:"ucp:192.168.1.100:502" + /// RTU示例:"rtu:COM3:9600:1" (格式:rtu:串口名:波特率:从站地址) + /// + public static IModbusClient Create(string connectionString) + { + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentException("connectionString 不能为空"); + var parts = connectionString.Split(separator, StringSplitOptions.RemoveEmptyEntries); + //var parts = connectionString.Split(':',options: StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < 2) + throw new ArgumentException("connectionString 格式错误"); + + var protocol = parts[0].ToLower(); + + if (protocol == "tcp") + { + // tcp:host:port + if (parts.Length < 3) + throw new ArgumentException("TCP格式应为 tcp:host:port"); + + string host = parts[1]; + if (!int.TryParse(parts[2], out int port)) + port = 502; // 默认端口 + + return new ModbusTcpClient(host, port); + } + else if (protocol == "ucp") + { + // ucp:host:port + if (parts.Length < 3) + throw new ArgumentException("TCP格式应为 tcp:host:port"); + + string host = parts[1]; + if (!int.TryParse(parts[2], out int port)) + port = 502; // 默认端口 + + return new ModbusUdpClient(host, port); + } + else if (protocol == "rtu") + { + // rtu:portName:baudRate:slaveId + if (parts.Length < 4) + throw new ArgumentException("RTU格式应为 rtu:portName:baudRate:slaveId"); + + string portName = parts[1]; + if (!int.TryParse(parts[2], out int baudRate)) + baudRate = 9600; + + if (!byte.TryParse(parts[3], out byte slaveId)) + slaveId = 1; + + return new ModbusRtuClient(portName, baudRate, slaveId: slaveId); + } + else + { + throw new NotSupportedException($"不支持的协议类型: {protocol}"); + } + } + + + /// + /// 创建 Modbus TCP 客户端 + /// + /// 服务器地址 + /// 端口,默认502 + public static ModbusTcpClient CreateTcpClient(string host, int port = 502) + { + return new ModbusTcpClient(host, port); + } + + /// + /// 创建 Modbus TCP 客户端 + /// + /// 服务器地址 + /// 端口,默认502 + public static ModbusUdpClient CreateUdpClient(string host, int port = 502) + { + return new ModbusUdpClient(host, port); + } + + /// + /// 创建 Modbus RTU 客户端 + /// + /// 串口名,比如 "COM3" + /// 波特率,默认9600 + /// 校验,默认None + /// 数据位,默认8 + /// 停止位,默认1 + /// 从站地址,默认1 + public static ModbusRtuClient CreateRtuClient(string portName, + int baudRate = 9600, + Parity parity = Parity.None, + int dataBits = 8, + StopBits stopBits = StopBits.One, + byte slaveId = 1) + { + return new ModbusRtuClient(portName, baudRate, parity, dataBits, stopBits, slaveId); + } + } +} diff --git a/Library/Network/Modbus/ModbusException.cs b/Library/Network/Modbus/ModbusException.cs new file mode 100644 index 0000000..f03f1d5 --- /dev/null +++ b/Library/Network/Modbus/ModbusException.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Network.Modbus +{ + public class ModbusException : Exception + { + public byte FunctionCode { get; } + public byte ExceptionCode { get; } + + public ModbusException(byte functionCode, byte exceptionCode) + : base($"Modbus异常码=0x{functionCode:X2},0x{exceptionCode:X2}({GetExceptionMessage(exceptionCode)})") + { + FunctionCode = functionCode; + ExceptionCode = exceptionCode; + } + + private static string GetExceptionMessage(byte code) => code switch + { + 0x01 => "非法功能。确认功能码是否被目标设备支持;检查设备固件版本是否过低;修改主站请求为设备支持的功能码", // 功能码错误 + 0x02 => "非法数据地址。检查主站请求的寄存器地址和长度是否越界;确保设备配置的寄存器数量正确", // 数据地址错误 + 0x03 => "非法数据值。检查写入的数值是否在设备支持的范围内;核对协议文档中对应寄存器的取值要求", // 数据值错误 + 0x04 => "从站设备故障。检查设备运行状态和日志;尝试重启设备;排查硬件或内部程序错误", // 从设备故障 + 0x05 => "确认。主站需通过轮询或延时机制等待处理完成,再次查询结果", // 确认 + 0x06 => "从站设备忙。增加请求重试延时;避免高频率发送编程指令", // 从设备忙 + 0x08 => "存储奇偶性差错。尝试重新发送请求;如错误持续出现,检查存储器硬件或文件一致性", // 内存奇偶校验错误 + 0x0A => "不可用网关路径。检查网关配置和负载;确认目标设备的网络连接可用性", // 网关路径不可用 + 0x0B => "网关目标设备响应失败。检查目标设备是否在线;检查网关的路由配置与网络连接", // 网关目标设备未响应 + _ => $"未知错误" // 未知错误 + }; + } +} diff --git a/Library/Network/Modbus/ModbusRequest.cs b/Library/Network/Modbus/ModbusRequest.cs new file mode 100644 index 0000000..31fb91b --- /dev/null +++ b/Library/Network/Modbus/ModbusRequest.cs @@ -0,0 +1,25 @@ + +using Serein.Library.Network.Modbus; +using System.Threading.Tasks; + +namespace Serein.Library.Network.Modbus +{ + public class ModbusRequest + { + /// + /// 功能码 + /// + public ModbusFunctionCode FunctionCode { get; set; } + + /// + /// PDU (Protocol Data Unit) 数据,不包括从站地址和CRC + /// + public byte[] PDU { get; set; } + + /// + /// 异步任务完成源,用于等待响应 + /// + public TaskCompletionSource Completion { get; set; } + } + +} diff --git a/Library/Network/Modbus/ModbusRtuClient.cs b/Library/Network/Modbus/ModbusRtuClient.cs new file mode 100644 index 0000000..9055367 --- /dev/null +++ b/Library/Network/Modbus/ModbusRtuClient.cs @@ -0,0 +1,323 @@ +using Serein.Library.Network.Modbus; +using System; +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.IO.Ports; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; + +namespace Serein.Library.Network.Modbus +{ + + + public class ModbusRtuClient : IModbusClient + { + public Action OnTx { get; set; } + public Action OnRx { get; set; } + + + private readonly SerialPort _serialPort; + private readonly SemaphoreSlim _requestLock = new SemaphoreSlim(1, 1); + private readonly byte _slaveId; + + private readonly CancellationTokenSource _cts = new(); + + public ModbusRtuClient(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One, byte slaveId = 1) + { + _slaveId = slaveId; + _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits) + { + ReadTimeout = 1000, + WriteTimeout = 1000 + }; + _serialPort.Open(); + + } + + #region 功能码封装 + + public async Task ReadCoils(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var response = await SendAsync(ModbusFunctionCode.ReadCoils, pdu); + return ParseDiscreteBits(response, quantity); + } + + public async Task ReadDiscreteInputs(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var response = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu); + return ParseDiscreteBits(response, quantity); + } + + public async Task ReadHoldingRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var response = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu); + return ParseRegisters(response, quantity); + } + + public async Task ReadInputRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var response = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu); + return ParseRegisters(response, quantity); + } + + public async Task WriteSingleCoil(ushort address, bool value) + { + var pdu = new byte[] + { + (byte)(address >> 8), + (byte)(address & 0xFF), + value ? (byte)0xFF : (byte)0x00, + 0x00 + }; + await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu); + } + + public async Task WriteSingleRegister(ushort address, ushort value) + { + var pdu = new byte[] + { + (byte)(address >> 8), + (byte)(address & 0xFF), + (byte)(value >> 8), + (byte)(value & 0xFF) + }; + await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu); + } + + + public async Task WriteMultipleCoils(ushort startAddress, bool[] values) + { + if (values == null || values.Length == 0) + throw new ArgumentException("values 不能为空"); + + int byteCount = (values.Length + 7) / 8; // 需要多少字节 + byte[] coilData = new byte[byteCount]; + + for (int i = 0; i < values.Length; i++) + { + if (values[i]) + coilData[i / 8] |= (byte)(1 << (i % 8)); // 设置对应位 + } + + var pdu = new List + { + (byte)(startAddress >> 8), // 起始地址高字节 + (byte)(startAddress & 0xFF), // 起始地址低字节 + (byte)(values.Length >> 8), // 数量高字节 + (byte)(values.Length & 0xFF), // 数量低字节 + (byte)coilData.Length // 数据字节数 + }; + pdu.AddRange(coilData); + + await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray()); + } + + public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values) + { + if (values == null || values.Length == 0) + throw new ArgumentException("values 不能为空"); + + var arrlen = 5 + values.Length * 2; + var pdu = new byte[arrlen]; + + pdu[0] = (byte)(startAddress >> 8); // 起始地址高字节 + pdu[1] = (byte)(startAddress & 0xFF); // 起始地址低字节 + pdu[2] = (byte)(values.Length >> 8); // 寄存器数量高字节 + pdu[3] = (byte)(values.Length & 0xFF); // 寄存器数量低字节 + pdu[4] = (byte)(values.Length * 2); // 数据字节数 + + // 添加寄存器数据(每个寄存器 2 字节:高字节在前) + var index = 5; + foreach(var val in values) + { + pdu[index++] = (byte)(val >> 8); + pdu[index++] = (byte)(val & 0xFF); + + } + + /* var pdu = new List + { + (byte)(startAddress >> 8), // 起始地址高字节 + (byte)(startAddress & 0xFF), // 起始地址低字节 + (byte)(values.Length >> 8), // 寄存器数量高字节 + (byte)(values.Length & 0xFF), // 寄存器数量低字节 + (byte)(values.Length * 2) // 数据字节数 + }; + + + foreach (var val in values) + { + pdu.Add((byte)(val >> 8)); + pdu.Add((byte)(val & 0xFF)); + }*/ + + await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu); + } + + #endregion + + #region 核心通信 + + public async Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu) + { + await _requestLock.WaitAsync(); + try + { + // 构造 RTU 帧 + byte[] frame = BuildFrame(_slaveId, (byte)functionCode, pdu); + OnTx?.Invoke(frame); // 触发发送日志 + await _serialPort.BaseStream.WriteAsync(frame, 0, frame.Length, _cts.Token); + await _serialPort.BaseStream.FlushAsync(_cts.Token); + + // 接收响应 + var response = await ReceiveResponseAsync(); + OnRx?.Invoke(response); // 触发接收日志 + // 检查功能码是否异常响应 + if ((response[1] & 0x80) != 0) + { + byte exceptionCode = response[2]; + throw new ModbusException(response[1], exceptionCode); + } + + + return response; + } + finally + { + _requestLock.Release(); + } + } + + + + /// + /// 接收响应 + /// + private async Task ReceiveResponseAsync() + { + var buffer = new byte[256]; + int offset = 0; + + while (true) + { + int read = await _serialPort.BaseStream.ReadAsync(buffer, offset, buffer.Length - offset, _cts.Token); + offset += read; + + // 最小RTU帧:地址(1) + 功能码(1) + 数据(N) + CRC(2) + if (offset >= 5) + { + int frameLength = offset; + if (!ValidateCrc(buffer, 0, frameLength)) + throw new IOException("CRC 校验失败"); + + byte[] response = new byte[frameLength - 2]; + Array.Copy(buffer, 0, response, 0, frameLength - 2); + return response; + } + } + } + + private byte[] BuildFrame(byte slaveAddr, byte functionCode, byte[] pdu) + { + var frame = new byte[2 + pdu.Length + 2]; // 地址 + 功能码 + PDU + CRC + frame[0] = slaveAddr; + frame[1] = functionCode; + Array.Copy(pdu, 0, frame, 2, pdu.Length); + ushort crc = Crc16(frame, 0, frame.Length - 2); + frame[frame.Length - 2] = (byte)(crc & 0xFF); + frame[frame.Length - 1] = (byte)(crc >> 8); + return frame; + } + + private static bool ValidateCrc(byte[] buffer, int offset, int length) + { + ushort crcCalc = Crc16(buffer, offset, length - 2); + ushort crcRecv = (ushort)(buffer[length - 2] | (buffer[length - 1] << 8)); + return crcCalc == crcRecv; + } + + #endregion + + #region PDU与解析 + + private byte[] BuildReadPdu(ushort startAddress, ushort quantity) + { + + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress); // 起始地址高低字节 + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity); // 读取数量高低字节 + return buffer; + } + + private bool[] ParseDiscreteBits(byte[] pdu, ushort count) + { + int byteCount = pdu[2]; // 第2字节是后续的字节数量 + int dataIndex = 3; // 数据从第3字节开始(0-based) + + var result = new bool[count]; + + for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++) + { + if (bitPos == 8) + { + bitPos = 0; + bytePos++; + } + result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0; + } + return result; + } + + private ushort[] ParseRegisters(byte[] pdu, ushort count) + { + var result = new ushort[count]; + int dataStart = 3; // 数据从第3字节开始 + + for (int i = 0; i < count; i++) + { + int offset = dataStart + i * 2; + result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]); + } + + return result; + } + + #endregion + + #region CRC16 + private static ushort Crc16(byte[] data, int offset, int length) + { + const ushort polynomial = 0xA001; + ushort crc = 0xFFFF; + + for (int i = offset; i < offset + length; i++) + { + crc ^= data[i]; + for (int j = 0; j < 8; j++) + { + if ((crc & 0x0001) != 0) + crc = (ushort)((crc >> 1) ^ polynomial); + else + crc >>= 1; + } + } + return crc; + } + #endregion + + public void Dispose() + { + _cts.Cancel(); + _serialPort?.Close(); + } + + + } +} diff --git a/Library/Network/Modbus/ModbusRtuRequest.cs b/Library/Network/Modbus/ModbusRtuRequest.cs new file mode 100644 index 0000000..f9e381a --- /dev/null +++ b/Library/Network/Modbus/ModbusRtuRequest.cs @@ -0,0 +1,19 @@ + +using Serein.Library.Network.Modbus; + + +namespace Serein.Library.Network.Modbus +{ + /// + /// Modbus RTU 请求实体(串口模式下无效) + /// + public sealed class ModbusRtuRequest : ModbusRequest + { + /// + /// 从站地址(1~247) + /// + public byte SlaveAddress { get; set; } + } + +} + diff --git a/Library/Network/Modbus/ModbusTcpClient.cs b/Library/Network/Modbus/ModbusTcpClient.cs index d0a4d23..56e7e19 100644 --- a/Library/Network/Modbus/ModbusTcpClient.cs +++ b/Library/Network/Modbus/ModbusTcpClient.cs @@ -4,19 +4,21 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; -using System.Text; -using System.Threading.Channels; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; -using System.ComponentModel.DataAnnotations; + namespace Serein.Library.Network.Modbus { /// /// Modbus TCP 客户端 /// - public class ModbusTcpClient : IDisposable + public class ModbusTcpClient : IModbusClient { + public Action OnTx { get; set; } + public Action OnRx { get; set; } + /// /// 消息通道 /// @@ -264,10 +266,15 @@ namespace Serein.Library.Network.Modbus /// private async Task ProcessQueueAsync() { - var request = await _channel.Reader.ReadAsync(); - byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU); - await _stream.WriteAsync(packet, 0, packet.Length); - await _stream.FlushAsync(); + while (_tcpClient.Connected) + { + var request = await _channel.Reader.ReadAsync(); + byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU); + OnTx?.Invoke(packet); // 触发发送日志 + await _stream.WriteAsync(packet, 0, packet.Length); + await _stream.FlushAsync(); + } + } /// @@ -275,6 +282,8 @@ namespace Serein.Library.Network.Modbus /// /// 功能码 /// 内容 + /// 超时时间 + /// 最大重发次数 /// /// public Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu) @@ -305,12 +314,8 @@ namespace Serein.Library.Network.Modbus var buffer = new byte[1024]; while (true) { -#if NET462 int len = await _stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); -#else - int len = await _stream.ReadAsync(buffer.AsMemory(0, buffer.Length)).ConfigureAwait(false); -#endif - + //int len = await _stream.ReadAsync(buffer.AsMemory(0, buffer.Length)).ConfigureAwait(false); if (len == 0) return; // 连接关闭 if (len < 6) @@ -320,6 +325,7 @@ namespace Serein.Library.Network.Modbus } + ushort protocolId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(2, 2)); if (protocolId != 0x0000) { @@ -339,7 +345,22 @@ namespace Serein.Library.Network.Modbus if (_pendingRequests.TryRemove(transactionId, out var tcs)) { var responsePdu = new ReadOnlySpan(buffer, 6, dataLength).ToArray(); - tcs.SetResult(responsePdu); // 如需 byte[] 则 ToArray + if (OnRx is not null) + { + var packet = new ReadOnlySpan(buffer, 0, 6 + dataLength).ToArray(); + OnRx?.Invoke(packet); // 触发接收日志 + } + + // 检查是否异常响应 + if ((responsePdu[1] & 0x80) != 0) + { + byte exceptionCode = responsePdu[2]; + tcs.SetException(new ModbusException(responsePdu[1], exceptionCode)); + } + else + { + tcs.SetResult(responsePdu); + } } else { @@ -400,4 +421,6 @@ namespace Serein.Library.Network.Modbus _tcpClient?.Close(); } } + + } diff --git a/Library/Network/Modbus/ModbusTcpRequest.cs b/Library/Network/Modbus/ModbusTcpRequest.cs index 969e0e9..cf8241b 100644 --- a/Library/Network/Modbus/ModbusTcpRequest.cs +++ b/Library/Network/Modbus/ModbusTcpRequest.cs @@ -1,32 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - + namespace Serein.Library.Network.Modbus { /// /// Modbus TCP 请求实体 /// - public class ModbusTcpRequest + public class ModbusTcpRequest : ModbusRequest { - /// - /// 事务ID - /// public ushort TransactionId { get; set; } - /// - /// 功能码 - /// - public ModbusFunctionCode FunctionCode { get; set; } - /// - /// PDU 数据 - /// - public byte[] PDU { get; set; } - /// - /// 请求的完成源,用于异步等待响应 - /// - public TaskCompletionSource Completion { get; set; } } -} +} \ No newline at end of file diff --git a/Library/Network/Modbus/ModbusUdpClient.cs b/Library/Network/Modbus/ModbusUdpClient.cs new file mode 100644 index 0000000..fb2f6ed --- /dev/null +++ b/Library/Network/Modbus/ModbusUdpClient.cs @@ -0,0 +1,260 @@ +using System; +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Net; +using System.Text; +using System.Threading.Channels; +using System.Threading.Tasks; +using System.Threading; + +namespace Serein.Library.Network.Modbus +{ + public class ModbusUdpClient : IModbusClient + { + public Action OnTx { get; set; } + public Action OnRx { get; set; } + + private readonly Channel _channel = Channel.CreateUnbounded(); + private readonly UdpClient _udpClient; + private readonly IPEndPoint _remoteEndPoint; + private readonly ConcurrentDictionary> _pendingRequests = new(); + private int _transactionId = 0; + + public ModbusUdpClient(string host, int port = 502) + { + _remoteEndPoint = new IPEndPoint(IPAddress.Parse(host), port); + _udpClient = new UdpClient(); + _udpClient.Connect(_remoteEndPoint); + + _ = ProcessQueueAsync(); + _ = ReceiveLoopAsync(); + } + + #region 功能码封装 + public async Task ReadCoils(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadCoils, pdu); + return ParseDiscreteBits(responsePdu, quantity); + } + + public async Task ReadDiscreteInputs(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadDiscreteInputs, pdu); + return ParseDiscreteBits(responsePdu, quantity); + } + + public async Task ReadHoldingRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadHoldingRegisters, pdu); + return ParseRegisters(responsePdu, quantity); + } + + public async Task ReadInputRegisters(ushort startAddress, ushort quantity) + { + var pdu = BuildReadPdu(startAddress, quantity); + var responsePdu = await SendAsync(ModbusFunctionCode.ReadInputRegisters, pdu); + return ParseRegisters(responsePdu, quantity); + } + + public async Task WriteSingleCoil(ushort address, bool value) + { + var pdu = new byte[] + { + (byte)(address >> 8), + (byte)(address & 0xFF), + value ? (byte)0xFF : (byte)0x00, + 0x00 + }; + await SendAsync(ModbusFunctionCode.WriteSingleCoil, pdu); + } + + public async Task WriteSingleRegister(ushort address, ushort value) + { + var pdu = new byte[] + { + (byte)(address >> 8), + (byte)(address & 0xFF), + (byte)(value >> 8), + (byte)(value & 0xFF) + }; + await SendAsync(ModbusFunctionCode.WriteSingleRegister, pdu); + } + + public async Task WriteMultipleCoils(ushort startAddress, bool[] values) + { + int byteCount = (values.Length + 7) / 8; + byte[] data = new byte[byteCount]; + + for (int i = 0; i < values.Length; i++) + { + if (values[i]) + data[i / 8] |= (byte)(1 << (i % 8)); + } + + var pdu = new List + { + (byte)(startAddress >> 8), + (byte)(startAddress & 0xFF), + (byte)(values.Length >> 8), + (byte)(values.Length & 0xFF), + (byte)data.Length + }; + pdu.AddRange(data); + + await SendAsync(ModbusFunctionCode.WriteMultipleCoils, pdu.ToArray()); + } + + public async Task WriteMultipleRegisters(ushort startAddress, ushort[] values) + { + var pdu = new List + { + (byte)(startAddress >> 8), + (byte)(startAddress & 0xFF), + (byte)(values.Length >> 8), + (byte)(values.Length & 0xFF), + (byte)(values.Length * 2) + }; + + foreach (var val in values) + { + pdu.Add((byte)(val >> 8)); + pdu.Add((byte)(val & 0xFF)); + } + + await SendAsync(ModbusFunctionCode.WriteMultipleRegister, pdu.ToArray()); + } + #endregion + + #region 核心通信 + + public Task SendAsync(ModbusFunctionCode functionCode, byte[] pdu) + { + int id = Interlocked.Increment(ref _transactionId); + var transactionId = (ushort)(id % ushort.MaxValue); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var request = new ModbusTcpRequest + { + TransactionId = transactionId, + FunctionCode = functionCode, + PDU = pdu, + Completion = tcs + }; + + _pendingRequests[transactionId] = tcs; + _channel.Writer.TryWrite(request); + return tcs.Task; + } + + private async Task ProcessQueueAsync() + { + while (true) + { + var request = await _channel.Reader.ReadAsync(); + byte[] packet = BuildPacket(request.TransactionId, 0x01, (byte)request.FunctionCode, request.PDU); + OnTx?.Invoke(packet); + await _udpClient.SendAsync(packet, packet.Length); + } + } + + private async Task ReceiveLoopAsync() + { + while (true) + { + UdpReceiveResult result = await _udpClient.ReceiveAsync(); + var buffer = result.Buffer; + + if (buffer.Length < 6) continue; + + ushort transactionId = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(0, 2)); + if (_pendingRequests.TryRemove(transactionId, out var tcs)) + { + OnRx?.Invoke(buffer); + var responsePdu = new ReadOnlySpan(buffer, 6, buffer.Length - 6).ToArray(); + + if ((responsePdu[1] & 0x80) != 0) + { + byte exceptionCode = responsePdu[2]; + tcs.SetException(new ModbusException(responsePdu[1], exceptionCode)); + } + else + { + tcs.SetResult(responsePdu); + } + } + } + } + + private byte[] BuildPacket(ushort transactionId, byte unitId, byte functionCode, byte[] pduData) + { + int pduLength = 1 + pduData.Length; + int totalLength = 7 + pduLength; + + Span packet = totalLength <= 256 ? stackalloc byte[totalLength] : new byte[totalLength]; + packet[0] = (byte)(transactionId >> 8); + packet[1] = (byte)(transactionId); + packet[2] = 0; packet[3] = 0; + ushort length = (ushort)(pduLength + 1); + packet[4] = (byte)(length >> 8); + packet[5] = (byte)(length); + packet[6] = unitId; + packet[7] = functionCode; + pduData.AsSpan().CopyTo(packet.Slice(8)); + return packet.ToArray(); + } + #endregion + + private byte[] BuildReadPdu(ushort startAddress, ushort quantity) + { + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(0, 2), startAddress); + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(2, 2), quantity); + return buffer; + } + + private bool[] ParseDiscreteBits(byte[] pdu, ushort count) + { + var result = new bool[count]; + int byteCount = pdu[2]; + int dataIndex = 3; + + for (int i = 0, bytePos = 0, bitPos = 0; i < count; i++, bitPos++) + { + if (bitPos == 8) + { + bitPos = 0; + bytePos++; + } + result[i] = ((pdu[dataIndex + bytePos] >> bitPos) & 0x01) != 0; + } + return result; + } + + private ushort[] ParseRegisters(byte[] pdu, ushort count) + { + var result = new ushort[count]; + int dataStart = 3; + + for (int i = 0; i < count; i++) + { + int offset = dataStart + i * 2; + result[i] = (ushort)((pdu[offset] << 8) | pdu[offset + 1]); + } + return result; + } + + public void Dispose() + { + foreach (var tcs in _pendingRequests.Values) + tcs.TrySetCanceled(); + + _udpClient?.Dispose(); + } + } +} diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index e5f070c..b6d829b 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -45,9 +45,8 @@ - - + diff --git a/Library/Utils/EmitHelper.cs b/Library/Utils/EmitHelper.cs index 955a3f8..28666f1 100644 --- a/Library/Utils/EmitHelper.cs +++ b/Library/Utils/EmitHelper.cs @@ -210,7 +210,310 @@ namespace Serein.Library.Utils }; } - - } + + /// + /// 创建字段 Getter 委托:Func<object, object> + /// + public static Func CreateFieldGetter(FieldInfo fieldInfo) + { + if (fieldInfo == null) + throw new ArgumentNullException(nameof(fieldInfo)); + + var method = new DynamicMethod( + fieldInfo.Name + "_Get", + typeof(object), + new[] { typeof(object) }, + fieldInfo.DeclaringType, + true); + + ILGenerator il = method.GetILGenerator(); + + if (!fieldInfo.IsStatic) + { + // 加载实例 + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); + il.Emit(OpCodes.Ldfld, fieldInfo); + } + else + { + il.Emit(OpCodes.Ldsfld, fieldInfo); + } + + // 如果是值类型,装箱 + if (fieldInfo.FieldType.IsValueType) + il.Emit(OpCodes.Box, fieldInfo.FieldType); + + il.Emit(OpCodes.Ret); + + return (Func)method.CreateDelegate(typeof(Func)); + } + + /// + /// 创建字段 Setter 委托:Action<object, object> + /// + public static Action CreateFieldSetter(FieldInfo fieldInfo) + { + if (fieldInfo == null) + throw new ArgumentNullException(nameof(fieldInfo)); + if (fieldInfo.IsInitOnly) + throw new InvalidOperationException($"字段 {fieldInfo.Name} 是只读字段,无法设置值。"); + + var method = new DynamicMethod( + fieldInfo.Name + "_Set", + null, + new[] { typeof(object), typeof(object) }, + fieldInfo.DeclaringType, + true); + + ILGenerator il = method.GetILGenerator(); + + if (!fieldInfo.IsStatic) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); + } + + // 加载值 + il.Emit(OpCodes.Ldarg_1); + if (fieldInfo.FieldType.IsValueType) + il.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType); + else + il.Emit(OpCodes.Castclass, fieldInfo.FieldType); + + if (fieldInfo.IsStatic) + il.Emit(OpCodes.Stsfld, fieldInfo); + else + il.Emit(OpCodes.Stfld, fieldInfo); + + il.Emit(OpCodes.Ret); + + return (Action)method.CreateDelegate(typeof(Action)); + } + + /// + /// 创建属性 Getter 委托:Func<object, object> + /// + public static Func CreatePropertyGetter(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + throw new ArgumentNullException(nameof(propertyInfo)); + var getMethod = propertyInfo.GetGetMethod(true); + if (getMethod == null) + throw new InvalidOperationException($"属性 {propertyInfo.Name} 没有可用的 Getter。"); + + var method = new DynamicMethod( + propertyInfo.Name + "_Get", + typeof(object), + new[] { typeof(object) }, + propertyInfo.DeclaringType, + true); + + ILGenerator il = method.GetILGenerator(); + + if (!getMethod.IsStatic) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); + il.EmitCall(OpCodes.Callvirt, getMethod, null); + } + else + { + il.EmitCall(OpCodes.Call, getMethod, null); + } + + // 装箱 + if (propertyInfo.PropertyType.IsValueType) + il.Emit(OpCodes.Box, propertyInfo.PropertyType); + + il.Emit(OpCodes.Ret); + + return (Func)method.CreateDelegate(typeof(Func)); + } + + /// + /// 创建属性 Setter 委托:Action<object, object> + /// + public static Action CreatePropertySetter(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + throw new ArgumentNullException(nameof(propertyInfo)); + var setMethod = propertyInfo.GetSetMethod(true); + if (setMethod == null) + throw new InvalidOperationException($"属性 {propertyInfo.Name} 没有可用的 Setter。"); + + var method = new DynamicMethod( + propertyInfo.Name + "_Set", + null, + new[] { typeof(object), typeof(object) }, + propertyInfo.DeclaringType, + true); + + ILGenerator il = method.GetILGenerator(); + + if (!setMethod.IsStatic) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); + } + + // 加载值 + il.Emit(OpCodes.Ldarg_1); + if (propertyInfo.PropertyType.IsValueType) + il.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType); + else + il.Emit(OpCodes.Castclass, propertyInfo.PropertyType); + + if (setMethod.IsStatic) + il.EmitCall(OpCodes.Call, setMethod, null); + else + il.EmitCall(OpCodes.Callvirt, setMethod, null); + + il.Emit(OpCodes.Ret); + + return (Action)method.CreateDelegate(typeof(Action)); + } + + + /// + /// 创建集合赋值委托:Action<object, object, object> + /// + /// + /// + /// + public static Action CreateCollectionSetter(Type collectionType) + { + DynamicMethod dm = new DynamicMethod( + "SetCollectionValue", + null, + new[] { typeof(object), typeof(object), typeof(object) }, + typeof(EmitHelper).Module, + true); + + ILGenerator il = dm.GetILGenerator(); + + if (collectionType.IsArray) + { + // (object array, object index, object value) => ((T[])array)[(int)index] = (T)value; + var elementType = collectionType.GetElementType()!; + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, collectionType); // 转为真实数组类型 + + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Unbox_Any, typeof(int)); // index + + il.Emit(OpCodes.Ldarg_2); + if (elementType.IsValueType) + il.Emit(OpCodes.Unbox_Any, elementType); + else + il.Emit(OpCodes.Castclass, elementType); + + il.Emit(OpCodes.Stelem, elementType); // 设置数组元素 + il.Emit(OpCodes.Ret); + } + else + { + // 尝试获取 set_Item 方法 + MethodInfo? setItem = collectionType.GetMethod("set_Item", BindingFlags.Instance | BindingFlags.Public); + if (setItem == null) + throw new NotSupportedException($"类型 {collectionType} 不支持 set_Item。"); + + var parameters = setItem.GetParameters(); + var indexType = parameters[0].ParameterType; + var valueType = parameters[1].ParameterType; + + // (object collection, object index, object value) => ((CollectionType)collection)[(IndexType)index] = (ValueType)value; + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, collectionType); + + il.Emit(OpCodes.Ldarg_1); + if (indexType.IsValueType) + il.Emit(OpCodes.Unbox_Any, indexType); + else + il.Emit(OpCodes.Castclass, indexType); + + il.Emit(OpCodes.Ldarg_2); + if (valueType.IsValueType) + il.Emit(OpCodes.Unbox_Any, valueType); + else + il.Emit(OpCodes.Castclass, valueType); + + il.Emit(OpCodes.Callvirt, setItem); + il.Emit(OpCodes.Ret); + } + + return (Action)dm.CreateDelegate(typeof(Action)); + } + + + + /// + /// 创建集合获取委托:Func<object, object, object> + /// + /// + /// + /// + public static Func CreateCollectionGetter(Type collectionType) + { + DynamicMethod dm = new DynamicMethod( + "GetCollectionValue", + typeof(object), + new[] { typeof(object), typeof(object) }, + typeof(EmitHelper).Module, + true); + + ILGenerator il = dm.GetILGenerator(); + + if (collectionType.IsArray) + { + // (object array, object index) => ((T[])array)[(int)index] + var elementType = collectionType.GetElementType()!; + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, collectionType); // 转为真实数组类型 + + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Unbox_Any, typeof(int)); // index + + il.Emit(OpCodes.Ldelem, elementType); // 取值 + + if (elementType.IsValueType) + il.Emit(OpCodes.Box, elementType); // 装箱 + + il.Emit(OpCodes.Ret); + } + else + { + // 调用 get_Item 方法 + MethodInfo? getItem = collectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public); + if (getItem == null) + throw new NotSupportedException($"类型 {collectionType} 不支持 get_Item。"); + + var parameters = getItem.GetParameters(); + var indexType = parameters[0].ParameterType; + var returnType = getItem.ReturnType; + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, collectionType); + + il.Emit(OpCodes.Ldarg_1); + if (indexType.IsValueType) + il.Emit(OpCodes.Unbox_Any, indexType); + else + il.Emit(OpCodes.Castclass, indexType); + + il.Emit(OpCodes.Callvirt, getItem); + + if (returnType.IsValueType) + il.Emit(OpCodes.Box, returnType); + + il.Emit(OpCodes.Ret); + } + + return (Func)dm.CreateDelegate(typeof(Func)); + } + + } } + + diff --git a/Library/Utils/SereinExpression/SereinConditionParser.cs b/Library/Utils/SereinExpression/SereinConditionParser.cs index f0c7963..f8cf317 100644 --- a/Library/Utils/SereinExpression/SereinConditionParser.cs +++ b/Library/Utils/SereinExpression/SereinConditionParser.cs @@ -36,6 +36,16 @@ namespace Serein.Library.Utils.SereinExpression } } + /// + /// 连接字符串数组的指定部分 + /// + /// + /// + /// + /// + /// + /// + /// public static string JoinStrings(string[] parts, int startIndex, int count, char separator) { if (parts == null) @@ -51,14 +61,21 @@ namespace Serein.Library.Utils.SereinExpression // 使用 string.Join 连接 return string.Join(separator.ToString(), subArray); } - - - } - + /// + /// 条件解析器(生成IL进行判断) + /// 格式: data.[propertyName] [operator] [value] + /// public class SereinConditionParser { + /// + /// 条件表达式 + /// + /// + /// + /// + /// public static bool To(T data, string expression) { try @@ -67,8 +84,8 @@ namespace Serein.Library.Utils.SereinExpression { return false; } - var parse = ConditionParse(data, expression); - var result = parse.Evaluate(data); + var parse = ConditionParse(data, expression); // 解析条件 + var result = parse.Evaluate(data); // 执行判断 return result; } @@ -79,15 +96,23 @@ namespace Serein.Library.Utils.SereinExpression } } - public static SereinConditionResolver ConditionParse(object data, string expression) + /// + /// 解析条件 + /// + /// + /// + /// + private static SereinConditionResolver ConditionParse(object data, string expression) { + //ReadOnlySpan expSpan = expression.AsSpan(); + if (expression[0] == '.') // 表达式前缀属于从上一个节点数据对象获取成员值 { - return ParseObjectExpression(data, expression); + return ParseObjectExpression(data, expression); // 对象表达式解析 } else { - return ParseSimpleExpression(data, expression); + return ParseSimpleExpression(data, expression); // 简单表达式解析 } @@ -115,6 +140,7 @@ namespace Serein.Library.Utils.SereinExpression return null; } + /// /// 获取对象指定名称的成员 /// @@ -505,42 +531,7 @@ namespace Serein.Library.Utils.SereinExpression } } - //public static T ValueParse(object value) where T : struct, IComparable - //{ - // return (T)ValueParse(typeof(T), value); - //} - - //public static object ValueParse(Type type, object value) - //{ - - // string? valueStr = value.ToString(); - // if (string.IsNullOrEmpty(valueStr)) - // { - // throw new ArgumentException("value is null"); - // } - // object result = type switch - // { - // Type t when t.IsEnum => Enum.Parse(type, valueStr), - // Type t when t == typeof(bool) => bool.Parse(valueStr), - // Type t when t == typeof(float) => float.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(decimal) => decimal.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(double) => double.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(sbyte) => sbyte.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(byte) => byte.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(short) => short.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(ushort) => ushort.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(int) => int.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(uint) => uint.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(long) => long.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(ulong) => ulong.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(nint) => nint.Parse(valueStr, CultureInfo.InvariantCulture), - // Type t when t == typeof(nuint) => nuint.Parse(valueStr, CultureInfo.InvariantCulture), - // _ => throw new ArgumentException("非预期值类型") - // }; - // return result; - //} - - + /// /// 数值操作类型 @@ -648,3 +639,41 @@ namespace Serein.Library.Utils.SereinExpression } } + + + +//public static T ValueParse(object value) where T : struct, IComparable +//{ +// return (T)ValueParse(typeof(T), value); +//} + +//public static object ValueParse(Type type, object value) +//{ + +// string? valueStr = value.ToString(); +// if (string.IsNullOrEmpty(valueStr)) +// { +// throw new ArgumentException("value is null"); +// } +// object result = type switch +// { +// Type t when t.IsEnum => Enum.Parse(type, valueStr), +// Type t when t == typeof(bool) => bool.Parse(valueStr), +// Type t when t == typeof(float) => float.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(decimal) => decimal.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(double) => double.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(sbyte) => sbyte.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(byte) => byte.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(short) => short.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(ushort) => ushort.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(int) => int.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(uint) => uint.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(long) => long.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(ulong) => ulong.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(nint) => nint.Parse(valueStr, CultureInfo.InvariantCulture), +// Type t when t == typeof(nuint) => nuint.Parse(valueStr, CultureInfo.InvariantCulture), +// _ => throw new ArgumentException("非预期值类型") +// }; +// return result; +//} + diff --git a/NodeFlow/Model/Node/NodeModelBaseFunc.cs b/NodeFlow/Model/Node/NodeModelBaseFunc.cs index 97dddab..650f321 100644 --- a/NodeFlow/Model/Node/NodeModelBaseFunc.cs +++ b/NodeFlow/Model/Node/NodeModelBaseFunc.cs @@ -57,51 +57,6 @@ namespace Serein.NodeFlow.Model return; } - /* /// - /// 移除该节点 - /// - public virtual void Remove() - { - if (this.DebugSetting.CancelInterrupt != null) - { - this.DebugSetting.CancelInterrupt?.Invoke(); - } - - if (this.IsPublic) - { - this.CanvasDetails.PublicNodes.Remove(this); - } - - this.DebugSetting.NodeModel = null; - this.DebugSetting = null; - if(this.MethodDetails is not null) - { - if (this.MethodDetails.ParameterDetailss != null) - { - foreach (var pd in this.MethodDetails.ParameterDetailss) - { - pd.DataValue = null; - pd.Items = null; - pd.NodeModel = null; - pd.ExplicitType = null; - pd.DataType = null; - pd.Name = null; - pd.ArgDataSourceNodeGuid = null; - pd.InputType = ParameterValueInputType.Input; - } - } - this.MethodDetails.ParameterDetailss = null; - this.MethodDetails.NodeModel = null; - this.MethodDetails.ReturnType = null; - this.MethodDetails.ActingInstanceType = null; - this.MethodDetails = null; - } - - this.Position = null; - this.DisplayName = null; - - this.Env = null; - }*/ /// /// 执行节点对应的方法 @@ -122,16 +77,16 @@ namespace Serein.NodeFlow.Model if (token.IsCancellationRequested) { return null; } } - MethodDetails md = MethodDetails; + MethodDetails? md = MethodDetails; if (md is null) { - throw new Exception($"节点{this.Guid}不存在方法信息,请检查是否需要重写节点的ExecutingAsync"); + throw new Exception($"节点{Guid}不存在方法信息,请检查是否需要重写节点的ExecutingAsync"); } if (!context.Env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行到某个节点 { - throw new Exception($"节点{this.Guid}不存在对应委托"); } + if (md.IsStatic) { object[] args = await this.GetParametersAsync(context, token); diff --git a/NodeFlow/Model/Node/SingleScriptNode.cs b/NodeFlow/Model/Node/SingleScriptNode.cs index 547f034..816a5c9 100644 --- a/NodeFlow/Model/Node/SingleScriptNode.cs +++ b/NodeFlow/Model/Node/SingleScriptNode.cs @@ -105,7 +105,7 @@ namespace Serein.NodeFlow.Model } /// - /// 导出脚本代码 + /// 保存项目时保存脚本代码 /// /// /// @@ -143,6 +143,61 @@ namespace Serein.NodeFlow.Model { try { + HashSet varNames = new HashSet(); + foreach (var pd in MethodDetails.ParameterDetailss) + { + if (varNames.Contains(pd.Name)) + { + throw new Exception($"脚本节点重复的变量名称:{pd.Name} - {Guid}"); + } + varNames.Add(pd.Name); + } + + var argTypes = MethodDetails.ParameterDetailss + .Select(pd => + { + if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) + { + var Type = pd.DataType; + return (pd.Name, Type); + } + if (Env.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var node) && + node.MethodDetails?.ReturnType is not null) + { + pd.DataType = node.MethodDetails.ReturnType; + var Type = node.MethodDetails.ReturnType; + return (pd.Name, Type); + } + return default; + }) + .Where(x => x != default) + .ToDictionary(x => x.Name, x => x.Type); // 准备预定义类型 + + + var returnType = sereinScript.ParserScript(Script, argTypes); // 开始解析获取程序主节点 + MethodDetails.ReturnType = returnType; + return true; + } + catch (Exception ex) + { + SereinEnv.WriteLine(InfoType.WARN, ex.Message); + return false; // 解析失败 + } + } + + /// + /// 转换为 C# 代码,并且附带方法信息 + /// + public SereinScriptMethodInfo? ToCsharpMethodInfo(string methodName) + { + try + { + if (string.IsNullOrWhiteSpace(methodName)) + { + var tmp = Guid.Replace("-", ""); + methodName = $"FlowMethod_{tmp}"; + } + HashSet varNames = new HashSet(); foreach (var pd in MethodDetails.ParameterDetailss) { @@ -153,7 +208,6 @@ namespace Serein.NodeFlow.Model varNames.Add(pd.Name); } - var argTypes = MethodDetails.ParameterDetailss .Select(pd => { @@ -176,17 +230,14 @@ namespace Serein.NodeFlow.Model var returnType = sereinScript.ParserScript(Script, argTypes); // 开始解析获取程序主节点 - MethodDetails.ReturnType = returnType; - - var code = sereinScript.ConvertCSharpCode("Test", argTypes); - Debug.WriteLine(code); - return true; + var scriptMethodInfo = sereinScript.ConvertCSharpCode(methodName, argTypes); + return scriptMethodInfo; } catch (Exception ex) { SereinEnv.WriteLine(InfoType.WARN, ex.Message); - return false; // 解析失败 + return null; // 解析失败 } } @@ -212,11 +263,10 @@ namespace Serein.NodeFlow.Model { if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); var @params = await flowCallNode.GetParametersAsync(context, token); - if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); - + //if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); //context.AddOrUpdate($"{context.Guid}_{this.Guid}_Params", @params[0]); // 后面再改 - if (IsScriptChanged) + /* if (IsScriptChanged) { lock (@params) { if (IsScriptChanged) @@ -226,7 +276,7 @@ namespace Serein.NodeFlow.Model context.Env.WriteLine(InfoType.INFO, $"[{Guid}]脚本解析完成"); } } - } + }*/ IScriptInvokeContext scriptContext = new ScriptInvokeContext(context); @@ -248,7 +298,7 @@ namespace Serein.NodeFlow.Model var envEvent = context.Env.Event; envEvent.FlowRunComplete += onFlowStop; // 防止运行后台流程 - if (token.IsCancellationRequested) return null; + if (token.IsCancellationRequested) return new FlowResult(this.Guid, context); var result = await sereinScript.InterpreterAsync(scriptContext); // 从入口节点执行 envEvent.FlowRunComplete -= onFlowStop; diff --git a/NodeFlow/Serein.NodeFlow.csproj b/NodeFlow/Serein.NodeFlow.csproj index 219547f..4928697 100644 --- a/NodeFlow/Serein.NodeFlow.csproj +++ b/NodeFlow/Serein.NodeFlow.csproj @@ -77,6 +77,7 @@ + diff --git a/NodeFlow/Services/FlowCoreGenerateService.cs b/NodeFlow/Services/FlowCoreGenerateService.cs index 7cbf26c..2390640 100644 --- a/NodeFlow/Services/FlowCoreGenerateService.cs +++ b/NodeFlow/Services/FlowCoreGenerateService.cs @@ -1,8 +1,10 @@ -using Serein.Library; +using Newtonsoft.Json.Linq; +using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; using Serein.NodeFlow.Env; using Serein.NodeFlow.Model; +using Serein.Script; using System; using System.Collections.Generic; using System.Linq; @@ -10,6 +12,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using System.Xml.Linq; namespace Serein.NodeFlow.Services { @@ -46,8 +49,11 @@ namespace Serein.NodeFlow.Services } } - var flowCallNode = flowModelService.GetAllNodeModel().Where(n => n.ControlType == NodeControlType.FlowCall).Select(n => (SingleFlowCallNode)n).ToArray(); - GenerateFlowApi_InitFlowApiMethodInfos(flowCallNode); + var scriptNodes = flowModelService.GetAllNodeModel().Where(n => n.ControlType == NodeControlType.Script).OfType().ToArray(); + GenerateScript_InitSereinScriptMethodInfos(scriptNodes); // 初始化脚本方法 + + var flowCallNode = flowModelService.GetAllNodeModel().Where(n => n.ControlType == NodeControlType.FlowCall).OfType().ToArray(); + GenerateFlowApi_InitFlowApiMethodInfos(flowCallNode); // 初始化流程接口信息 GenerateFlowApi_InterfaceAndImpleClass(stringBuilder); // 生成接口类 GenerateFlowApi_ApiParamClass(stringBuilder); // 生成接口参数类 string flowTemplateClassName = $"FlowTemplate"; // 类名 @@ -66,16 +72,24 @@ namespace Serein.NodeFlow.Services } // 生成实现流程接口的实现方法 - var infos = flowApiMethodInfos.Values.ToArray(); - foreach (var info in infos) + var flowApiInfos = flowApiMethodInfos.Values.ToArray(); + foreach (var info in flowApiInfos) { stringBuilder.AppendCode(2, info.ToObjPoolSignature()); stringBuilder.AppendCode(2, info.ToImpleMethodSignature(FlowApiMethodInfo.ParamType.Defute)); stringBuilder.AppendCode(2, info.ToImpleMethodSignature(FlowApiMethodInfo.ParamType.HasToken)); stringBuilder.AppendCode(2, info.ToImpleMethodSignature(FlowApiMethodInfo.ParamType.HasContextAndToken)); } + stringBuilder.AppendCode(0, $"}}"); + // 载入脚本节点转换的C#代码 + var scriptInfos = scriptMethodInfos.Values.ToArray(); + foreach (var info in scriptInfos) + { + stringBuilder.AppendCode(2, info.CsharpCode); + } + return stringBuilder.ToString(); } @@ -132,18 +146,19 @@ namespace Serein.NodeFlow.Services /// private void GenerateMethod(StringBuilder sb_main, IFlowNode flowNode) { - string? dynamicContextTypeName = typeof(IFlowContext).FullName; + string? flowContextTypeName = typeof(IFlowContext).FullName; string? flowContext = nameof(flowContext); if (flowNode.ControlType == NodeControlType.Action && flowNode is SingleActionNode actionNode) { - CreateMethodCore_Action(sb_main, actionNode, dynamicContextTypeName, flowContext); + CreateMethodCore_Action(sb_main, actionNode, flowContextTypeName, flowContext); } else if (flowNode.ControlType == NodeControlType.Flipflop) { } - else if (flowNode.ControlType == NodeControlType.Script) + else if (flowNode.ControlType == NodeControlType.Script && flowNode is SingleScriptNode singleScriptNode) { + CreateMethodCore_Script(sb_main, singleScriptNode, flowContextTypeName, flowContext); } else if (flowNode.ControlType == NodeControlType.UI) { @@ -157,7 +172,7 @@ namespace Serein.NodeFlow.Services } else if (flowNode.ControlType == NodeControlType.FlowCall && flowNode is SingleFlowCallNode flowCallNode) { - CreateMethodCore_FlowCall(sb_main, flowCallNode, dynamicContextTypeName, flowContext); + CreateMethodCore_FlowCall(sb_main, flowCallNode, flowContextTypeName, flowContext); } return; @@ -169,10 +184,10 @@ namespace Serein.NodeFlow.Services /// /// /// - /// + /// /// /// - private void CreateMethodCore_Action(StringBuilder sb_main, SingleActionNode actionNode, string? dynamicContextTypeName, string flowContext) + private void CreateMethodCore_Action(StringBuilder sb_main, SingleActionNode actionNode, string? flowContextTypeName, string flowContext) { if (!flowLibraryService.TryGetMethodInfo(actionNode.MethodDetails.AssemblyName, actionNode.MethodDetails.MethodName, @@ -339,7 +354,7 @@ namespace Serein.NodeFlow.Services var resultTypeName = actionNode.MethodDetails.IsAsync ? "async Task" : "void"; sb_main.AppendCode(2, $"[Description(\"{instanceTypeFullName}.{methodInfo.Name}\")]"); - sb_main.AppendCode(2, $"private {resultTypeName} {actionNode.ToNodeMethodName()}(global::{dynamicContextTypeName} {flowContext})"); + sb_main.AppendCode(2, $"private {resultTypeName} {actionNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); sb_main.AppendCode(2, $"{{"); sb_main.AppendCode(0, sb_invoke_login.ToString()); sb_main.AppendCode(2, $"}}"); // 方法结束 @@ -352,103 +367,372 @@ namespace Serein.NodeFlow.Services /// /// /// - /// + /// /// /// - private void CreateMethodCore_FlowCall(StringBuilder sb_main, SingleFlowCallNode flowCallNode, string? dynamicContextTypeName, string flowContext) + private void CreateMethodCore_FlowCall(StringBuilder sb_main, SingleFlowCallNode flowCallNode, string? flowContextTypeName, string flowContext) { if (!flowApiMethodInfos.TryGetValue(flowCallNode, out var flowApiMethodInfo)) { return; } - if (!flowLibraryService.TryGetMethodInfo(flowCallNode.MethodDetails.AssemblyName, - flowCallNode.MethodDetails.MethodName, - out var methodInfo) || methodInfo is null) + + if(flowCallNode.TargetNode is SingleScriptNode singleScriptNode) + { + if (!scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) + { + return; + } + + var instanceType = flowCallNode.MethodDetails.ActingInstanceType; + var returnType = singleScriptNode.MethodDetails.ReturnType; + + //var instanceName = instanceType.ToCamelCase();// $"instance_{instanceType.Name}"; + + //var instanceTypeFullName = instanceType.FullName; + var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; + + #region 方法内部逻辑 + StringBuilder sb_invoke_login = new StringBuilder(); + + if (flowCallNode.MethodDetails is null) return; + //var param = methodInfo.GetParameters(); + var md = flowCallNode.MethodDetails; + var pds = flowCallNode.MethodDetails.ParameterDetailss; + //if (param is null) return; + if (pds is null) return; + + /* for (int index = 0; index < pds.Length; index++) + { + ParameterDetails? pd = pds[index]; + ParameterInfo parameterInfo = param[index]; + var paramtTypeFullName = parameterInfo.ParameterType.FullName; + }*/ + + var flowDataName = $"flowData{flowApiMethodInfo.ApiMethodName}"; + var apiData = $"apiData{flowApiMethodInfo.ApiMethodName}"; + sb_invoke_login.AppendCode(3, $"global::{typeof(object).FullName} {flowDataName} = {flowContext}.GetFlowData(\"{flowApiMethodInfo.ApiMethodName}\").Value;"); + sb_invoke_login.AppendCode(3, $"if({flowDataName} is {flowApiMethodInfo.ParamTypeName} {apiData})"); + sb_invoke_login.AppendCode(3, $"{{"); + foreach (var info in flowApiMethodInfo.ParamInfos) + { + sb_invoke_login.AppendCode(4, $"global::{info.Type.FullName} {info.ParamName} = {apiData}.{info.ParamName.ToPascalCase()}; "); + } + var invokeParamContext = string.Join(", ", flowApiMethodInfo.ParamInfos.Select(x => x.ParamName)); + if (flowApiMethodInfo.IsVoid) + { + // 调用无返回值方法 + var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; + // 如果目标方法是异步的,则自动 await 进行等待 + sb_invoke_login.AppendCode(4, $"{(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + } + else + { + // 调用有返回值方法 + var resultName = $"result{flowApiMethodInfo.ApiMethodName}"; + var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; + // 如果目标方法是异步的,则自动 await 进行等待 + sb_invoke_login.AppendCode(4, $"var {resultName} = {(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + + sb_invoke_login.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{flowCallNode.TargetNode.Guid}\", {resultName});"); // 更新数据 + sb_invoke_login.AppendCode(4, $"{flowApiMethodInfo.ObjPoolName}.Return({apiData});"); // 归还到对象池 + //sb_invoke_login.AppendCode(3, $"return result;", false); + } + sb_invoke_login.AppendCode(3, $"}}"); + sb_invoke_login.AppendCode(3, $"else"); + sb_invoke_login.AppendCode(3, $"{{"); + sb_invoke_login.AppendCode(4, $"throw new Exception(\"接口参数类型异常\");"); + sb_invoke_login.AppendCode(3, $"}}"); + + + + + #endregion + + + var resultTypeName = flowCallNode.MethodDetails.IsAsync ? "async Task" : "void"; + + sb_main.AppendCode(2, $"[Description(\"脚本节点接口\")]"); + sb_main.AppendCode(2, $"private {resultTypeName} {flowCallNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); + sb_main.AppendCode(2, $"{{"); + sb_main.AppendCode(0, sb_invoke_login.ToString()); + sb_main.AppendCode(2, $"}} "); // 方法结束 + + sb_main.AppendLine(); // 方法结束 + } + else + { + if (!flowLibraryService.TryGetMethodInfo(flowCallNode.MethodDetails.AssemblyName, + flowCallNode.MethodDetails.MethodName, + out var methodInfo) || methodInfo is null) + { + return; + } + + var isRootNode = flowCallNode.IsRoot(); + + var instanceType = flowCallNode.MethodDetails.ActingInstanceType; + var returnType = methodInfo.ReturnType; + + var instanceName = instanceType.ToCamelCase();// $"instance_{instanceType.Name}"; + + var instanceTypeFullName = instanceType.FullName; + var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; + + #region 方法内部逻辑 + StringBuilder sb_invoke_login = new StringBuilder(); + + if (flowCallNode.MethodDetails is null) return; + //var param = methodInfo.GetParameters(); + var md = flowCallNode.MethodDetails; + var pds = flowCallNode.MethodDetails.ParameterDetailss; + //if (param is null) return; + if (pds is null) return; + + /* for (int index = 0; index < pds.Length; index++) + { + ParameterDetails? pd = pds[index]; + ParameterInfo parameterInfo = param[index]; + var paramtTypeFullName = parameterInfo.ParameterType.FullName; + }*/ + + var flowDataName = $"flowData{flowApiMethodInfo.ApiMethodName}"; + var apiData = $"apiData{flowApiMethodInfo.ApiMethodName}"; + sb_invoke_login.AppendCode(3, $"global::{typeof(object).FullName} {flowDataName} = {flowContext}.GetFlowData(\"{flowApiMethodInfo.ApiMethodName}\").Value;"); + sb_invoke_login.AppendCode(3, $"if({flowDataName} is {flowApiMethodInfo.ParamTypeName} {apiData})"); + sb_invoke_login.AppendCode(3, $"{{"); + foreach (var info in flowApiMethodInfo.ParamInfos) + { + sb_invoke_login.AppendCode(4, $"global::{info.Type.FullName} {info.ParamName} = {apiData}.{info.ParamName.ToPascalCase()}; "); + } + var invokeParamContext = string.Join(", ", flowApiMethodInfo.ParamInfos.Select(x => x.ParamName)); + if (flowApiMethodInfo.IsVoid) + { + // 调用无返回值方法 + // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 + var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; + // 如果目标方法是异步的,则自动 await 进行等待 + sb_invoke_login.AppendCode(4, $"{(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + } + else + { + // 调用有返回值方法 + var resultName = $"result{flowApiMethodInfo.ApiMethodName}"; + // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 + var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; + // 如果目标方法是异步的,则自动 await 进行等待 + sb_invoke_login.AppendCode(4, $"var {resultName} = {(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + + sb_invoke_login.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{flowCallNode.TargetNode.Guid}\", {resultName});"); // 更新数据 + sb_invoke_login.AppendCode(4, $"{flowApiMethodInfo.ObjPoolName}.Return({apiData});"); // 归还到对象池 + //sb_invoke_login.AppendCode(3, $"return result;", false); + } + sb_invoke_login.AppendCode(3, $"}}"); + sb_invoke_login.AppendCode(3, $"else"); + sb_invoke_login.AppendCode(3, $"{{"); + sb_invoke_login.AppendCode(4, $"throw new Exception(\"接口参数类型异常\");"); + sb_invoke_login.AppendCode(3, $"}}"); + + + + + #endregion + + + var resultTypeName = flowCallNode.MethodDetails.IsAsync ? "async Task" : "void"; + + sb_main.AppendCode(2, $"[Description(\"{instanceTypeFullName}.{methodInfo.Name}\")]"); + sb_main.AppendCode(2, $"private {resultTypeName} {flowCallNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); + sb_main.AppendCode(2, $"{{"); + sb_main.AppendCode(0, sb_invoke_login.ToString()); + sb_main.AppendCode(2, $"}} "); // 方法结束 + + sb_main.AppendLine(); // 方法结束 + } + + + } + + + /// + /// 生成[Script]节点的方法调用 + /// + /// + /// + /// + /// + private void CreateMethodCore_Script(StringBuilder sb_main, SingleScriptNode singleScriptNode, string? flowContextTypeName, string flowContext) + { + + + if (!scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) { return; } - var isRootNode = flowCallNode.IsRoot(); + var isRootNode = singleScriptNode.IsRoot(); - var instanceType = flowCallNode.MethodDetails.ActingInstanceType; - var returnType = methodInfo.ReturnType; - var instanceName = instanceType.ToCamelCase();// $"instance_{instanceType.Name}"; - - var instanceTypeFullName = instanceType.FullName; + var returnType = scriptMethodInfo.ReturnType; var returnTypeFullName = returnType == typeof(void) ? "void" : returnType.FullName; #region 方法内部逻辑 StringBuilder sb_invoke_login = new StringBuilder(); - - if (flowCallNode.MethodDetails is null) return; - var param = methodInfo.GetParameters(); - var md = flowCallNode.MethodDetails; - var pds = flowCallNode.MethodDetails.ParameterDetailss; + if (singleScriptNode.MethodDetails is null) return; + var param = scriptMethodInfo.ParamInfos; + var md = singleScriptNode.MethodDetails; + var pds = singleScriptNode.MethodDetails.ParameterDetailss; if (param is null) return; if (pds is null) return; + bool isGetPreviousNode = false; for (int index = 0; index < pds.Length; index++) { ParameterDetails? pd = pds[index]; - ParameterInfo parameterInfo = param[index]; + SereinScriptMethodInfo.SereinScriptParamInfo parameterInfo = param[index]; var paramtTypeFullName = parameterInfo.ParameterType.FullName; + + if (pd.IsExplicitData) + { + // 只能是 数值、 文本、枚举, 才能作为显式参数 + if (parameterInfo.ParameterType.IsValueType) + { + if (parameterInfo.ParameterType.IsEnum) + { + sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = global::{paramtTypeFullName}.{pd.DataValue}; // 获取当前节点的上一节点数据"); + } + else + { + var value = pd.DataValue.ToConvert(parameterInfo.ParameterType); + sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = (global::{paramtTypeFullName}){value}; // 获取当前节点的上一节点数据"); + + } + } + else if (parameterInfo.ParameterType == typeof(string)) + { + sb_invoke_login.AppendCode(3, $"global::{paramtTypeFullName} value{index} = \"{pd.DataValue}\"; // 获取当前节点的上一节点数据"); + } + else + { + // 处理表达式 + } + + } + else + { + #region 非显式设置的参数以正常方式获取 + if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) + { + var previousNode = $"previousNode{index}"; + var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; + sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{singleScriptNode.Guid}\");"); // 获取运行时上一节点Guid + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); + } + else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) + { + if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) + { + var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{paramtTypeFullName}"; + var otherNodeReturnType = otherNode.MethodDetails.ReturnType; + if (otherNodeReturnType == typeof(object)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); + } + else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); + } + else + { + // 获取的数据无法转换为目标方法入参类型 + throw new Exception("获取的数据无法转换为目标方法入参类型"); + } + } + else + { + // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 + throw new Exception("指定了Guid,但项目中不存在对应的节点"); + } + } + else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) + { + if (flowModelService.TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var otherNode)) // 获取指定节点 + { + var otherNodeReturnType = otherNode.MethodDetails.ReturnType; + var valueType = pd.IsParams ? $"global::{pd.DataType.FullName}" : $"global::{otherNode.MethodDetails.ReturnType.FullName}"; + if (otherNodeReturnType == typeof(object)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}(\"{pd.ArgDataSourceNodeGuid}\").Value; // 获取指定节点的数据"); + } + else if (pd.DataType.IsAssignableFrom(otherNodeReturnType)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {otherNode.ToNodeMethodName()}({flowContext}); // 获取指定节点的数据"); + } + else + { + // 获取的数据无法转换为目标方法入参类型 + throw new Exception("获取的数据无法转换为目标方法入参类型"); + } + + } + else + { + // 指定了Guid,但项目中不存在对应的节点,需要抛出异常 + throw new Exception("指定了Guid,但项目中不存在对应的节点"); + } + } + #endregion + + } } - var flowDataName = $"flowData{flowApiMethodInfo.ApiMethodName}"; - var apiData = $"apiData{flowApiMethodInfo.ApiMethodName}"; - sb_invoke_login.AppendCode(3, $"global::{typeof(object).FullName} {flowDataName} = {flowContext}.GetFlowData(\"{flowApiMethodInfo.ApiMethodName}\").Value;"); - sb_invoke_login.AppendCode(3, $"if({flowDataName} is {flowApiMethodInfo.ParamTypeName} {apiData})"); - sb_invoke_login.AppendCode(3, $"{{"); - foreach (var info in flowApiMethodInfo.ParamInfos) - { - sb_invoke_login.AppendCode(4, $"global::{info.Type.FullName} {info.ParamName} = {apiData}.{info.ParamName.ToPascalCase()}; "); - } - var invokeParamContext = string.Join(", ", flowApiMethodInfo.ParamInfos.Select(x => x.ParamName)); - if (flowApiMethodInfo.IsVoid) + if (scriptMethodInfo.ReturnType == typeof(void)) { // 调用无返回值方法 // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 - var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; + var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; // 如果目标方法是异步的,则自动 await 进行等待 - sb_invoke_login.AppendCode(4, $"{(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; + sb_invoke_login.AppendCode(3, $"global::{invokeFunctionContext}(", false); + for (int index = 0; index < pds.Length; index++) + { + sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); + } + sb_invoke_login.AppendCode(0, $"); // 调用生成的C#代码"); } else { // 调用有返回值方法 - var resultName = $"result{flowApiMethodInfo.ApiMethodName}"; // 如果目标方法是静态的,则以“命名空间.类.方法”形式调用,否则以“实例.方法”形式调用 - var invokeFunctionContext = methodInfo.IsStatic ? $"global::{instanceType}.{methodInfo.Name}" : $"{instanceName}.{methodInfo.Name}"; + var invokeFunctionContext = $"{scriptMethodInfo.ClassName}.{scriptMethodInfo.MethodName}"; // 如果目标方法是异步的,则自动 await 进行等待 - sb_invoke_login.AppendCode(4, $"var {resultName} = {(md.IsAsync ? $"await" : string.Empty)} {invokeFunctionContext}({invokeParamContext});"); + invokeFunctionContext = md.IsAsync ? $"await {invokeFunctionContext}" : invokeFunctionContext; + sb_invoke_login.AppendCode(3, $"var result = {invokeFunctionContext}(", false); + for (int index = 0; index < pds.Length; index++) + { + sb_invoke_login.Append($"{(index == 0 ? "" : ",")}value{index}"); + } + sb_invoke_login.AppendCode(0, $"); // 调用生成的C#代码"); - sb_invoke_login.AppendCode(4, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{flowCallNode.TargetNode.Guid}\", {resultName});"); // 更新数据 - sb_invoke_login.AppendCode(4, $"{flowApiMethodInfo.ObjPoolName}.Return({apiData});"); // 归还到对象池 + sb_invoke_login.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.AddOrUpdate)}(\"{singleScriptNode.Guid}\", result);", false); // 更新数据 //sb_invoke_login.AppendCode(3, $"return result;", false); } - sb_invoke_login.AppendCode(3, $"}}"); - sb_invoke_login.AppendCode(3, $"else"); - sb_invoke_login.AppendCode(3, $"{{"); - sb_invoke_login.AppendCode(4, $"throw new Exception(\"接口参数类型异常\");"); - sb_invoke_login.AppendCode(3, $"}}"); - - - - #endregion // global::{returnTypeFullName} - var resultTypeName = flowCallNode.MethodDetails.IsAsync ? "async Task" : "void"; + var resultTypeName = singleScriptNode.MethodDetails.IsAsync ? "async Task" : "void"; - sb_main.AppendCode(2, $"[Description(\"{instanceTypeFullName}.{methodInfo.Name}\")]"); - sb_main.AppendCode(2, $"private {resultTypeName} {flowCallNode.ToNodeMethodName()}(global::{dynamicContextTypeName} {flowContext})"); + sb_main.AppendCode(2, $"[Description(\"脚本节点\")]"); + sb_main.AppendCode(2, $"private {resultTypeName} {singleScriptNode.ToNodeMethodName()}(global::{flowContextTypeName} {flowContext})"); sb_main.AppendCode(2, $"{{"); sb_main.AppendCode(0, sb_invoke_login.ToString()); - sb_main.AppendCode(2, $"}} "); // 方法结束 - + sb_main.AppendCode(2, $"}}"); // 方法结束 sb_main.AppendLine(); // 方法结束 + + + } /// @@ -478,7 +762,10 @@ namespace Serein.NodeFlow.Services foreach (var node in flowNodes) { var nodeMethod = node.ToNodeMethodName(); // 节点对应的方法名称 - if (node.ControlType == NodeControlType.Action || node.ControlType == NodeControlType.FlowCall) + if (node.ControlType == NodeControlType.Action + || node.ControlType == NodeControlType.FlowCall + || node.ControlType == NodeControlType.Script + ) { sb.AppendCode(3, $"Get(\"{node.Guid}\").SetAction({nodeMethod});"); } @@ -649,6 +936,38 @@ namespace Serein.NodeFlow.Services sb.AppendCode(2, $"}}"); } + #region 脚本节点的代码生成 + private Dictionary scriptMethodInfos = []; + private void GenerateScript_InitSereinScriptMethodInfos(SingleScriptNode[] flowCallNodes) + { + bool isError = false; + foreach(var node in flowCallNodes) + { + var methodName = node.ToNodeMethodName(); + var info = node.ToCsharpMethodInfo(methodName); + if(info is null) + { + isError = true; + SereinEnv.WriteLine(InfoType.WARN, $"脚本节点[{node.Guid}]无法生成代码信息"); + } + else + { + scriptMethodInfos[node] = info; + } + + } + if (isError) + { + + } + } + #endregion + + #region 流程接口节点的代码生成 + + /// + /// 流程接口节点与对应的流程方法信息 + /// private Dictionary flowApiMethodInfos = []; /// @@ -667,7 +986,7 @@ namespace Serein.NodeFlow.Services var info = flowCallNode.ToFlowApiMethodInfo(); if (info is not null) { - flowApiMethodInfos.TryAdd(flowCallNode, info); + flowApiMethodInfos[flowCallNode] = info; } } } @@ -725,6 +1044,7 @@ namespace Serein.NodeFlow.Services } } + #endregion #region 辅助代码生成的方法 @@ -783,7 +1103,9 @@ namespace Serein.NodeFlow.Services - + /// + /// 指示流程接口方法需要生成什么代码 + /// internal class FlowApiMethodInfo { public FlowApiMethodInfo(SingleFlowCallNode singleFlowCallNode) @@ -1063,6 +1385,14 @@ namespace Serein.NodeFlow.Services sb.AppendCode(3, $"{flowContext}.{nameof(IFlowContext.SetPreviousNode)}(\"{NodeModel.Guid}\", \"{ApiMethodName}\");"); sb.AppendCode(3, $"global::{typeof(CallNode).FullName} node = Get(\"{NodeModel.Guid}\");"); sb.AppendCode(3, $"global::{typeof(FlowResult).FullName} {flowResult} = await node.{nameof(CallNode.StartFlowAsync)}({flowContext}, {token}); // 调用目标方法"); + if(ReturnType == typeof(object)) + { + sb.AppendCode(3, $"if ({flowResult}.{nameof(FlowResult.Value)} is null)"); + sb.AppendCode(3, $"{{"); + sb.AppendCode(4, $"return null;"); + sb.AppendCode(3, $"}}"); + } + sb.AppendCode(3, $"if ({flowResult}.{nameof(FlowResult.Value)} is global::{ReturnType.FullName} result)"); sb.AppendCode(3, $"{{"); sb.AppendCode(4, $"return result;"); @@ -1127,6 +1457,8 @@ namespace Serein.NodeFlow.Services return flowApiMethodInfo; } + + /// diff --git a/README.md b/README.md index 4d1a720..097e3b0 100644 --- a/README.md +++ b/README.md @@ -96,80 +96,6 @@ https://space.bilibili.com/33526379 SereinEnv.GetFlowGlobalData("KeyName"); // 获取全局数据 SereinEnv.AddOrUpdateFlowGlobalData("KeyName", obj); // 设置/更新全局数据,不建议 ~~~ -* **ExpOp- 表达式节点** - * 入参: 自定义的表达式。 - * 取值表达式:@Get - * 描述:有时节点返回了object,但下一个节点只需要对象中某个属性,而非整个对象。如果修改节点的定义,有可能破坏了代码的封装,为了解决这个痛点,于是增加了表达式功能。 - * 使用方法: - 1. 获取对象的属性成员: - ~~~~ - @Get .[property]/[field] - ~~~~ - 2. 获取对象的数组成员中下标为22的项: - ~~~~ - @Get .array[22] - ~~~~ - 3. 获取对象的字典成员中键为“33”的值: - ~~~ - @Get .dict[33] - ~~~ - 4. 获取对象“ID”属性并转为int: - ~~~ - @Get .ID - ~~~ - 5. 获取KeyName为【 MyDevice 】全局数据: - ~~~ - @Get #MyDevice# - ~~~ - 6. 从全局数据【 MyDevice 】中获取“IP”属性: - ~~~ - @Get #MyDevice#.IP - ~~~ - * 数据类型转换表达式:@Dtc - * 描述:有时需要显式的设置节点参数值,但参数接收了其它的类型,需要经过一次转换,将显式的文本值转为入参数据类型。 - * 使用方法: - ~~~ - @Dtc 1233 - @Dtc True - @Dtc 2024-12-24 11:13:42 (注:如果右值为“now”,则自动获取当前时间) - ~~~ -* **ExpCondition - 条件表达式节点** - * 入参: 自定义。 - * 描述:与表达式节点不同,条件表达式节点是判断条件是否成立,如果成立,返回true,否则返回false,如果表达式执行失败,而进入 error 分支。 - * 使用方式: - * 入参说明:默认从上一节点获取,也可以显式设定值,也可以参考表达式节点,使用“@Get .[property]/[field]”的方式重新定义入参数据,用以条件表达式判断。 - * 条件表达式:默认“PASS”,代表跳过判断直接进入下一个分支。 - * 条件表达式格式“.[property]/[field]< type> [op] value”,注意,开头必须使用“.”符号,这有助于显然的表达需要从入参对象中取内部某个值。 - * 表达式符号说明: - * [property] /[field] : 属性/字段 - * [op] : 操作符 - 1. bool表达式:== - 2. 数值表达式 :==、>=、 <=、in a-b (表示判断是否在a至b的数值范围内), !in a-b(取反); - 3. 文本表达式:==/equals(等于)、!=/notequals(不等于)、c/contains(出现过)、nc/doesnotcontain(没有出现过)、sw/startswith(开头等于)、ew/endswith(结尾等于) - * < type> :指定需要转换为某个类型,可不选。 - * [value] : 条件值 - * 使用示例: - ~~~ - 场景1:上一个节点传入了该对象(伪代码): - class Data - { - string Name; // 性能 - string Age; // 年龄,外部传入了文本类型 - string IdentityCardNumber; // 身份证号 - - } - 需求:需要判断年龄是否在某个区间,例如需要大于18岁,小于35岁。 - 条件表达式:.Age in 18-35 - - - - 需求:需要判断是否是北京身份证(开头为”1100”)。 - 条件表达式:.IdentityCardNumber sw 1100 - 另一种方法: - 入参使用表达式:@Get .IdentityCardNumber - 条件表达式:sw 1100 - - ~~~ ## 3. 从DLL生成控件的枚举值: * **Action - 动作** * 入参:自定义。如果入参类型为IFlowContext,会传入当前的上下文;如果入参类型为IFlowNode,会传入节点对应的实体Model。如果不显式指定参数来源,参数会尝试获取运行时上一节点返回值,并根据当前入参类型尝试进行类型转换。 diff --git a/Serein.Script/SereinSciptException.cs b/Serein.Script/SereinSciptException.cs deleted file mode 100644 index 47d2380..0000000 --- a/Serein.Script/SereinSciptException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Serein.Script.Node; - -namespace Serein.Script -{ - public sealed class SereinSciptException : Exception - { - //public ASTNode Node { get; } - public override string Message { get; } - - public SereinSciptException(ASTNode node, string message) - { - //this.Node = node; - Message = $"异常信息 : {message} ,代码在第{node.Row}行: {node.Code.Trim()}"; - } - } - -} diff --git a/Serein.Script/SereinSciptParserException.cs b/Serein.Script/SereinSciptParserException.cs new file mode 100644 index 0000000..cc1abc9 --- /dev/null +++ b/Serein.Script/SereinSciptParserException.cs @@ -0,0 +1,32 @@ +using Serein.Script.Node; + +namespace Serein.Script +{ + public sealed class SereinSciptParserException : Exception + { + //public ASTNode Node { get; } + public override string Message { get; } + + public SereinSciptParserException(ASTNode node, string message) + { + //this.Node = node; + Message = $"异常信息 : {message} ,代码在第{node.Row}行: {node.Code.Trim()}"; + } + } + + public sealed class SereinSciptInterpreterExceptio : Exception + { + //public ASTNode Node { get; } + public override string Message { get; } + + public SereinSciptInterpreterExceptio(ASTNode node, string message) + { + //this.Node = node; + Message = $"异常信息 : {message} ,代码在第{node.Row}行: {node.Code.Trim()}"; + } + + + public override string StackTrace => string.Empty; + } + +} diff --git a/Serein.Script/SereinScript.cs b/Serein.Script/SereinScript.cs index 2384bee..aa4526f 100644 --- a/Serein.Script/SereinScript.cs +++ b/Serein.Script/SereinScript.cs @@ -12,8 +12,6 @@ using System.Threading.Tasks; namespace Serein.Script { - - public class SereinScript { @@ -83,15 +81,15 @@ namespace Serein.Script /// - /// 转换为c#代码 + /// 转换为 C# 代码,并且附带方法信息 /// /// 脚本 /// 挂载的变量 /// - public string ConvertCSharpCode(string mehtodName, Dictionary? argTypes = null) + public SereinScriptMethodInfo? ConvertCSharpCode(string mehtodName, Dictionary? argTypes = null) { - if (string.IsNullOrWhiteSpace(mehtodName)) return string.Empty; - if (programNode is null) return string.Empty; + if (string.IsNullOrWhiteSpace(mehtodName)) return null; + if (programNode is null) return null; SereinScriptToCsharpScript tool = new SereinScriptToCsharpScript(TypeAnalysis); return tool.CompileToCSharp(mehtodName, programNode, argTypes); } diff --git a/Serein.Script/SereinScriptInterpreter.cs b/Serein.Script/SereinScriptInterpreter.cs index 2dbefec..51b5145 100644 --- a/Serein.Script/SereinScriptInterpreter.cs +++ b/Serein.Script/SereinScriptInterpreter.cs @@ -23,7 +23,9 @@ namespace Serein.Script /// /// 缓存对象方法调用节点 /// - private Dictionary MethodNodeDelegateCaches { get; } = new Dictionary(); + //private Dictionary MethodNodeDelegateCaches { get; } = new Dictionary(); + private static Dictionary ASTDelegateDetails { get; } = new Dictionary(); + public SereinScriptInterpreter(Dictionary symbolInfos) { @@ -79,8 +81,8 @@ namespace Serein.Script case IfNode ifNode: // if语句结构 async Task InterpreterIfNodeAsync(IScriptInvokeContext context, IfNode ifNode) { - var result = await InterpretAsync(context, ifNode.Condition) ?? throw new SereinSciptException(ifNode, $"条件语句返回了 null"); - if (result is not bool condition) throw new SereinSciptException(ifNode, "条件语句返回值不为 bool 类型"); + var result = await InterpretAsync(context, ifNode.Condition) ?? throw new SereinSciptParserException(ifNode, $"条件语句返回了 null"); + if (result is not bool condition) throw new SereinSciptParserException(ifNode, "条件语句返回值不为 bool 类型"); var branchNodes = condition ? ifNode.TrueBranch : ifNode.FalseBranch; if (branchNodes.Count == 0) return default; object? data = default; @@ -103,8 +105,8 @@ namespace Serein.Script object? data = default; while (true) { - var result = await InterpretAsync(context, whileNode.Condition) ?? throw new SereinSciptException(whileNode, $"循环节点条件返回了 null"); - if (result is not bool condition) throw new SereinSciptException(whileNode, "循环节点条件返回值不为 bool 类型"); + var result = await InterpretAsync(context, whileNode.Condition) ?? throw new SereinSciptParserException(whileNode, $"循环节点条件返回了 null"); + if (result is not bool condition) throw new SereinSciptParserException(whileNode, "循环节点条件返回值不为 bool 类型"); if (!condition) break; if (whileNode.Body.Count == 0) break; foreach (var node in whileNode.Body) @@ -137,9 +139,9 @@ namespace Serein.Script { // 递归计算二元操作 var left = await InterpretAsync(context, binaryOperationNode.Left); - if (left == null ) throw new SereinSciptException(binaryOperationNode.Left, $"左值尝试使用 null"); + if (left == null ) throw new SereinSciptParserException(binaryOperationNode.Left, $"左值尝试使用 null"); var right = await InterpretAsync(context, binaryOperationNode.Right); - if (right == null) throw new SereinSciptException(binaryOperationNode.Right, "右值尝试使用计算 null"); + if (right == null) throw new SereinSciptParserException(binaryOperationNode.Right, "右值尝试使用计算 null"); var op = binaryOperationNode.Operator; var result = BinaryOperationEvaluator.EvaluateValue(left, op, right); return result; @@ -159,7 +161,7 @@ namespace Serein.Script throw new ArgumentNullException($"解析{collectionAssignmentNode}节点时,索引返回空。"); } var valueValue = await InterpretAsync(context, collectionAssignmentNode.Value); - SetCollectionValue(collectionValue, indexValue, valueValue); + await SetCollectionValueAsync(collectionAssignmentNode, collectionValue, indexValue, valueValue); } await InterpreterCollectionAssignmentNodeAsync(context, collectionAssignmentNode); return default; @@ -176,7 +178,7 @@ namespace Serein.Script { throw new ArgumentNullException($"解析{collectionIndexNode}节点时,索引返回空。"); } - var result = GetCollectionValue(collectionValue, indexValue); + var result = await GetCollectionValueAsync(collectionIndexNode, collectionValue, indexValue); return result; } return await InterpreterCollectionIndexNodeAsync(context, collectionIndexNode); @@ -208,7 +210,7 @@ namespace Serein.Script type = symbolInfos[objectInstantiationNode.Type]; if (type is null) { - throw new SereinSciptException(objectInstantiationNode, $"使用了未定义的类型\"{objectInstantiationNode.Type.TypeName}\""); + throw new SereinSciptParserException(objectInstantiationNode, $"使用了未定义的类型\"{objectInstantiationNode.Type.TypeName}\""); } } @@ -221,7 +223,7 @@ namespace Serein.Script var obj = Activator.CreateInstance(type, args: args);// 创建对象 if (obj is null) { - throw new SereinSciptException(objectInstantiationNode, $"类型创建失败\"{objectInstantiationNode.Type.TypeName}\""); + throw new SereinSciptParserException(objectInstantiationNode, $"类型创建失败\"{objectInstantiationNode.Type.TypeName}\""); } for (int i = 0; i < objectInstantiationNode.CtorAssignments.Count; i++) @@ -229,7 +231,7 @@ namespace Serein.Script var ctorAssignmentNode = objectInstantiationNode.CtorAssignments[i]; var propertyName = ctorAssignmentNode.MemberName; var value = await InterpretAsync(context, ctorAssignmentNode.Value); - SetPropertyValue(obj, propertyName, value); + await SetPropertyValueAsync(ctorAssignmentNode, obj, propertyName, value); } return obj; } @@ -243,8 +245,8 @@ namespace Serein.Script { var target = await InterpretAsync(context, memberAccessNode.Object); var memberName = memberAccessNode.MemberName; - if(target is null) throw new SereinSciptException(memberAccessNode, $"无法获取成员,对象为 null \"{memberAccessNode.Object.Code}\""); - var value = GetPropertyValue(target, memberName); + if(target is null) throw new SereinSciptParserException(memberAccessNode, $"无法获取成员,对象为 null \"{memberAccessNode.Object.Code}\""); + var value = await GetPropertyValueAsync(memberAccessNode, target, memberName); return value; } return await InterpreterMemberAccessNodeAsync(context, memberAccessNode); @@ -254,8 +256,8 @@ namespace Serein.Script var target = await InterpretAsync(context, memberAssignmentNode.Object); var memberName = memberAssignmentNode.MemberName; var value = await InterpretAsync(context, memberAssignmentNode.Value); - if (target is null) throw new SereinSciptException(memberAssignmentNode, $"无法设置成员,对象为 null \"{memberAssignmentNode.Object.Code}\""); - SetPropertyValue(target, memberName, value); + if (target is null) throw new SereinSciptParserException(memberAssignmentNode, $"无法设置成员,对象为 null \"{memberAssignmentNode.Object.Code}\""); + await SetPropertyValueAsync(memberAssignmentNode, target, memberName, value); return value; } return await InterpreterMemberAssignmentNodeAsync(context, memberAssignmentNode); @@ -263,13 +265,13 @@ namespace Serein.Script async Task InterpreterMemberFunctionCallNodeAsync(IScriptInvokeContext context, MemberFunctionCallNode memberFunctionCallNode) { var target = await InterpretAsync(context, memberFunctionCallNode.Object); - if (!MethodNodeDelegateCaches.TryGetValue(memberFunctionCallNode, out DelegateDetails? delegateDetails)) + if (!ASTDelegateDetails.TryGetValue(memberFunctionCallNode, out DelegateDetails? delegateDetails)) { var methodName = memberFunctionCallNode.FunctionName; var methodInfo = memberFunctionCallNode.Arguments.Count == 0 ? target?.GetType().GetMethod(methodName, []) : target?.GetType().GetMethod(methodName);// 获取参数列表的类型 - if (methodInfo is null) throw new SereinSciptException(memberFunctionCallNode, $"对象没有方法\"{memberFunctionCallNode.FunctionName}\""); + if (methodInfo is null) throw new SereinSciptParserException(memberFunctionCallNode, $"对象没有方法\"{memberFunctionCallNode.FunctionName}\""); delegateDetails = new DelegateDetails(methodInfo); - MethodNodeDelegateCaches[memberFunctionCallNode] = delegateDetails; + ASTDelegateDetails[memberFunctionCallNode] = delegateDetails; } // 查询是否有缓存 @@ -310,7 +312,7 @@ namespace Serein.Script // 查找并执行对应的函数 if (!SereinScript.FunctionDelegates.TryGetValue(funcName, out DelegateDetails? function)) - throw new SereinSciptException(functionCallNode, $"没有挂载方法\"{functionCallNode.FunctionName}\""); + throw new SereinSciptParserException(functionCallNode, $"没有挂载方法\"{functionCallNode.FunctionName}\""); /* if (!function.EmitMethodInfo.IsStatic) { @@ -336,34 +338,50 @@ namespace Serein.Script } + /// /// 设置对象成员 /// + /// 节点,用于缓存委托避免重复反射 /// 对象 /// 属性名称 /// 属性值 /// - private void SetPropertyValue(object target, string memberName, object? value) + private async Task SetPropertyValueAsync(ASTNode node, object target, string memberName, object? value) { + if(ASTDelegateDetails.TryGetValue(node ,out DelegateDetails? delegateDetails)) + { + await delegateDetails.InvokeAsync(target, [value]); + return; + } + var targetType = target?.GetType(); if (targetType is null) return; var propertyInfo = targetType.GetProperty(memberName); if (propertyInfo is null) { - var fieldInfo = target?.GetType().GetRuntimeField(memberName); + FieldInfo? fieldInfo = target?.GetType().GetRuntimeField(memberName); if (fieldInfo is null) { throw new Exception($"类型 {targetType} 对象没有成员\"{memberName}\""); } else { - var convertedValue = Convert.ChangeType(value, fieldInfo.FieldType); - fieldInfo.SetValue(target, convertedValue); + delegateDetails = new DelegateDetails(fieldInfo, DelegateDetails.GSType.Set); + ASTDelegateDetails[node] = delegateDetails; // 缓存委托 + await delegateDetails.InvokeAsync(target, [value]); + + //var convertedValue = Convert.ChangeType(value, fieldInfo.FieldType); + //fieldInfo.SetValue(target, convertedValue); } } else { - if (value is null) + delegateDetails = new DelegateDetails(propertyInfo, DelegateDetails.GSType.Set); + ASTDelegateDetails[node] = delegateDetails; // 缓存委托 + await delegateDetails.InvokeAsync(target, [value]); + + /*if (value is null) { propertyInfo.SetValue(target, null); return; @@ -380,19 +398,27 @@ namespace Serein.Script else { throw new Exception($"类型 {targetType} 对象成员\"{memberName}\" 赋值时异常"); - } + }*/ } } /// /// 从对象获取值 /// + /// 节点,用于缓存委托避免重复反射 /// 对象 /// 成员名称 /// /// - private object? GetPropertyValue(object target, string memberName) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private async Task GetPropertyValueAsync(ASTNode node, object target, string memberName) { + if (ASTDelegateDetails.TryGetValue(node, out DelegateDetails? delegateDetails)) + { + var result = await delegateDetails.InvokeAsync(target, []); + return result; + } + var targetType = target?.GetType(); if (targetType is null) return null; var propertyInfo = targetType.GetProperty(memberName); @@ -405,35 +431,63 @@ namespace Serein.Script } else { - return fieldInfo.GetValue(target); + delegateDetails = new DelegateDetails(propertyInfo, DelegateDetails.GSType.Get); + ASTDelegateDetails[node] = delegateDetails; // 缓存委托 + var result = await delegateDetails.InvokeAsync(target, []); + return result; + + //return fieldInfo.GetValue(target); } } else { - return propertyInfo.GetValue(target); + delegateDetails = new DelegateDetails(propertyInfo, DelegateDetails.GSType.Get); + ASTDelegateDetails[node] = delegateDetails; // 缓存委托 + var result = await delegateDetails.InvokeAsync(target, []); + return result; + //return propertyInfo.GetValue(target); } } + /// /// 设置集合成员 /// + /// 节点,用于缓存委托避免重复反射 /// /// /// /// - /// - private void SetCollectionValue(object collectionValue, object indexValue, object valueValue) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private async Task SetCollectionValueAsync(ASTNode node, object collectionValue, object indexValue, object valueValue) { + if (ASTDelegateDetails.TryGetValue(node, out DelegateDetails? delegateDetails)) + { + await delegateDetails.InvokeAsync(collectionValue, [indexValue, valueValue]); + return; + } + else + { + var collectionType = collectionValue.GetType(); // 目标对象的类型 + delegateDetails = new DelegateDetails(collectionType, DelegateDetails.EmitType.CollectionSetter); + ASTDelegateDetails[node] = delegateDetails; // 缓存委托 + await delegateDetails.InvokeAsync(collectionValue, [indexValue, valueValue]); + return; + } + +#if false + // 解析数组/集合名与索引部分 var targetType = collectionValue.GetType(); // 目标对象的类型 #region 处理键值对 if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { // 目标是键值对 - var method = targetType.GetMethod("set_Item", BindingFlags.Public | BindingFlags.Instance); - if (method is not null) + var methodInfo = targetType.GetMethod("set_Item", BindingFlags.Public | BindingFlags.Instance); + if (methodInfo is not null) { - method.Invoke(collectionValue, [indexValue, valueValue]); + + methodInfo.Invoke(collectionValue, [indexValue, valueValue]); } } #endregion @@ -465,18 +519,40 @@ namespace Serein.Script } } #endregion - throw new ArgumentException($"解析异常, {collectionValue} 并非有效集合。"); + throw new ArgumentException($"解析异常, {collectionValue} 并非有效集合。"); +#endif } /// /// 获取集合中的成员 /// + /// 节点,用于缓存委托避免重复反射 /// /// /// - /// - private object? GetCollectionValue(object collectionValue, object indexValue) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private async Task GetCollectionValueAsync(ASTNode node, object collectionValue, object indexValue) { + if (ASTDelegateDetails.TryGetValue(node, out DelegateDetails? delegateDetails)) + { + var result = await delegateDetails.InvokeAsync(collectionValue, [indexValue]); + return result; + } + else + { + // 针对 string 特化 + if(collectionValue is string chars && indexValue is int index) + { + return chars[index]; + } + var collectionType = collectionValue.GetType(); // 目标对象的类型 + delegateDetails = new DelegateDetails(collectionType, DelegateDetails.EmitType.CollectionGetter); + ASTDelegateDetails[node] = delegateDetails; // 缓存委托 + var result = await delegateDetails.InvokeAsync(collectionValue, [indexValue]); + return result; + } + +#if false // 解析数组/集合名与索引部分 var targetType = collectionValue.GetType(); // 目标对象的类型 #region 处理键值对 @@ -504,7 +580,7 @@ namespace Serein.Script { throw new ArgumentException($"解析{collectionValue}节点时,数组下标越界。"); } - + return array.GetValue(index); } else if (collectionValue is IList list) @@ -519,7 +595,8 @@ namespace Serein.Script } #endregion - throw new ArgumentException($"解析{collectionValue}节点时,左值并非有效集合。"); + throw new ArgumentException($"解析{collectionValue}节点时,左值并非有效集合。"); +#endif } diff --git a/Serein.Script/SereinScriptMethodInfo.cs b/Serein.Script/SereinScriptMethodInfo.cs new file mode 100644 index 0000000..9e321d7 --- /dev/null +++ b/Serein.Script/SereinScriptMethodInfo.cs @@ -0,0 +1,22 @@ +namespace Serein.Script +{ + public class SereinScriptMethodInfo + { + public string ClassName { get; set; } + public string MethodName { get; set; } + + public Type? ReturnType { get; set; } + + public bool IsAsync { get; set; } + + public List ParamInfos { get; set; } + + public string CsharpCode { get; set; } + + public class SereinScriptParamInfo + { + public string ParamName { get; set; } + public Type ParameterType { get; set; } + } + } +} diff --git a/Serein.Script/SereinScriptToCsharpScript.cs b/Serein.Script/SereinScriptToCsharpScript.cs index 9c7a869..a1051e2 100644 --- a/Serein.Script/SereinScriptToCsharpScript.cs +++ b/Serein.Script/SereinScriptToCsharpScript.cs @@ -18,8 +18,12 @@ namespace Serein.Script /// /// 将 Serein 脚本转换为 C# 脚本的类 /// - internal class SereinScriptToCsharpScript + public class SereinScriptToCsharpScript { + public const string ClassName = nameof(SereinScriptToCsharpScript); + + public SereinScriptMethodInfo sereinScriptMethodInfo ; + /// /// 符号表 /// @@ -62,19 +66,35 @@ namespace Serein.Script private List> _classDefinitions = new List>(); - public string CompileToCSharp(string mehtodName, ProgramNode programNode, Dictionary? param) + public SereinScriptMethodInfo CompileToCSharp(string mehtodName, ProgramNode programNode, Dictionary? param) { _codeBuilder.Clear(); + sereinScriptMethodInfo = new SereinScriptMethodInfo() + { + ClassName = ClassName, + ParamInfos = new List(), + MethodName = mehtodName, + }; var sb = _codeBuilder; var methodResultType = _symbolInfos[programNode]; if(methodResultType == typeof(void)) { methodResultType = typeof(object); } - var taskFullName = typeof(Task).FullName; - var returnContent = _isTaskMain ? $"global::{taskFullName}" : $"global::{methodResultType.FullName}"; + var taskFullName = typeof(Task).FullName; + string? returnContent; + if (_isTaskMain) + { + returnContent = $"global::{taskFullName}"; + sereinScriptMethodInfo.IsAsync = true; + } + else + { + returnContent = $"global::{methodResultType.FullName}"; + sereinScriptMethodInfo.IsAsync = false; + } - AppendLine("public class SereinScriptToCsharp"); + AppendLine($"public partial class {ClassName}"); AppendLine( "{"); Indent(); if(param is null || param.Count == 0) @@ -92,10 +112,13 @@ namespace Serein.Script ConvertCode(stmt); // 递归遍历 Append(";"); } - if(!_symbolInfos.Keys.Any(node => node is ReturnNode)) + + if (_symbolInfos[programNode] == typeof(void)) { + AppendLine(""); AppendLine("return null;"); } + Unindent(); AppendLine("}"); Unindent(); @@ -105,16 +128,24 @@ namespace Serein.Script { cd.Invoke(sb); } - - return sb.ToString(); + sereinScriptMethodInfo.CsharpCode = sb.ToString(); + sereinScriptMethodInfo.ReturnType = methodResultType; + return sereinScriptMethodInfo; } private string GetMethodParamster(Dictionary param) { var values = param.Select(kvp => { - _local[kvp.Key] = kvp.Value; - return $"global::{kvp.Value.FullName} {kvp.Key}"; + var paramName = kvp.Key; + var type = kvp.Value; + _local[paramName] = type; + sereinScriptMethodInfo.ParamInfos.Add(new SereinScriptMethodInfo.SereinScriptParamInfo + { + ParameterType = type, + ParamName = paramName, + }); + return $"global::{type.FullName} {paramName}"; }); return string.Join(',', values); } diff --git a/Workbench/Node/View/ActionNodeControl.xaml.cs b/Workbench/Node/View/ActionNodeControl.xaml.cs index be84931..bcd728a 100644 --- a/Workbench/Node/View/ActionNodeControl.xaml.cs +++ b/Workbench/Node/View/ActionNodeControl.xaml.cs @@ -18,10 +18,6 @@ namespace Serein.Workbench.Node.View { DataContext = viewModel; InitializeComponent(); - /*if(ExecuteJunctionControl.MyNode != null) - { - ExecuteJunctionControl.MyNode.Guid = viewModel.NodeModel.Guid; - }*/ } /// diff --git a/Workbench/Node/View/ScriptNodeControl.xaml b/Workbench/Node/View/ScriptNodeControl.xaml index 7cf3d20..732929b 100644 --- a/Workbench/Node/View/ScriptNodeControl.xaml +++ b/Workbench/Node/View/ScriptNodeControl.xaml @@ -55,6 +55,7 @@ + diff --git a/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs b/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs index 5e9e39b..eb467d7 100644 --- a/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs @@ -3,6 +3,7 @@ using Serein.Library.Utils; using Serein.NodeFlow.Model; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -45,6 +46,14 @@ namespace Serein.Workbench.Node.ViewModel { NodeModel.ReloadScript(); // 工作台重新加载脚本 }); + + CommandGenerateCode = new RelayCommand(o => + { + var info = NodeModel.ToCsharpMethodInfo($"Test_{nodeModel.Guid.Replace("-","_")}"); // 工作台重新加载脚本 + if (info is null) return; + SereinEnv.WriteLine(InfoType.INFO, $"{info.ClassName}.{info.MethodName}({string.Join(",", info.ParamInfos.Select(i => $"global::{i.ParameterType.FullName} {i.ParamName}"))})"); + SereinEnv.WriteLine(InfoType.INFO, info.CsharpCode); + }); } @@ -58,6 +67,11 @@ namespace Serein.Workbench.Node.ViewModel /// public ICommand CommandExecuting { get; } + /// + /// 生成c#代码 + /// + public ICommand CommandGenerateCode { get; } + } diff --git a/Workbench/ViewModels/MainMenuBarViewModel.cs b/Workbench/ViewModels/MainMenuBarViewModel.cs index b940314..a6cbc41 100644 --- a/Workbench/ViewModels/MainMenuBarViewModel.cs +++ b/Workbench/ViewModels/MainMenuBarViewModel.cs @@ -144,7 +144,7 @@ namespace Serein.Workbench.ViewModels } catch (Exception ex) { - + Debug.WriteLine(ex.Message); } flowEnvironment.StartRemoteServerAsync(); } diff --git a/Workbench/Views/BaseNodesView.xaml b/Workbench/Views/BaseNodesView.xaml index b47227f..6ce395d 100644 --- a/Workbench/Views/BaseNodesView.xaml +++ b/Workbench/Views/BaseNodesView.xaml @@ -14,9 +14,9 @@ - - - + + +