diff --git a/Library.Core/DynamicContext.cs b/Library.Core/DynamicContext.cs index 735a5f7..8f4eae4 100644 --- a/Library.Core/DynamicContext.cs +++ b/Library.Core/DynamicContext.cs @@ -40,6 +40,11 @@ namespace Serein.Library.Core /// public ConnectionInvokeType NextOrientation { get; set; } + /// + /// 运行时异常信息 + /// + public Exception ExceptionOfRuning { get; set; } + /// /// 每个流程上下文分别存放节点的当前数据 /// @@ -138,6 +143,10 @@ namespace Serein.Library.Core disposable?.Dispose(); } } + else + { + + } } foreach (var nodeObj in ContextShareData.Values) { @@ -158,5 +167,92 @@ namespace Serein.Library.Core RunState = RunState.Completion; } + + private void Dispose(ref IDictionary keyValuePairs) + { + foreach (var nodeObj in keyValuePairs.Values) + { + if (nodeObj is null) + { + continue; + } + + if (nodeObj is IDisposable disposable) /* typeof(IDisposable).IsAssignableFrom(nodeObj?.GetType()) &&*/ + { + disposable?.Dispose(); + } + else if (nodeObj is IDictionary tmpDict) + { + Dispose(ref tmpDict); + } + else if (nodeObj is ICollection tmpList) + { + Dispose(ref tmpList); + } + else if (nodeObj is IList tmpList2) + { + Dispose(ref tmpList2); + } + } + keyValuePairs.Clear(); + } + private void Dispose(ref ICollection list) + { + foreach (var nodeObj in list) + { + if (nodeObj is null) + { + continue; + } + + if (nodeObj is IDisposable disposable) /* typeof(IDisposable).IsAssignableFrom(nodeObj?.GetType()) &&*/ + { + disposable?.Dispose(); + } + else if (nodeObj is IDictionary tmpDict) + { + Dispose(ref tmpDict); + } + else if (nodeObj is ICollection tmpList) + { + Dispose(ref tmpList); + } + else if (nodeObj is IList tmpList2) + { + Dispose(ref tmpList2); + } + } + + list.Clear(); + } + private void Dispose(ref IList list) + { + foreach (var nodeObj in list) + { + if (nodeObj is null) + { + continue; + } + + if (nodeObj is IDisposable disposable) /* typeof(IDisposable).IsAssignableFrom(nodeObj?.GetType()) &&*/ + { + disposable?.Dispose(); + } + else if (nodeObj is IDictionary tmpDict) + { + Dispose(ref tmpDict); + } + else if (nodeObj is ICollection tmpList) + { + Dispose(ref tmpList); + } + else if (nodeObj is IList tmpList2) + { + Dispose(ref tmpList2); + } + } + + list.Clear(); + } } } diff --git a/Library.Framework/DynamicContext.cs b/Library.Framework/DynamicContext.cs index 9a971d8..72dffc8 100644 --- a/Library.Framework/DynamicContext.cs +++ b/Library.Framework/DynamicContext.cs @@ -40,6 +40,11 @@ namespace Serein.Library.Framework.NodeFlow /// public ConnectionInvokeType NextOrientation { get; set; } + /// + /// 运行时异常信息 + /// + public Exception ExceptionOfRuning { get; set; } + /// /// 每个上下文分别存放节点的当前数据 /// diff --git a/Library/Api/IDynamicContext.cs b/Library/Api/IDynamicContext.cs index 53ada34..e5fd86a 100644 --- a/Library/Api/IDynamicContext.cs +++ b/Library/Api/IDynamicContext.cs @@ -31,6 +31,11 @@ namespace Serein.Library.Api /// ConnectionInvokeType NextOrientation { get; set; } + /// + /// 运行时异常信息 + /// + Exception ExceptionOfRuning { get; set; } + /// /// 设置节点的运行时上一节点,用以多线程中隔开不同流程的数据 /// diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 0b545a8..ea2e96c 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -857,18 +857,20 @@ namespace Serein.Library.Api /// /// 获取方法描述信息 /// + /// 程序集名称 /// 方法描述 /// 方法信息 /// - bool TryGetMethodDetailsInfo(string libraryName, string methodName, out MethodDetailsInfo mdInfo); + bool TryGetMethodDetailsInfo(string assemblyName, string methodName, out MethodDetailsInfo mdInfo); /// /// 获取指定方法的Emit委托 /// + /// 程序集名称 /// /// /// - bool TryGetDelegateDetails(string libraryName, string methodName, out DelegateDetails del); + bool TryGetDelegateDetails(string assemblyName, string methodName, out DelegateDetails del); #region 远程相关 diff --git a/Library/FlowNode/DelegateDetails.cs b/Library/FlowNode/DelegateDetails.cs index c8f27b0..55c0f83 100644 --- a/Library/FlowNode/DelegateDetails.cs +++ b/Library/FlowNode/DelegateDetails.cs @@ -26,6 +26,7 @@ namespace Serein.Library _emitDelegate = emitDelegate; } + /// /// 记录Emit委托 /// @@ -36,7 +37,9 @@ namespace Serein.Library _emitMethodType = EmitMethodType; _emitDelegate = EmitDelegate; } - /// + + + /*/// /// 更新委托方法 /// /// @@ -45,7 +48,9 @@ namespace Serein.Library { _emitMethodType = EmitMethodType; _emitDelegate = EmitDelegate; - } + }*/ + + private Delegate _emitDelegate; private EmitMethodType _emitMethodType; diff --git a/Library/FlowNode/MethodDetails.cs b/Library/FlowNode/MethodDetails.cs index d8489e5..6c2e633 100644 --- a/Library/FlowNode/MethodDetails.cs +++ b/Library/FlowNode/MethodDetails.cs @@ -1,6 +1,7 @@ using Serein.Library.Api; using Serein.Library.Utils; using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; @@ -36,7 +37,7 @@ namespace Serein.Library private bool _isProtectionParameter; /// - /// 作用实例的类型(多个相同的节点将拥有相同的类型) + /// 调用节点方法时需要的实例(多个相同的节点将拥有相同的类型) /// [PropertyInfo] private Type _actingInstanceType; @@ -77,6 +78,7 @@ namespace Serein.Library /// [PropertyInfo] private ParameterDetails[] _parameterDetailss; + //private List _parameterDetailss; /// /// 描述该方法是否存在可选参数 @@ -137,6 +139,9 @@ namespace Serein.Library && index < ParameterDetailss.Length) // 防止下标越界 { ParameterDetailss[index] = null; // 释放对象引用 + + + var tmp = ArrayHelper.RemoteToArray(ParameterDetailss, index); // 新增; UpdateParamIndex(ref tmp); ParameterDetailss = tmp; // 新增 diff --git a/Library/FlowNode/NodeModelBaseData.cs b/Library/FlowNode/NodeModelBaseData.cs index eca2ae8..976d107 100644 --- a/Library/FlowNode/NodeModelBaseData.cs +++ b/Library/FlowNode/NodeModelBaseData.cs @@ -62,25 +62,6 @@ namespace Serein.Library /// [PropertyInfo(IsProtection = true)] private MethodDetails _methodDetails ; - - /// - /// 运行时的上一节点 - /// - //[PropertyInfo] - //private NodeModelBase _previousNode ; - - /// - /// 当前节点执行完毕后需要执行的下一个分支的类别 - /// - //[PropertyInfo] - //private ConnectionInvokeType _nextOrientation = ConnectionInvokeType.None; - - /// - /// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值) - /// - [PropertyInfo] - private Exception _runingException ; - } diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs index faec818..aaba4c6 100644 --- a/Library/FlowNode/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Linq.Expressions; using System.Net.Http.Headers; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -109,7 +110,7 @@ namespace Serein.Library { // 保存的参数信息项数量大于方法本身的方法入参数量(可能存在可变入参) var length = nodeInfo.ParameterData.Length - pds.Length; // 需要扩容的长度 - this.MethodDetails.ParameterDetailss = ArrayHelper.ArrayExpansion(pds, length); // 扩容入参描述数组 + this.MethodDetails.ParameterDetailss = ArrayHelper.Expansion(pds, length); // 扩容入参描述数组 pds = this.MethodDetails.ParameterDetailss; var startParmsPd = pds[md.ParamsArgIndex]; // 获取可变入参参数描述 for(int i = md.ParamsArgIndex + 1; i <= md.ParamsArgIndex + length; i++) @@ -144,6 +145,10 @@ namespace Serein.Library } #endregion + #region 程序集更新,更新节点方法描述、以及所有入参描述的类型 + + #endregion + #region 调试中断 /// @@ -256,7 +261,6 @@ namespace Serein.Library newFlowData = null; await Console.Out.WriteLineAsync($"节点[{this.MethodDetails?.MethodName}]异常:" + ex); context.NextOrientation = ConnectionInvokeType.IsError; - currentNode.RuningException = ex; } @@ -317,7 +321,7 @@ namespace Serein.Library { throw new Exception($"节点{this.Guid}不存在方法信息,请检查是否需要重写节点的ExecutingAsync"); } - if (!context.Env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) + if (!context.Env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行到某个节点 { throw new Exception($"节点{this.Guid}不存在对应委托"); } @@ -700,4 +704,68 @@ namespace Serein.Library #endregion } + + +#if false + public static class NodeModelExtension + { + /// + /// 程序集更新,更新节点方法描述、以及所有入参描述的类型 + /// + /// 节点Model + /// 新的方法描述 + public static void UploadMethod(this NodeModelBase nodeModel, MethodDetails newMd) + { + var thisMd = nodeModel.MethodDetails; + + thisMd.ActingInstanceType = newMd.ActingInstanceType; // 更新方法需要的类型 + + var thisPds = thisMd.ParameterDetailss; + var newPds = newMd.ParameterDetailss; + // 当前存在可变参数,且新的方法也存在可变参数,需要把可变参数的数目与值传递过去 + if (thisMd.HasParamsArg && newMd.HasParamsArg) + { + int paramsLength = thisPds.Length - thisMd.ParamsArgIndex - 1; // 确定扩容长度 + newMd.ParameterDetailss = ArrayHelper.Expansion(newPds, paramsLength);// 为新方法的入参参数描述进行扩容 + newPds = newMd.ParameterDetailss; + int index = newMd.ParamsArgIndex; // 记录 + var templatePd = newPds[newMd.ParamsArgIndex]; // 新的入参模板 + for (int i = thisMd.ParamsArgIndex; i < thisPds.Length; i++) + { + ParameterDetails thisPd = thisPds[i]; + var newPd = templatePd.CloneOfModel(nodeModel); // 复制参数描述 + newPd.Index = i + 1; // 更新索引 + newPd.IsParams = true; + newPd.DataValue = thisPd.DataValue; // 保留参数值 + newPd.ArgDataSourceNodeGuid = thisPd.ArgDataSourceNodeGuid; // 保留参数来源信息 + newPd.ArgDataSourceType = thisPd.ArgDataSourceType; // 保留参数来源信息 + newPd.IsParams = thisPd.IsParams; // 保留显式参数设置 + newPds[index++] = newPd; + } + } + + + var thidPdLength = thisMd.HasParamsArg ? thisMd.ParamsArgIndex : thisPds.Length; + // 遍历当前的参数描述(不包含可变参数),找到匹配项,复制必要的数据进行保留 + for (int i = 0; i < thisPds.Length; i++) + { + ParameterDetails thisPd = thisPds[i]; + var newPd = newPds.FirstOrDefault(t_newPd => !t_newPd.IsParams // 不为可变参数 + && t_newPd.Name.Equals(thisPd.Name, StringComparison.OrdinalIgnoreCase) // 存在相同名称 + && t_newPd.DataType.Name.Equals(thisPd.DataType.Name) // 存在相同入参类型名称(以类型作为区分) + ); + if (newPd != null) // 如果匹配上了 + { + newPd.DataValue = thisPd.DataValue; // 保留参数值 + newPd.ArgDataSourceNodeGuid = thisPd.ArgDataSourceNodeGuid; // 保留参数来源信息 + newPd.ArgDataSourceType = thisPd.ArgDataSourceType; // 保留参数来源信息 + newPd.IsParams = thisPd.IsParams; // 保留显式参数设置 + } + } + thisMd.ReturnType = newMd.ReturnType; + nodeModel.MethodDetails = newMd; + + } + } +#endif } diff --git a/Library/SereinBaseFunction.cs b/Library/SereinBaseFunction.cs new file mode 100644 index 0000000..8286c19 --- /dev/null +++ b/Library/SereinBaseFunction.cs @@ -0,0 +1,108 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.Utils; +using Serein.Library.Utils.SereinExpression; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; + +namespace Serein.Library +{ + /// + /// 基础功能 + /// + + [DynamicFlow(Name ="[基础功能]")] + public class SereinBaseFunction + { + //[NodeAction(NodeType.Action,"条件节点")] + //private bool SereinConditionNode(IDynamicContext context, + // object targetObject, + // string exp = "ISPASS") + //{ + // var isPass = SereinConditionParser.To(targetObject, exp); + // context.NextOrientation = isPass ? ConnectionInvokeType.IsSucceed : ConnectionInvokeType.IsFail; + // return isPass; + //} + + //[NodeAction(NodeType.Action, "表达式节点")] + //private object SereinExpNode(IDynamicContext context, + // object targetObject, + // string exp) + //{ + + // exp = "@" + exp; + // var newData = SerinExpressionEvaluator.Evaluate(exp, targetObject, out bool isChange); + // object result; + // if (isChange || exp.StartsWith("@GET",System.StringComparison.OrdinalIgnoreCase)) + // { + // result = newData; + // } + // else + // { + // result = targetObject; + // } + // context.NextOrientation = ConnectionInvokeType.IsSucceed; + // return result; + //} + + + + [NodeAction(NodeType.Action, "键值对组装")] + private Dictionary SereinKvDataCollectionNode(/*NodeModelBase nodeModel, */ + string argName, + params object[] value) + { + //var paramsArgIndex = nodeModel.MethodDetails.ParamsArgIndex; + //var pds = nodeModel.MethodDetails.ParameterDetailss; + //var length = pds.Length - paramsArgIndex; + //for(int i = paramsArgIndex; i < pds.Length; i++) + //{ + // var pd = pds[i]; + //} + + var names = argName.Split(';'); + var count = Math.Min(value.Length, names.Length); + var dict = new Dictionary(); + for (int i = 0; i < count; i++) + { + dict[names[i]] = value[i]; + } + return dict; + } + + [NodeAction(NodeType.Action, "数组组装")] + private object[] SereinListDataCollectionNode(params object[] value) + { + return value; + } + + [NodeAction(NodeType.Action, "输出")] + private object[] SereinConsoleNode(params object[] value) + { + foreach (var item in value) + { + Console.WriteLine(item); + } + return value; + } + + + /* if (!DynamicObjectHelper.TryResolve(dict, className, out var result)) + { + Console.WriteLine("赋值过程中有错误,请检查属性名和类型!"); + } + else + { + DynamicObjectHelper.PrintObjectProperties(result); + } + //if (!ObjDynamicCreateHelper.TryResolve(externalData, "RootType", out var result)) + //{ + // Console.WriteLine("赋值过程中有错误,请检查属性名和类型!"); + + //} + //ObjDynamicCreateHelper.PrintObjectProperties(result!); + return result;*/ + } +} diff --git a/Library/Utils/ArrayHelper.cs b/Library/Utils/ArrayHelper.cs index 2fab977..ed83a60 100644 --- a/Library/Utils/ArrayHelper.cs +++ b/Library/Utils/ArrayHelper.cs @@ -19,7 +19,7 @@ namespace Serein.Library.Utils /// 扩容长度 /// 新的数组 /// length 传入负值 - public static T[] ArrayExpansion(T[] original, int length) + public static T[] Expansion(T[] original, int length) { if(length == 0) { @@ -58,7 +58,10 @@ namespace Serein.Library.Utils public static T[] AddToArray(T[] original, T newObject) { // 创建一个新数组,比原数组大1 - T[] newArray = ArrayHelper.ArrayExpansion(original, 1); + T[] newArray = ArrayHelper.Expansion(original, 1); + + original.CopyTo(newArray, 0); + // 将新对象放在最后一位 newArray[newArray.Length - 1] = newObject; return newArray; diff --git a/Library/Utils/DynamicObjectHelper.cs b/Library/Utils/DynamicObjectHelper.cs new file mode 100644 index 0000000..4f552e2 --- /dev/null +++ b/Library/Utils/DynamicObjectHelper.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library.Utils +{ + public class DynamicObjectHelper + { + // 类型缓存,键为类型的唯一名称(可以根据实际需求调整生成方式) + static Dictionary typeCache = new Dictionary(); + + public static object Resolve(IDictionary properties, string typeName) + { + var obj = CreateObjectWithProperties(properties, typeName); + //SetPropertyValues(obj, properties); + return obj; + } + public static bool TryResolve(IDictionary properties, string typeName, out object result) + { + result = CreateObjectWithProperties(properties, typeName); + bool success = SetPropertyValuesWithValidation(result, properties); + return success; + // 打印赋值结果 + + } + // 递归方法:打印对象属性及类型 + public static void PrintObjectProperties(object obj, string indent = "") + { + var objType = obj.GetType(); + foreach (var prop in objType.GetProperties()) + { + var value = prop.GetValue(obj); + Console.WriteLine($"{indent}{prop.Name} (Type: {prop.PropertyType.Name}): {value}"); + + if (value != null) + { + if (prop.PropertyType.IsArray) // 处理数组类型 + { + var array = (Array)value; + Console.WriteLine($"{indent}{prop.Name} is an array with {array.Length} elements:"); + for (int i = 0; i < array.Length; i++) + { + var element = array.GetValue(i); + if (element != null && element.GetType().IsClass && !(element is string)) + { + Console.WriteLine($"{indent}\tArray[{i}] (Type: {element.GetType().Name}) contains a nested object:"); + PrintObjectProperties(element, indent + "\t\t"); + } + else + { + Console.WriteLine($"{indent}\tArray[{i}] (Type: {element?.GetType().Name}): {element}"); + } + } + } + else if (value.GetType().IsClass && !(value is string)) // 处理嵌套对象 + { + Console.WriteLine($"{indent}{prop.Name} contains a nested object:"); + PrintObjectProperties(value, indent + "\t"); + } + } + } + } + + + + // 方法 1: 创建动态类型及其对象实例 + public static object CreateObjectWithProperties(IDictionary properties, string typeName) + { + // 如果类型已经缓存,直接返回缓存的类型 + if (typeCache.ContainsKey(typeName)) + { + return Activator.CreateInstance(typeCache[typeName]); + } + + // 定义动态程序集和模块 + var assemblyName = new AssemblyName("DynamicAssembly"); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); + + // 定义动态类型 + var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public); + + // 为每个属性名和值添加相应的属性到动态类型中 + foreach (var kvp in properties) + { + string propName = kvp.Key; + object propValue = kvp.Value; + Type propType; + + if (propValue is IList>) // 处理数组类型 + { + var nestedPropValue = (propValue as IList>)[0]; + var nestedType = CreateObjectWithProperties(nestedPropValue, $"{propName}Element"); + propType = nestedType.GetType().MakeArrayType(); // 创建数组类型 + } + else if (propValue is Dictionary nestedProperties) + { + // 如果值是嵌套的字典,递归创建嵌套类型 + propType = CreateObjectWithProperties(nestedProperties, $"{typeName}_{propName}").GetType(); + } + else + { + // 如果是普通类型,使用值的类型 + propType = propValue?.GetType() ?? typeof(object); + } + + // 定义私有字段和公共属性 + var fieldBuilder = typeBuilder.DefineField("_" + propName, propType, FieldAttributes.Private); + var propertyBuilder = typeBuilder.DefineProperty(propName, PropertyAttributes.HasDefault, propType, null); + + // 定义 getter 方法 + var getMethodBuilder = typeBuilder.DefineMethod( + "get_" + propName, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + propType, + Type.EmptyTypes); + + var getIL = getMethodBuilder.GetILGenerator(); + getIL.Emit(OpCodes.Ldarg_0); + getIL.Emit(OpCodes.Ldfld, fieldBuilder); + getIL.Emit(OpCodes.Ret); + + // 定义 setter 方法 + var setMethodBuilder = typeBuilder.DefineMethod( + "set_" + propName, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + null, + new Type[] { propType }); + + var setIL = setMethodBuilder.GetILGenerator(); + setIL.Emit(OpCodes.Ldarg_0); + setIL.Emit(OpCodes.Ldarg_1); + setIL.Emit(OpCodes.Stfld, fieldBuilder); + setIL.Emit(OpCodes.Ret); + + // 将 getter 和 setter 方法添加到属性 + propertyBuilder.SetGetMethod(getMethodBuilder); + propertyBuilder.SetSetMethod(setMethodBuilder); + } + + // 创建类型并缓存 + var dynamicType = typeBuilder.CreateType(); + typeCache[typeName] = dynamicType; + + // 创建对象实例 + return Activator.CreateInstance(dynamicType); + } + + // 方法 2: 递归设置对象的属性值 + public static void SetPropertyValues(object obj, Dictionary properties) + { + var objType = obj.GetType(); + + foreach (var kvp in properties) + { + var propInfo = objType.GetProperty(kvp.Key); + object value = kvp.Value; + + // 如果值是嵌套的字典类型,递归处理嵌套对象 + if (value is Dictionary nestedProperties) + { + // 创建嵌套对象 + var nestedObj = Activator.CreateInstance(propInfo.PropertyType); + + // 递归设置嵌套对象的值 + SetPropertyValues(nestedObj, nestedProperties); + + // 将嵌套对象赋值给属性 + propInfo.SetValue(obj, nestedObj); + } + else + { + // 直接赋值给属性 + propInfo.SetValue(obj, value); + } + } + } + // 方法 2: 递归设置对象的属性值(带验证) + + public static bool SetPropertyValuesWithValidation(object obj, IDictionary properties) + { + var objType = obj.GetType(); + bool allSuccessful = true; // 标记是否所有属性赋值成功 + + foreach (var kvp in properties) + { + var propName = kvp.Key; + var propValue = kvp.Value; + + var propInfo = objType.GetProperty(propName); + + if (propInfo == null) + { + // 属性不存在,打印警告并标记失败 + Console.WriteLine($"Warning: 属性 '{propName}' 不存在于类型 '{objType.Name}' 中,跳过赋值。"); + allSuccessful = false; + continue; + } + + // 检查属性类型是否与要赋的值兼容 + var targetType = propInfo.PropertyType; + if (!IsCompatibleType(targetType, propValue)) + { + // 如果类型不兼容,打印错误并标记失败 + Console.WriteLine($"Error: 无法将类型 '{propValue?.GetType().Name}' 赋值给属性 '{propName}' (Type: {targetType.Name}),跳过赋值。"); + allSuccessful = false; + continue; + } + + try + { + // 如果值是一个嵌套对象,递归赋值 + if (propValue is Dictionary nestedProperties) + { + var nestedObj = Activator.CreateInstance(propInfo.PropertyType); + if (nestedObj != null && SetPropertyValuesWithValidation(nestedObj, nestedProperties)) + { + propInfo.SetValue(obj, nestedObj); + } + else + { + allSuccessful = false; // 嵌套赋值失败 + } + } + else if (propValue is IList> list) // 处理列表 + { + // 获取目标类型的数组元素类型 + var elementType = propInfo.PropertyType.GetElementType(); + if (elementType != null) + { + var array = Array.CreateInstance(elementType, list.Count); + + for (int i = 0; i < list.Count; i++) + { + var item = Activator.CreateInstance(elementType); + if (item != null && SetPropertyValuesWithValidation(item, list[i])) + { + array.SetValue(item, i); + } + else + { + allSuccessful = false; // 赋值失败 + } + } + propInfo.SetValue(obj, array); + } + } + else + { + // 直接赋值 + propInfo.SetValue(obj, propValue); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error: 为属性 '{propName}' 赋值时发生异常:{ex.Message}"); + allSuccessful = false; + } + } + + return allSuccessful; + } + + // 检查类型兼容性的方法(支持嵌套类型) + static bool IsCompatibleType(Type targetType, object value) + { + if (value == null) + { + // 如果值为null,且目标类型是引用类型或者可空类型,则兼容 + return !targetType.IsValueType || Nullable.GetUnderlyingType(targetType) != null; + } + + // 检查值的类型是否与目标类型相同,或是否可以转换为目标类型 + if (targetType.IsAssignableFrom(value.GetType())) + { + return true; + } + + // 处理数组类型 + if (targetType.IsArray) + { + // 检查数组的元素类型与值的类型兼容 + var elementType = targetType.GetElementType(); + return value is IList>; // 假设值是一个列表,具体处理逻辑在赋值时 + } + + // 处理嵌套类型的情况 + if (value is Dictionary && targetType.IsClass && !targetType.IsPrimitive) + { + // 如果目标类型是一个复杂对象,并且值是一个字典,可能是嵌套对象 + return true; // 假设可以递归处理嵌套对象 + } + + return false; + } + + + } +} diff --git a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs index f15ad8b..591e01c 100644 --- a/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs +++ b/Library/Utils/SereinExpression/SerinExpressionEvaluator.cs @@ -22,7 +22,7 @@ namespace Serein.Library.Utils.SereinExpression public static T Evaluate(string expression, T inputValue) { - + // 替换占位符@为输入值 expression = expression.Replace("@", inputValue.ToString()); try @@ -51,7 +51,7 @@ namespace Serein.Library.Utils.SereinExpression /// public static object Evaluate(string expression, object targetObJ, out bool isChange) { - if(expression is null || targetObJ is null) + if (expression is null || targetObJ is null) { throw new Exception("表达式条件expression is null、 targetObJ is null"); } @@ -88,13 +88,13 @@ namespace Serein.Library.Utils.SereinExpression throw new NotSupportedException($"Operation {operation} is not supported."); } - + return result; } - private static readonly char[] separator = new char[] { '(', ')' }; + private static readonly char[] separator = new char[] { '(', ')' }; private static readonly char[] separatorArray = new char[] { ',' }; /// @@ -128,6 +128,7 @@ namespace Serein.Library.Utils.SereinExpression return method.Invoke(target, parameterValues); } + /// /// 获取值 /// @@ -156,56 +157,53 @@ namespace Serein.Library.Utils.SereinExpression } var targetType = target?.GetType(); // 目标对象的类型 - if(targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + #region 处理键值对 + if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { var typetmp = target.GetType().FullName; // 目标是键值对 var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1); var method = targetType.GetMethod("get_Item", BindingFlags.Public | BindingFlags.Instance); - if(method != null) + if (method != null) { - var result = method.Invoke(target, new object[] { indexStr }); - if(result != null) + var result = method.Invoke(target, new object[] { indexStr }); + if (result != null) { - return result; + target = result; } } - //var dict = target as Dictionary; - ////var dict = (Dictionary)target; - //var temp = dict[indexStr]; - ////if (target is Dictionary dict) - ////{ - //// var temp = dict[indexStr]; - ////} - //var TMP2= target.GetType().GetEnumValues(); - - } + } + #endregion else { #region 表达式处理集合对象 // 获取数组或集合对象 - var arrayProperty = target?.GetType().GetProperty(arrayName); - if (arrayProperty is null) + + // 如果arrayName为空,说明target可能是数组,而不需要再获取属性了 + if (!string.IsNullOrEmpty(arrayName)) { - var arrayField = target?.GetType().GetField(arrayName); - if (arrayField is null) + var arrayProperty = target?.GetType().GetProperty(arrayName); + if (arrayProperty is null) { - throw new ArgumentException($"Member {arrayName} not found on target."); + var arrayField = target?.GetType().GetField(arrayName); + if (arrayField is null) + { + throw new ArgumentException($"Member {arrayName} not found on target."); + } + else + { + target = arrayField.GetValue(target); + } } else { - target = arrayField.GetValue(target); + target = arrayProperty.GetValue(target); } } - else - { - target = arrayProperty.GetValue(target); - } - - + // 提取数组索引 var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1); if (!int.TryParse(indexStr, out int index)) @@ -236,7 +234,6 @@ namespace Serein.Library.Utils.SereinExpression #endregion } - } else { @@ -288,7 +285,7 @@ namespace Serein.Library.Utils.SereinExpression // 检查是否包含数组索引 var arrayIndexStart = member.IndexOf('['); - if (arrayIndexStart != -1) + if (arrayIndexStart != -1) { // 解析数组名和索引 var arrayName = member.Substring(0, arrayIndexStart); @@ -394,6 +391,7 @@ namespace Serein.Library.Utils.SereinExpression return target; } + /// /// 计算数学简单表达式 /// @@ -408,7 +406,7 @@ namespace Serein.Library.Utils.SereinExpression private static T ComputedNumber(object value, string expression) where T : struct, IComparable { T result = value.ToConvert(); - return SerinArithmeticExpressionEvaluator.Evaluate(expression, result); + return SerinArithmeticExpressionEvaluator.Evaluate(expression, result); } } } diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index de5343e..d5a6f31 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -14,6 +14,8 @@ using System.Numerics; using System.Reflection; using System.Reflection.Metadata.Ecma335; using System.Runtime.Loader; +using System.Security.AccessControl; +using System.Text; using System.Xml.Linq; using static Serein.Library.Utils.ChannelFlowInterrupt; @@ -389,6 +391,7 @@ namespace Serein.NodeFlow.Env if (flowStarter is null) { + Console.WriteLine("没有启动流程,无法运行单个节点"); return; } if (true || FlowState == RunState.Running || FlipFlopState == RunState.Running) @@ -540,9 +543,7 @@ namespace Serein.NodeFlow.Env continue; } MethodDetails? methodDetails = null; - FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, - nodeInfo.MethodName, - out methodDetails); // 加载项目时尝试获取方法信息 + FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName,out methodDetails); // 加载项目时尝试获取方法信息 var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 nodeModel.LoadInfo(nodeInfo); // 创建节点model if (nodeModel is null) @@ -724,9 +725,21 @@ namespace Serein.NodeFlow.Env /// public void LoadLibrary(string dllPath) { + try { - (var libraryInfo, var mdInfos) = FlowLibraryManagement.LoadLibrary(dllPath); + #region 检查是否已经加载本地依赖 + var thisAssembly = typeof(IFlowEnvironment).Assembly; + var thisAssemblyName = thisAssembly.GetName().Name; + if (!string.IsNullOrEmpty(thisAssemblyName) && FlowLibraryManagement.GetLibraryMdsOfAssmbly(thisAssemblyName).Count == 0) + { + var tmp = FlowLibraryManagement.LoadLibraryOfPath(thisAssembly.Location); + UIContextOperation?.Invoke(() => OnDllLoad?.Invoke(new LoadDllEventArgs(tmp.Item1, tmp.Item2))); // 通知UI创建dll面板显示 + } + + #endregion + + (var libraryInfo, var mdInfos) = FlowLibraryManagement.LoadLibraryOfPath(dllPath); if (mdInfos.Count > 0) { UIContextOperation?.Invoke(() => OnDllLoad?.Invoke(new LoadDllEventArgs(libraryInfo, mdInfos))); // 通知UI创建dll面板显示 @@ -746,8 +759,38 @@ namespace Serein.NodeFlow.Env /// public bool UnloadLibrary(string assemblyName) { - return FlowLibraryManagement.UnloadLibrary(assemblyName); + // 获取与此程序集相关的节点 + var groupedNodes = NodeModels.Values.Where(node => node.MethodDetails.AssemblyName.Equals(assemblyName)).ToArray(); + if (groupedNodes.Length == 0) + { + var isPass = FlowLibraryManagement.UnloadLibrary(assemblyName); + return isPass; + } + else + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(); + for (int i = 0; i < groupedNodes.Length; i++) + { + NodeModelBase? node = groupedNodes[i]; + sb.AppendLine($"{i} => {node.Guid}"); + } + Console.WriteLine($"无法卸载[{assemblyName}]程序集,因为这些节点依赖于此程序集:{sb.ToString()}"); + return false; + } + + //var mds = FlowLibraryManagement.GetLibraryMdsOfAssmbly(assemblyName); + //if(mds.Count > 0) + //{ + + + //} + //else + //{ + // return true; + //} + //var library = LibraryInfos.Values.FirstOrDefault(nl => assemblyName.Equals(nl.AssemblyName)); //if (library is null) //{ @@ -810,7 +853,7 @@ namespace Serein.NodeFlow.Env } else { - if (FlowLibraryManagement.TryGetMethodDetails(methodDetailsInfo.AssemblyName, + if (FlowLibraryManagement.TryGetMethodDetails(methodDetailsInfo.AssemblyName, // 创建节点 methodDetailsInfo.MethodName, out var methodDetails)) { @@ -940,6 +983,24 @@ namespace Serein.NodeFlow.Env } + /// + /// 移除连接节点之间方法调用的关系 + /// + /// 起始节点Guid + /// 目标节点Guid + /// 连接关系 + /// + public async Task RemoveConnectInvokeAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) + { + // 获取起始节点与目标节点 + var fromNode = GuidToModel(fromNodeGuid); + var toNode = GuidToModel(toNodeGuid); + if (fromNode is null || toNode is null) return false; + + var result = await RemoteConnectAsync(fromNode, toNode, connectionType); + return result; + } + /// /// 创建节点之间的参数来源关系 /// @@ -985,23 +1046,6 @@ namespace Serein.NodeFlow.Env } - /// - /// 移除连接节点之间方法调用的关系 - /// - /// 起始节点Guid - /// 目标节点Guid - /// 连接关系 - /// - public async Task RemoveConnectInvokeAsync(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType) - { - // 获取起始节点与目标节点 - var fromNode = GuidToModel(fromNodeGuid); - var toNode = GuidToModel(toNodeGuid); - if (fromNode is null || toNode is null) return false; - - var result = await RemoteConnectAsync(fromNode, toNode, connectionType); - return result; - } /// /// 移除连接节点之间参数传递的关系 @@ -1319,6 +1363,36 @@ namespace Serein.NodeFlow.Env { var nodeModel = GuidToModel(nodeGuid); if (nodeModel is null) return false; + + var argInfo = nodeModel.MethodDetails.ParameterDetailss + .Where(pd => !string.IsNullOrEmpty(pd.ArgDataSourceNodeGuid)) + .Select(pd => (pd.ArgDataSourceNodeGuid, pd.ArgDataSourceType, pd.Index)) + .ToArray(); + + #region 暂时移除连接关系 + foreach((var fromGuid, var type, var index) in argInfo) + { + await UIContextOperation.InvokeAsync(() => + OnNodeConnectChange?.Invoke( + new NodeConnectChangeEventArgs( + fromGuid, // 从哪个节点开始 + nodeModel.Guid, // 连接到那个节点 + JunctionOfConnectionType.Arg, + index, // 参数 + type, // 连接线的样式类型 + NodeConnectChangeEventArgs.ConnectChangeType.Remote // 是创建连接还是删除连接 + ))); // 通知UI + } + for (int i = 0; i < argInfo.Length; i++) + { + ParameterDetails? pd = nodeModel.MethodDetails.ParameterDetailss[i]; + var fromNode = GuidToModel(pd.ArgDataSourceNodeGuid); + if (fromNode is null) continue; + var argSourceGuid = pd.ArgDataSourceNodeGuid; + var argSourceType = pd.ArgDataSourceType; + } + #endregion + bool isPass; if (isAdd) { @@ -1328,6 +1402,22 @@ namespace Serein.NodeFlow.Env { isPass = nodeModel.MethodDetails.RemoveParamsArg(paramIndex); } + + await Task.Delay(50); + foreach ((var fromGuid, var type, var index) in argInfo) + { + await UIContextOperation.InvokeAsync(() => + OnNodeConnectChange?.Invoke( + new NodeConnectChangeEventArgs( + fromGuid, // 从哪个节点开始 + nodeModel.Guid, // 连接到那个节点 + JunctionOfConnectionType.Arg, + index, // 参数 + type, // 连接线的样式类型 + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + ))); // 通知UI + + } return isPass; } @@ -1475,7 +1565,10 @@ namespace Serein.NodeFlow.Env /// private async Task RemoteConnectAsync(NodeModelBase fromNode, NodeModelBase toNode, int argIndex) { - + if (string.IsNullOrEmpty(toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid)) + { + return false; + } toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid = null; toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType = ConnectionArgSourceType.GetPreviousNodeData; // 恢复默认值 @@ -1770,28 +1863,24 @@ namespace Serein.NodeFlow.Env /// /// private async Task ConnectArgSourceOfNodeAsync(NodeModelBase fromNode, - NodeModelBase toNode, - ConnectionArgSourceType connectionArgSourceType, - int argIndex) + NodeModelBase toNode, + ConnectionArgSourceType connectionArgSourceType, + int argIndex) { - var oldNodeGuid = toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid; - - if (!string.IsNullOrEmpty(oldNodeGuid)) + var toNodeArgSourceGuid = toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid; + if (!string.IsNullOrEmpty(toNodeArgSourceGuid)) { - //if(NodeModels.TryGetValue(oldNodeGuid, out var oldNodeModel)) + await RemoteConnectAsync(fromNode, toNode, argIndex); + //Console.WriteLine("目标入参已确定参数来源,不可连接"); + //return false; + //if (toNodeArgSourceGuid.Equals(fromNode.Guid)) //{ - // // 已经存在连接 - // await RemoteConnectAsync(oldNodeModel, toNode, argIndex); // 已经存在连接,将其移除 + // //await RemoteConnectAsync(fromNode, toNode, argIndex); // 相同起始节点不同控制点已经连接,将其移除 + //} + //else + //{ + //} - if (oldNodeGuid.Equals(fromNode.Guid)) - { - await RemoteConnectAsync(fromNode, toNode, argIndex); // 相同起始节点不同控制点已经连接,将其移除 - } - else - { - Console.WriteLine("目标入参已确定参数来源,不可连接 "); - return false; - } } toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid = fromNode.Guid; toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType = connectionArgSourceType; @@ -1801,7 +1890,7 @@ namespace Serein.NodeFlow.Env fromNode.Guid, // 从哪个节点开始 toNode.Guid, // 连接到那个节点 JunctionOfConnectionType.Arg, - (int)argIndex, // 连接线的样式类型 + argIndex, // 连接线的样式类型 connectionArgSourceType, NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 ))); // 通知UI diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index 96474b0..25e2597 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -184,7 +184,7 @@ namespace Serein.NodeFlow //object?[]? args = [Context]; foreach (var md in initMethods) // 初始化 { - if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) + if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行初始化 { throw new Exception("不存在对应委托"); } @@ -205,7 +205,7 @@ namespace Serein.NodeFlow { //object?[]? data = [md.ActingInstance, args]; //md.MethodDelegate.DynamicInvoke(data); - if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) + if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行正在加载 { throw new Exception("不存在对应委托"); } @@ -228,7 +228,7 @@ namespace Serein.NodeFlow foreach (MethodDetails? md in exitMethods) { - if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) + if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行退出执行 { throw new Exception("不存在对应委托"); } diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index 788067b..2ffc877 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -53,40 +53,36 @@ namespace Serein.NodeFlow.Model /// /// public override async Task ExecutingAsync(IDynamicContext context) - { - // 条件区域中遍历每个条件节点 - foreach (SingleConditionNode? node in ConditionNodes) - { - var state = await JudgeAsync(context, node); - context.NextOrientation = state; // 每次判读完成后,设置区域后继方向为判断结果 - if (state != ConnectionInvokeType.IsSucceed) - { - // 如果条件不通过,立刻推出循环 - break; - } - } - - //var previousNode = context.GetPreviousNode() - return Task.FromResult(context.TransmissionData(this)); // 条件区域透传上一节点的数据 - } - - - private async Task JudgeAsync(IDynamicContext context, SingleConditionNode node) { try { - await node.ExecutingAsync(context); - return context.NextOrientation; + // 条件区域中遍历每个条件节点 + foreach (SingleConditionNode? node in ConditionNodes) + { + var state = await node.ExecutingAsync(context); + if (context.NextOrientation != ConnectionInvokeType.IsSucceed) + { + // 如果条件不通过,立刻推出循环 + break; + } + } + + //var previousNode = context.GetPreviousNode() + return context.TransmissionData(this); // 条件区域透传上一节点的数据 } catch (Exception ex) { Console.WriteLine(ex.Message); context.NextOrientation = ConnectionInvokeType.IsError; - RuningException = ex; - return ConnectionInvokeType.IsError; + context.ExceptionOfRuning = ex; + return context.TransmissionData(this); // 条件区域透传上一节点的数据 } + + } + + public override ParameterData[] GetParameterdatas() { return []; diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index c97666b..f8099d6 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -108,7 +108,7 @@ namespace Serein.NodeFlow.Model catch (Exception ex) { context.NextOrientation = ConnectionInvokeType.IsError; - RuningException = ex; + context.ExceptionOfRuning = ex; } Console.WriteLine($"{result} {Expression} -> " + context.NextOrientation); diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index 4de043e..d73a17a 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -98,7 +98,7 @@ namespace Serein.NodeFlow.Model catch (Exception ex) { context.NextOrientation = ConnectionInvokeType.IsError; - RuningException = ex; + context.ExceptionOfRuning = ex; return parameter; } diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index b213c75..23dc5aa 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -43,7 +43,7 @@ namespace Serein.NodeFlow.Model #endregion MethodDetails md = MethodDetails; - if (!context.Env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) + if (!context.Env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行到某个节点 { throw new Exception("不存在对应委托"); } @@ -72,14 +72,14 @@ namespace Serein.NodeFlow.Model } await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex); context.NextOrientation = ConnectionInvokeType.None; - RuningException = ex; + context.ExceptionOfRuning = ex; return null; } catch (Exception ex) { await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex); context.NextOrientation = ConnectionInvokeType.IsError; - RuningException = ex; + context.ExceptionOfRuning = ex; return null; } finally diff --git a/NodeFlow/Tool/FlowLibrary.cs b/NodeFlow/Tool/FlowLibrary.cs index 87a18b4..6aacdd9 100644 --- a/NodeFlow/Tool/FlowLibrary.cs +++ b/NodeFlow/Tool/FlowLibrary.cs @@ -13,36 +13,35 @@ namespace Serein.NodeFlow { /// - /// + /// 加载在流程中的程序集依赖 /// public class FlowLibrary { private readonly Assembly assembly; private readonly Action actionOfUnloadAssmbly; - private string _assemblyFilePath; - public FlowLibrary(string assemblyFilePath, - Assembly assembly, + public FlowLibrary(Assembly assembly, Action actionOfUnloadAssmbly) { - this._assemblyFilePath = assemblyFilePath; this.assembly = assembly; this.actionOfUnloadAssmbly = actionOfUnloadAssmbly; - } - public string FullName => assembly.GetName().FullName; public string Version => assembly.GetName().Version.ToString(); /// /// 加载程序集时创建的方法描述 + /// Key : 方法名称 + /// Value :方法详情 /// public ConcurrentDictionary MethodDetailss { get; } = new ConcurrentDictionary(); /// /// 管理通过Emit动态构建的委托 + /// Key :方法名称 + /// Value :方法详情 /// public ConcurrentDictionary DelegateDetailss { get; } = new ConcurrentDictionary(); @@ -57,56 +56,24 @@ namespace Serein.NodeFlow /// public void Upload() { + DelegateDetailss.Clear(); + RegisterTypes.Clear(); + MethodDetailss.Clear(); actionOfUnloadAssmbly?.Invoke(); + } public NodeLibraryInfo ToInfo() { return new NodeLibraryInfo { - AssemblyName = FullName, - FileName = Path.GetFileName(_assemblyFilePath), - FilePath = _assemblyFilePath, + AssemblyName = assembly.GetName().Name, + FileName = Path.GetFileName(assembly.Location), + FilePath = assembly.Location, }; } - //public void LoadAssmely(Assembly assembly) - //{ - // (var registerTypes, var mdlist) = LoadAssembly2(assembly); - // if (mdlist.Count > 0) - // { - // var nodeLibraryInfo = new NodeLibraryInfo - // { - // //Assembly = assembly, - // AssemblyName = assembly.FullName, - // FileName = Path.GetFileName(_assemblyFilePath), - // FilePath = _assemblyFilePath, - // }; - - // //LibraryInfos.TryAdd(nodeLibraryInfo.AssemblyName, nodeLibraryInfo); - - // MethodDetailss.TryAdd(nodeLibraryInfo.AssemblyName, mdlist); - - // foreach (var md in mdlist) - // { - // MethodDetailss.TryAdd(md.MethodName, md); - // } - - // foreach (var kv in registerTypes) - // { - // if (!RegisterTypes.TryGetValue(kv.Key, out var types)) - // { - // types = new List(); - // RegisterTypes.TryAdd(kv.Key, types); - // } - // types.AddRange(kv.Value); - // } - // var mdInfos = mdlist.Select(md => md.ToInfo()).ToList(); // 转换成方法信息 - // } - //} - - /// /// 动态加载程序集 /// @@ -158,18 +125,13 @@ namespace Serein.NodeFlow // (Type, string) // Type : 具有 DynamicFlowAttribute 标记的类型 // string : 类型元数据 DynamicFlowAttribute 特性中的 Name 属性 + + types = types.Where(type => type.GetCustomAttribute() is DynamicFlowAttribute dynamicFlowAttribute + && dynamicFlowAttribute.Scan).ToList(); + foreach (var type in types) { - if (type.Name.Equals("YoloFlowControl")) - { - //var ab = type.GetCustomAttribute(); - //var attributes = assembly.GetCustomAttributes(); - //foreach (var attribute in attributes) - //{ - // Console.WriteLine(attribute.GetType().Name); - //} - } - if (type.GetCustomAttribute() is DynamicFlowAttribute dynamicFlowAttribute && dynamicFlowAttribute.Scan == true) + if (type.GetCustomAttribute() is DynamicFlowAttribute dynamicFlowAttribute) { scanTypes.Add((type, dynamicFlowAttribute.Name)); } diff --git a/NodeFlow/Tool/FlowLibraryManagement.cs b/NodeFlow/Tool/FlowLibraryManagement.cs index 80d5496..0ad3170 100644 --- a/NodeFlow/Tool/FlowLibraryManagement.cs +++ b/NodeFlow/Tool/FlowLibraryManagement.cs @@ -31,25 +31,34 @@ namespace Serein.NodeFlow.Tool /// private readonly ConcurrentDictionary _myFlowLibrarys = new ConcurrentDictionary(); - public (NodeLibraryInfo, List) LoadLibrary(string libraryfilePath) + /// + /// 加载类库 + /// + /// + /// + public (NodeLibraryInfo, List) LoadLibraryOfPath(string libraryfilePath) { - return LoadDllNodeInfo(libraryfilePath); } - public bool UnloadLibrary(string libraryName) + /// + /// 卸载类库 + /// + /// + /// + public bool UnloadLibrary(string assemblyName) { - libraryName = libraryName.Split(',')[0]; - if (_myFlowLibrarys.Remove(libraryName, out var flowLibrary)) + if (_myFlowLibrarys.Remove(assemblyName, out var flowLibrary)) { try { - flowLibrary.Upload(); + flowLibrary.Upload(); // 尝试卸载 + flowLibrary = null; return true; } catch (Exception ex) { - Console.WriteLine($"尝试卸载程序集[{libraryName}]发生错误:{ex}"); + Console.WriteLine($"尝试卸载程序集[{assemblyName}]发生错误:{ex}"); return false; } @@ -144,6 +153,20 @@ namespace Serein.NodeFlow.Tool return rsTypes; } + /// + /// 获取某个程序集下的所有方法信息,用于保存项目时调用 + /// + /// + public List GetLibraryMdsOfAssmbly(string assemblyName) + { + if (_myFlowLibrarys.TryGetValue(assemblyName, out var flowLibrary)) + { + return flowLibrary.MethodDetailss.Values.ToList(); + } + return []; + } + + /// /// 获取所有方法信息,用于保存项目时调用 @@ -165,9 +188,6 @@ namespace Serein.NodeFlow.Tool } - - - /// /// 序列化当前项目的依赖信息、节点信息,用于远程登录的场景,需要将依赖信息从本地(受控端)发送到远程(主控端) /// @@ -180,18 +200,38 @@ namespace Serein.NodeFlow.Tool #region 功能性方法 - /// - /// 从文件路径中加载程序集,返回相应的信息 - /// - /// - /// + private readonly string SereinLibraryDll = $"{nameof(Serein)}.{nameof(Serein.Library)}.dll"; + private (NodeLibraryInfo, List) LoadDllNodeInfo(string dllFilePath) { - -#if true var fileName = Path.GetFileName(dllFilePath); // 获取文件名 - var dir = Path.GetDirectoryName(dllFilePath); // 获取目录路径 - var sereinFlowLibraryPath = Path.Combine(dir, $"{nameof(Serein)}.{nameof(Serein.Library)}.dll"); + + if (SereinLibraryDll.Equals(fileName)) + { + + return LoadAssembly(typeof(IFlowEnvironment).Assembly, () => { + //Console.WriteLine("基础模块不能卸载"); + }); + } + else + { + var dir = Path.GetDirectoryName(dllFilePath); // 获取目录路径 + var sereinFlowLibraryPath = Path.Combine(dir, SereinLibraryDll); + // 每个类库下面至少需要有“Serein.Library.dll”类库依赖 + var flowAlc = new FlowLibraryAssemblyContext(sereinFlowLibraryPath, fileName); + Action actionUnload = () => + { + flowAlc?.Unload(); // 卸载程序集 + flowAlc = null; + GC.Collect(); // 强制触发GC确保卸载成功 + GC.WaitForPendingFinalizers(); + }; + var assembly = flowAlc.LoadFromAssemblyPath(dllFilePath); // 加载指定路径的程序集 + return LoadAssembly(assembly, actionUnload); + } + + /* var dir = Path.GetDirectoryName(dllFilePath); // 获取目录路径 + var sereinFlowLibraryPath = Path.Combine(dir, SereinLibraryDll); // 每个类库下面至少需要有“Serein.Library.dll”类库依赖 var flowAlc = new FlowLibraryAssemblyContext(sereinFlowLibraryPath, fileName); Action actionUnload = () => @@ -207,8 +247,29 @@ namespace Serein.NodeFlow.Tool actionUnload.Invoke(); throw new Exception($"程序集[{assembly.GetName().FullName}]已经加载过!"); } + FlowLibrary flowLibrary = new FlowLibrary(assembly, actionUnload); + if (flowLibrary.LoadAssembly(assembly)) + { + _myFlowLibrarys.TryAdd(assembly.GetName().Name, flowLibrary); + (NodeLibraryInfo, List) result = (flowLibrary.ToInfo(), + flowLibrary.MethodDetailss.Values.Select(md => md.ToInfo()).ToList()); + return result; + } + else + { + throw new Exception($"程序集[{assembly.GetName().FullName}]加载失败"); + }*/ + } - FlowLibrary flowLibrary = new FlowLibrary(dllFilePath, assembly, actionUnload); + private (NodeLibraryInfo, List) LoadAssembly(Assembly assembly,Action actionUnload) + { + if (_myFlowLibrarys.ContainsKey(assembly.GetName().Name)) + { + actionUnload.Invoke(); + throw new Exception($"程序集[{assembly.GetName().FullName}]已经加载过!"); + } + + FlowLibrary flowLibrary = new FlowLibrary(assembly, actionUnload); if (flowLibrary.LoadAssembly(assembly)) { _myFlowLibrarys.TryAdd(assembly.GetName().Name, flowLibrary); @@ -220,29 +281,6 @@ namespace Serein.NodeFlow.Tool { throw new Exception($"程序集[{assembly.GetName().FullName}]加载失败"); } - -#else - var fileName = Path.GetFileName(dllFilePath); // 获取文件名 - Assembly assembly = Assembly.LoadFrom(dllFilePath); // 加载程序集 - FlowLibrary flowLibrary = new FlowLibrary(dllFilePath, assembly, () => - { - Console.WriteLine("暂未实现卸载程序集"); - //flowAlc.Unload(); // 卸载程序集 - //flowAlc = null; - //GC.Collect(); // 强制触发GC确保卸载成功 - //GC.WaitForPendingFinalizers(); - }); - - _myFlowLibrarys.TryAdd(assembly.GetName().Name, flowLibrary); - - (NodeLibraryInfo, List) result = (flowLibrary.ToInfo(), - flowLibrary.MethodDetailss.Values.Select(md => md.ToInfo()).ToList()); - return result; - - - -#endif - } @@ -281,6 +319,7 @@ namespace Serein.NodeFlow.Tool return Default.Assemblies.FirstOrDefault(x => x.FullName == assemblyName.FullName); } + // return null; // 构建依赖项的路径 //string assemblyPath = Path.Combine(AppContext.BaseDirectory, assemblyName.Name + ".dll"); diff --git a/NodeFlow/Tool/NodeMethodDetailsHelper.cs b/NodeFlow/Tool/NodeMethodDetailsHelper.cs index 45edc57..7c77418 100644 --- a/NodeFlow/Tool/NodeMethodDetailsHelper.cs +++ b/NodeFlow/Tool/NodeMethodDetailsHelper.cs @@ -60,8 +60,7 @@ public static class NodeMethodDetailsHelper // method.GetParameters(),// 方法参数 // method.ReturnType);// 返回值 - //// 通过表达式树生成委托 - var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var methodDelegate);// 返回值 + Type? returnType; bool isTask = IsGenericTask(methodInfo.ReturnType, out var taskResult); @@ -137,7 +136,11 @@ public static class NodeMethodDetailsHelper // 如果存在可变参数,取最后一个元素的下标,否则为-1; ParamsArgIndex = hasParamsArg ? explicitDataOfParameters.Length - 1 : -1, }; - var dd = new DelegateDetails(emitMethodType, methodDelegate) ; + + //var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var methodDelegate);// 返回值 + + + var dd = new DelegateDetails(methodInfo) ; // 构造委托 methodDetails = md; delegateDetails = dd; diff --git a/Serein.BaseNode/SereinBaseNodes.cs b/Serein.BaseNode/SereinBaseNodes.cs index 9d54f6a..2e0db2a 100644 --- a/Serein.BaseNode/SereinBaseNodes.cs +++ b/Serein.BaseNode/SereinBaseNodes.cs @@ -1,6 +1,11 @@ using Serein.Library; using Serein.Library.Api; +using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; namespace Serein.BaseNode { @@ -10,7 +15,7 @@ namespace Serein.BaseNode Get, Set } - [DynamicFlow(Name ="基础节点")] + [DynamicFlow(Name ="[基础节点]")] internal class SereinBaseNodes { [NodeAction(NodeType.Action,"条件节点")] @@ -48,5 +53,39 @@ namespace Serein.BaseNode } + + [NodeAction(NodeType.Action, "KV数据收集节点")] + private Dictionary SereinKvDataCollectionNode(string argName, params object[] value) + { + var names = argName.Split(';'); + var count = Math.Min(value.Length, names.Length); + var dict = new Dictionary(); + for (int i = 0; i < count; i++) + { + dict[names[i]] = value[i]; + } + return dict; + } + [NodeAction(NodeType.Action, "List数据收集节点")] + private object[] SereinListDataCollectionNode(params object[] value) + { + return value; + } + + /* if (!DynamicObjectHelper.TryResolve(dict, className, out var result)) + { + Console.WriteLine("赋值过程中有错误,请检查属性名和类型!"); + } + else + { + DynamicObjectHelper.PrintObjectProperties(result); + } + //if (!ObjDynamicCreateHelper.TryResolve(externalData, "RootType", out var result)) + //{ + // Console.WriteLine("赋值过程中有错误,请检查属性名和类型!"); + + //} + //ObjDynamicCreateHelper.PrintObjectProperties(result!); + return result;*/ } } diff --git a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs index afdf017..167dc9e 100644 --- a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs +++ b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs @@ -122,6 +122,7 @@ namespace Serein.Library.NodeGenerator sb.AppendLine($"using System.Threading;"); sb.AppendLine($"using System.Threading.Tasks;"); sb.AppendLine($"using System.Collections.Concurrent;"); + sb.AppendLine($"using System.Collections.Generic;"); sb.AppendLine($"using Serein.Library;"); sb.AppendLine($"using Serein.Library.Api;"); sb.AppendLine($"using static Serein.Library.Utils.ChannelFlowInterrupt;"); diff --git a/SereinFlow.sln b/SereinFlow.sln index 3f5dcfd..d29f8e6 100644 --- a/SereinFlow.sln +++ b/SereinFlow.sln @@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.FlowStartTool", "Flo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Library.NodeGenerator", "Serein.Library.MyGenerator\Serein.Library.NodeGenerator.csproj", "{5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.BaseNode", "Serein.BaseNode\Serein.BaseNode.csproj", "{E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -62,6 +64,10 @@ Global {5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}.Debug|Any CPU.Build.0 = Debug|Any CPU {5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}.Release|Any CPU.ActiveCfg = Release|Any CPU {5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}.Release|Any CPU.Build.0 = Release|Any CPU + {E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index 3888d28..5e8e555 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -21,6 +21,7 @@ namespace Serein.Workbench filePath = @"F:\临时\project\linux\project.dnf"; filePath = @"F:\临时\project\linux\http\project.dnf"; filePath = @"F:\临时\project\yolo flow\project.dnf"; + filePath = @"F:\临时\project\data\project.dnf"; string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 App.FlowProjectData = JsonConvert.DeserializeObject(content); App.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;// diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index 7b0d6e4..5f1620d 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -78,17 +78,6 @@ - - - diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index 36fa9f7..c1edcbe 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -361,7 +361,7 @@ namespace Serein.Workbench /// 节点连接关系变更 /// /// - private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs) + private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs) { string fromNodeGuid = eventArgs.FromNodeGuid; string toNodeGuid = eventArgs.ToNodeGuid; @@ -395,16 +395,17 @@ namespace Serein.Workbench FlowChartCanvas, connectionType, startJunction, - endJunction, - () => EnvDecorator.RemoveConnectInvokeAsync(fromNodeGuid, toNodeGuid, connectionType) + endJunction ); + //() => EnvDecorator.RemoveConnectInvokeAsync(fromNodeGuid, toNodeGuid, connectionType) + if (toNodeControl is FlipflopNodeControl flipflopControl && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 { NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 } - connection.RefreshLine(); // 添加贝塞尔曲线显示 + //connection.RefreshLine(); // 添加贝塞尔曲线显示 Connections.Add(connection); fromNodeControl.AddCnnection(connection); toNodeControl.AddCnnection(connection); @@ -427,10 +428,9 @@ namespace Serein.Workbench foreach (var connection in removeConnections) { - connection.DeleteConnection(); Connections.Remove(connection); - fromNodeControl.RemoveCnnection(connection); - toNodeControl.RemoveCnnection(connection); + fromNodeControl.RemoveConnection(connection); // 移除连接 + toNodeControl.RemoveConnection(connection); // 移除连接 if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) { JudgmentFlipFlopNode(control); // 连接关系变更时判断 @@ -461,6 +461,10 @@ namespace Serein.Workbench _ => throw new Exception("窗体事件 FlowEnvironment_NodeConnectChangeEvemt 创建/删除节点之间的参数传递关系 JunctionControlBase 枚举值错误 。非预期的枚举值。") // 应该不会触发 }; + if(IToJunction.ArgDataJunction.Length == 0) + { + + } JunctionControlBase endJunction = IToJunction.ArgDataJunction[eventArgs.ArgIndex]; LineType lineType = LineType.Bezier; // 添加连接 @@ -470,16 +474,8 @@ namespace Serein.Workbench eventArgs.ArgIndex, eventArgs.ConnectionArgSourceType, startJunction, - endJunction, - () => EnvDecorator.RemoveConnectArgSourceAsync(fromNodeGuid, toNodeGuid, eventArgs.ArgIndex) + endJunction ); - - //if (toNodeControl is FlipflopNodeControl flipflopControl - // && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 - //{ - // NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 - //} - connection.RefreshLine(); // 添加贝塞尔曲线显示 Connections.Add(connection); fromNodeControl.AddCnnection(connection); toNodeControl.AddCnnection(connection); @@ -503,10 +499,9 @@ namespace Serein.Workbench if (connection.End is ArgJunctionControl junctionControl && junctionControl.ArgIndex == eventArgs.ArgIndex) { // 找到符合删除条件的连接线 - connection.DeleteConnection(); // 从UI层面上移除 Connections.Remove(connection); // 从本地记录中移除 - fromNodeControl.RemoveCnnection(connection); // 从节点持有的记录移除 - toNodeControl.RemoveCnnection(connection); // 从节点持有的记录移除 + fromNodeControl.RemoveConnection(connection); // 从节点持有的记录移除 + toNodeControl.RemoveConnection(connection); // 从节点持有的记录移除 } diff --git a/WorkBench/Node/View/ActionNodeControl.xaml.cs b/WorkBench/Node/View/ActionNodeControl.xaml.cs index c223617..bcd3500 100644 --- a/WorkBench/Node/View/ActionNodeControl.xaml.cs +++ b/WorkBench/Node/View/ActionNodeControl.xaml.cs @@ -37,42 +37,41 @@ namespace Serein.Workbench.Node.View /// /// 方法入参控制点(可能有,可能没) /// - private JunctionControlBase[] argDataJunction; - /// - /// 方法入参控制点(可能有,可能没) - /// - JunctionControlBase[] INodeJunction.ArgDataJunction { get { - if(argDataJunction == null) + JunctionControlBase[] INodeJunction.ArgDataJunction + { + get + { + // 获取 MethodDetailsControl 实例 + var methodDetailsControl = this.MethodDetailsControl; + var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl + if (itemsControl != null) { - // 获取 MethodDetailsControl 实例 - var methodDetailsControl = this.MethodDetailsControl; - argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; + var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; + var controls = new List(); - var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl - if (itemsControl != null) + for (int i = 0; i < itemsControl.Items.Count; i++) { - var controls = new List(); - - for (int i = 0; i < itemsControl.Items.Count; i++) + var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; + if (container != null) { - var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; - if (container != null) + var argControl = FindVisualChild(container); + if (argControl != null) { - var argControl = FindVisualChild(container); - if (argControl != null) - { - controls.Add(argControl); // 收集 ArgJunctionControl 实例 - } + controls.Add(argControl); // 收集 ArgJunctionControl 实例 } } - argDataJunction = controls.ToArray(); } + return argDataJunction = controls.ToArray(); } - return argDataJunction; - } } + else + { + return []; + } + } + } + + - - } } diff --git a/WorkBench/Node/View/DllControlControl.xaml b/WorkBench/Node/View/DllControlControl.xaml index 170f10f..e98dffb 100644 --- a/WorkBench/Node/View/DllControlControl.xaml +++ b/WorkBench/Node/View/DllControlControl.xaml @@ -4,7 +4,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Serein.Workbench.Node.View" - MaxWidth="300" > diff --git a/WorkBench/Node/View/FlipflopNodeControl.xaml.cs b/WorkBench/Node/View/FlipflopNodeControl.xaml.cs index df0a2f8..e325f13 100644 --- a/WorkBench/Node/View/FlipflopNodeControl.xaml.cs +++ b/WorkBench/Node/View/FlipflopNodeControl.xaml.cs @@ -32,10 +32,6 @@ namespace Serein.Workbench.Node.View /// JunctionControlBase INodeJunction.ReturnDataJunction => this.ResultJunctionControl; - /// - /// 方法入参控制点(可能有,可能没) - /// - private JunctionControlBase[] argDataJunction; /// /// 方法入参控制点(可能有,可能没) /// @@ -43,34 +39,34 @@ namespace Serein.Workbench.Node.View { get { - if (argDataJunction == null) + // 获取 MethodDetailsControl 实例 + var methodDetailsControl = this.MethodDetailsControl; + var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl + if (itemsControl != null) { - // 获取 MethodDetailsControl 实例 - var methodDetailsControl = this.MethodDetailsControl; - argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; + var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; + var controls = new List(); - var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl - if (itemsControl != null) + for (int i = 0; i < itemsControl.Items.Count; i++) { - var controls = new List(); - - for (int i = 0; i < itemsControl.Items.Count; i++) + var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; + if (container != null) { - var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; - if (container != null) + var argControl = FindVisualChild(container); + if (argControl != null) { - var argControl = FindVisualChild(container); - if (argControl != null) - { - controls.Add(argControl); // 收集 ArgJunctionControl 实例 - } + controls.Add(argControl); // 收集 ArgJunctionControl 实例 } } - argDataJunction = controls.ToArray(); } + return argDataJunction = controls.ToArray(); + } + else + { + return []; } - return argDataJunction; } } + } } diff --git a/Workbench/Node/Junction/JunctionControlBase.cs b/Workbench/Node/Junction/JunctionControlBase.cs index db4159a..d9eaf75 100644 --- a/Workbench/Node/Junction/JunctionControlBase.cs +++ b/Workbench/Node/Junction/JunctionControlBase.cs @@ -26,7 +26,7 @@ namespace Serein.Workbench.Node.View this.MouseDown += ParamsArg_OnMouseDown; // 增加或删除 this.MouseMove += ParamsArgControl_MouseMove; this.MouseLeave += ParamsArgControl_MouseLeave; - AddOrRemoveParamsAction = Add; + AddOrRemoveParamsTask = AddAsync; } @@ -90,11 +90,11 @@ namespace Serein.Workbench.Node.View private bool isMouseOver; // 鼠标悬停状态 - private Action AddOrRemoveParamsAction; // 增加或删除参数 + private Func AddOrRemoveParamsTask; // 增加或删除参数 - public void ParamsArg_OnMouseDown(object sender, MouseButtonEventArgs e) + public async void ParamsArg_OnMouseDown(object sender, MouseButtonEventArgs e) { - AddOrRemoveParamsAction?.Invoke(); + await AddOrRemoveParamsTask.Invoke(); } private void ParamsArgControl_MouseMove(object sender, MouseEventArgs e) @@ -111,7 +111,7 @@ namespace Serein.Workbench.Node.View // 如果焦点仍在控件上时,则改变点击事件 if (isMouseOver) { - AddOrRemoveParamsAction = Remove; + AddOrRemoveParamsTask = RemoveAsync; this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘 } @@ -125,20 +125,20 @@ namespace Serein.Workbench.Node.View private void ParamsArgControl_MouseLeave(object sender, MouseEventArgs e) { isMouseOver = false; - AddOrRemoveParamsAction = Add; // 鼠标焦点离开时恢复点击事件 + AddOrRemoveParamsTask = AddAsync; // 鼠标焦点离开时恢复点击事件 cancellationTokenSource?.Cancel(); this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘 } - private void Add() + private async Task AddAsync() { - this.MyNode.Env.ChangeParameter(MyNode.Guid, true, ArgIndex); + await this.MyNode.Env.ChangeParameter(MyNode.Guid, true, ArgIndex); } - private void Remove() + private async Task RemoveAsync() { - this.MyNode.Env.ChangeParameter(MyNode.Guid, false, ArgIndex); + await this.MyNode.Env.ChangeParameter(MyNode.Guid, false, ArgIndex); } } diff --git a/Workbench/Node/NodeControlBase.cs b/Workbench/Node/NodeControlBase.cs index c010507..04783aa 100644 --- a/Workbench/Node/NodeControlBase.cs +++ b/Workbench/Node/NodeControlBase.cs @@ -48,22 +48,23 @@ namespace Serein.Workbench.Node.View /// 删除了连接之后,还需要从节点中的记录移除 /// /// - public void RemoveCnnection(ConnectionControl connection) + public void RemoveConnection(ConnectionControl connection) { connectionControls.Remove(connection); connection.Remote(); } /// - /// 删除了连接之后,还需要从节点中的记录移除 + /// 删除所有连接 /// public void RemoveAllConection() { foreach (var connection in this.connectionControls) { - connection.Remote(); // 主动更新连线位置 + connection.Remote(); } } + /// /// 更新与该节点有关的数据 /// diff --git a/Workbench/Node/View/ConnectionControl.cs b/Workbench/Node/View/ConnectionControl.cs index 2df8986..0ca021a 100644 --- a/Workbench/Node/View/ConnectionControl.cs +++ b/Workbench/Node/View/ConnectionControl.cs @@ -104,7 +104,6 @@ namespace Serein.Workbench.Node.View public class ConnectionControl { - private readonly Action RemoteCallback; /// @@ -152,11 +151,9 @@ namespace Serein.Workbench.Node.View public ConnectionControl(Canvas Canvas, ConnectionInvokeType invokeType, JunctionControlBase Start, - JunctionControlBase End, - Action remoteCallback) + JunctionControlBase End) { this.LineType = LineType.Bezier; - this.RemoteCallback = remoteCallback; this.Canvas = Canvas; this.InvokeType = invokeType; this.Start = Start; @@ -174,13 +171,11 @@ namespace Serein.Workbench.Node.View int argIndex, ConnectionArgSourceType argSourceType, JunctionControlBase Start, - JunctionControlBase End, - Action remoteCallback) + JunctionControlBase End) { this.LineType = LineType; - this.RemoteCallback = remoteCallback; this.Canvas = Canvas; - this.ArgIndex = ArgIndex; + this.ArgIndex = argIndex; this.ArgSourceType = argSourceType; this.Start = Start; this.End = End; @@ -223,24 +218,26 @@ namespace Serein.Workbench.Node.View private void ConfigureLineContextMenu() { var contextMenu = new ContextMenu(); - contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => this.DeleteConnection())); + contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => this.Remote())); BezierLine.ContextMenu = contextMenu; } - /// - /// 删除该连线 - /// - /// - public void DeleteConnection() - { - RemoteCallback?.Invoke(); - } + /// /// 删除该连线 /// public void Remote() { Canvas.Children.Remove(BezierLine); + var env = Start.MyNode.Env; + if (Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke) + { + env.RemoveConnectInvokeAsync(Start.MyNode.Guid, End.MyNode.Guid, InvokeType); + } + else if (Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Arg) + { + env.RemoveConnectArgSourceAsync(Start.MyNode.Guid, End.MyNode.Guid, ArgIndex) ; + } } /// @@ -252,280 +249,26 @@ namespace Serein.Workbench.Node.View BezierLine.UpdatePoints(startPoint, endPoint); } + private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心 private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心 + /// + /// 刷新坐标 + /// - #region 工具方法 - private (Point startPoint,Point endPoint) RefreshPoint(Canvas canvas, FrameworkElement startElement, FrameworkElement endElement) + private (Point startPoint, Point endPoint) RefreshPoint(Canvas canvas, FrameworkElement startElement, FrameworkElement endElement) { + var startPoint = startElement.TranslatePoint(rightCenterOfStartLocation, canvas); // 获取起始节点的中心位置 var endPoint = endElement.TranslatePoint(leftCenterOfEndLocation, canvas); // 计算终点位置 return (startPoint, endPoint); } - - - - - #endregion } - /* - /// - /// 连接控件,表示控件的连接关系 - /// - public class ConnectionControl : Shape - { - private readonly Action RemoteCallback; - - /// - /// 关于调用 - /// - /// - /// - public ConnectionControl(Canvas Canvas, - ConnectionInvokeType Type, - JunctionControlBase Start, - JunctionControlBase End, - Action remoteCallback) - { - this.RemoteCallback = remoteCallback; - this.Canvas = Canvas; - this.Type = Type; - this.Start = Start; - this.End = End; - this.Start.Background = GetLineColor(Type); // 线条颜色 - this.End.Background = GetLineColor(Type); // 线条颜色 - InitElementPoint(); - } - - /// - /// 关于入参 - /// - /// - /// - public ConnectionControl(Canvas Canvas, - int argIndex, - ConnectionArgSourceType connectionArgSourceType, - JunctionControlBase Start, - JunctionControlBase End, - Action remoteCallback) - { - this.RemoteCallback = remoteCallback; - this.Canvas = Canvas; - this.ArgIndex = ArgIndex; - this.ConnectionArgSourceType = connectionArgSourceType; - this.Start = Start; - this.End = End; - this.Start.Background = GetLineColor(Type); // 线条颜色 - this.End.Background = GetLineColor(Type); // 线条颜色 - InitElementPoint(); - } - - /// - /// 所在的画布 - /// - public Canvas Canvas { get; } - - /// - /// 调用方法类型,连接类型 - /// - public ConnectionInvokeType Type { get; } - - /// - /// 获取参数类型,第几个参数 - /// - public int ArgIndex { get; set; } = -1; - - /// - /// 参数来源(决定了连接线的样式) - /// - public ConnectionArgSourceType ConnectionArgSourceType { get; set; } - - /// - /// 起始控制点 - /// - public JunctionControlBase Start { get; set; } - - /// - /// 目标控制点 - /// - public JunctionControlBase End { get; set; } - - - /// - /// 配置连接曲线的右键菜单 - /// - /// - private void ConfigureLineContextMenu(ConnectionControl connection) - { - var contextMenu = new ContextMenu(); - contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection))); - connection.ContextMenu = contextMenu; - - } - - /// - /// 删除该连线 - /// - /// - private void DeleteConnection(ConnectionControl connection) - { - var connectionToRemove = connection; - if (connectionToRemove is null) - { - return; - } - if(this.Start is JunctionControlBase startJunctionControlBase) - { - startJunctionControlBase.Background = Brushes.Transparent; - } - if (this.End is JunctionControlBase endJunctionControlBase) - { - endJunctionControlBase.Background = Brushes.Transparent; - } - this.Canvas.g - RemoteCallback?.Invoke(); - } - - /// - /// 移除 - /// - public void RemoveFromCanvas() - { - Canvas.Children.Remove(this); // 移除线 - } - - /// - /// 重新绘制 - /// - public void AddOrRefreshLine() - { - this.InvalidateVisual(); - } - - public void InitElementPoint() - { - leftCenterOfEndLocation = new Point(0, End.ActualHeight / 2); // 目标节点选择左侧边缘中心 - rightCenterOfStartLocation = new Point(Start.ActualWidth, Start.ActualHeight / 2); // 起始节点选择右侧边缘中心 - brush = GetLineColor(Type); // 线条颜色 - hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线 - hitVisiblePen.Freeze(); // Freeze以提高性能 - visualPen = new Pen(brush, 2.0); // 默认可视化Pen - visualPen.Freeze(); // Freeze以提高性能 - ConfigureLineContextMenu(this); // 设置连接右键事件 - linkSize = 4; // 整线条粗细 - Canvas.Children.Add(this); // 添加线 - Grid.SetZIndex(this, -9999999); // 置底 - } - - /// - /// 控件重绘事件 - /// - /// - protected override void OnRender(DrawingContext drawingContext) - { - RefreshPoint(Canvas, this.Start, this.End); // 刷新坐标 - DrawBezierCurve(drawingContext, startPoint, endPoint, linkSize, brush); // 刷新线条显示位置 - } - - private readonly StreamGeometry streamGeometry = new StreamGeometry(); - private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心 - private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心 - private Pen hitVisiblePen; // 初始化碰撞检测线 - private Pen visualPen; // 默认可视化Pen - private Point startPoint; // 连接线的起始节点 - private Point endPoint; // 连接线的终点 - private Brush brush; // 线条颜色 - double linkSize; // 根据缩放比例调整线条粗细 - protected override Geometry DefiningGeometry => streamGeometry; - - #region 工具方法 - - public void RefreshPoint(Canvas canvas, FrameworkElement startElement, FrameworkElement endElement) - { - endPoint = endElement.TranslatePoint(leftCenterOfEndLocation, canvas); // 计算终点位置 - startPoint = startElement.TranslatePoint(rightCenterOfStartLocation, canvas); // 获取起始节点的中心位置 - } - - /// - /// 根据连接类型指定颜色 - /// - /// - /// - /// - public static SolidColorBrush GetLineColor(ConnectionInvokeType currentConnectionType) - { - return currentConnectionType switch - { - ConnectionInvokeType.IsSucceed => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")), - ConnectionInvokeType.IsFail => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")), - ConnectionInvokeType.IsError => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FE1343")), - ConnectionInvokeType.Upstream => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")), - _ => throw new Exception(), - }; - } - private Point c0, c1; // 用于计算贝塞尔曲线控制点逻辑 - private Vector axis = new Vector(1, 0); - private Vector startToEnd; - private void DrawBezierCurve(DrawingContext drawingContext, - Point start, - Point end, - double linkSize, - Brush brush, - bool isHitTestVisible = false, - double strokeThickness = 1.0, - bool isMouseOver = false, - double dashOffset = 0.0) - { - // 控制点的计算逻辑 - double power = 8 * 8; // 控制贝塞尔曲线的“拉伸”强度 - - // 计算轴向向量与起点到终点的向量 - //var axis = new Vector(1, 0); - startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo(); - - // 计算拉伸程度k,拉伸与水平夹角正相关 - var k = 1 - Math.Pow(Math.Max(0, axis.DotProduct(startToEnd)), 10.0); - - // 如果起点x大于终点x,增加额外的偏移量,避免重叠 - var bias = start.X > end.X ? Math.Abs(start.X - end.X) * 0.25 : 0; - - // 控制点的实际计算 - c0 = new Point(+(power + bias) * k + start.X, start.Y); - c1 = new Point(-(power + bias) * k + end.X, end.Y); - - // 准备StreamGeometry以用于绘制曲线 - streamGeometry.Clear(); - using (var context = streamGeometry.Open()) - { - context.BeginFigure(start, true, false); // 曲线起点 - context.BezierTo(c0, c1, end, true, false); // 画贝塞尔曲线 - } - - drawingContext.DrawGeometry(null, visualPen, streamGeometry); - - // 绘制碰撞检测线 - //if (true) - //{ - // //hitVisiblePen = new Pen(Brushes.Transparent, linkSize + strokeThickness); - // //hitVisiblePen.Freeze(); - // drawingContext.DrawGeometry(null, hitVisiblePen, streamGeometry); - //} - //else - //{ - - - //} - - } - #endregion - } - */ -