在Serein.Library添加了基础功能模块,暂时实现了键值对/数组数据的创建(可配合JSON库进行序列化)

This commit is contained in:
fengjiayi
2024-11-04 23:30:52 +08:00
parent b7be0f2c6e
commit dff9a00fb6
33 changed files with 1046 additions and 609 deletions

View File

@@ -40,6 +40,11 @@ namespace Serein.Library.Core
/// </summary> /// </summary>
public ConnectionInvokeType NextOrientation { get; set; } public ConnectionInvokeType NextOrientation { get; set; }
/// <summary>
/// 运行时异常信息
/// </summary>
public Exception ExceptionOfRuning { get; set; }
/// <summary> /// <summary>
/// 每个流程上下文分别存放节点的当前数据 /// 每个流程上下文分别存放节点的当前数据
/// </summary> /// </summary>
@@ -138,6 +143,10 @@ namespace Serein.Library.Core
disposable?.Dispose(); disposable?.Dispose();
} }
} }
else
{
}
} }
foreach (var nodeObj in ContextShareData.Values) foreach (var nodeObj in ContextShareData.Values)
{ {
@@ -158,5 +167,92 @@ namespace Serein.Library.Core
RunState = RunState.Completion; RunState = RunState.Completion;
} }
private void Dispose(ref IDictionary<string, object> 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<string, object> tmpDict)
{
Dispose(ref tmpDict);
}
else if (nodeObj is ICollection<object> tmpList)
{
Dispose(ref tmpList);
}
else if (nodeObj is IList<object> tmpList2)
{
Dispose(ref tmpList2);
}
}
keyValuePairs.Clear();
}
private void Dispose(ref ICollection<object> 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<string, object> tmpDict)
{
Dispose(ref tmpDict);
}
else if (nodeObj is ICollection<object> tmpList)
{
Dispose(ref tmpList);
}
else if (nodeObj is IList<object> tmpList2)
{
Dispose(ref tmpList2);
}
}
list.Clear();
}
private void Dispose(ref IList<object> 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<string, object> tmpDict)
{
Dispose(ref tmpDict);
}
else if (nodeObj is ICollection<object> tmpList)
{
Dispose(ref tmpList);
}
else if (nodeObj is IList<object> tmpList2)
{
Dispose(ref tmpList2);
}
}
list.Clear();
}
} }
} }

View File

@@ -40,6 +40,11 @@ namespace Serein.Library.Framework.NodeFlow
/// </summary> /// </summary>
public ConnectionInvokeType NextOrientation { get; set; } public ConnectionInvokeType NextOrientation { get; set; }
/// <summary>
/// 运行时异常信息
/// </summary>
public Exception ExceptionOfRuning { get; set; }
/// <summary> /// <summary>
/// 每个上下文分别存放节点的当前数据 /// 每个上下文分别存放节点的当前数据
/// </summary> /// </summary>

View File

@@ -31,6 +31,11 @@ namespace Serein.Library.Api
/// </summary> /// </summary>
ConnectionInvokeType NextOrientation { get; set; } ConnectionInvokeType NextOrientation { get; set; }
/// <summary>
/// 运行时异常信息
/// </summary>
Exception ExceptionOfRuning { get; set; }
/// <summary> /// <summary>
/// 设置节点的运行时上一节点,用以多线程中隔开不同流程的数据 /// 设置节点的运行时上一节点,用以多线程中隔开不同流程的数据
/// </summary> /// </summary>

View File

@@ -857,18 +857,20 @@ namespace Serein.Library.Api
/// <summary> /// <summary>
/// 获取方法描述信息 /// 获取方法描述信息
/// </summary> /// </summary>
/// <param name="assemblyName">程序集名称</param>
/// <param name="methodName">方法描述</param> /// <param name="methodName">方法描述</param>
/// <param name="mdInfo">方法信息</param> /// <param name="mdInfo">方法信息</param>
/// <returns></returns> /// <returns></returns>
bool TryGetMethodDetailsInfo(string libraryName, string methodName, out MethodDetailsInfo mdInfo); bool TryGetMethodDetailsInfo(string assemblyName, string methodName, out MethodDetailsInfo mdInfo);
/// <summary> /// <summary>
/// 获取指定方法的Emit委托 /// 获取指定方法的Emit委托
/// </summary> /// </summary>
/// <param name="assemblyName">程序集名称</param>
/// <param name="methodName"></param> /// <param name="methodName"></param>
/// <param name="del"></param> /// <param name="del"></param>
/// <returns></returns> /// <returns></returns>
bool TryGetDelegateDetails(string libraryName, string methodName, out DelegateDetails del); bool TryGetDelegateDetails(string assemblyName, string methodName, out DelegateDetails del);
#region #region

View File

@@ -26,6 +26,7 @@ namespace Serein.Library
_emitDelegate = emitDelegate; _emitDelegate = emitDelegate;
} }
/// <summary> /// <summary>
/// 记录Emit委托 /// 记录Emit委托
/// </summary> /// </summary>
@@ -36,7 +37,9 @@ namespace Serein.Library
_emitMethodType = EmitMethodType; _emitMethodType = EmitMethodType;
_emitDelegate = EmitDelegate; _emitDelegate = EmitDelegate;
} }
/// <summary>
/*/// <summary>
/// 更新委托方法 /// 更新委托方法
/// </summary> /// </summary>
/// <param name="EmitMethodType"></param> /// <param name="EmitMethodType"></param>
@@ -45,7 +48,9 @@ namespace Serein.Library
{ {
_emitMethodType = EmitMethodType; _emitMethodType = EmitMethodType;
_emitDelegate = EmitDelegate; _emitDelegate = EmitDelegate;
} }*/
private Delegate _emitDelegate; private Delegate _emitDelegate;
private EmitMethodType _emitMethodType; private EmitMethodType _emitMethodType;

View File

@@ -1,6 +1,7 @@
using Serein.Library.Api; using Serein.Library.Api;
using Serein.Library.Utils; using Serein.Library.Utils;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
@@ -36,7 +37,7 @@ namespace Serein.Library
private bool _isProtectionParameter; private bool _isProtectionParameter;
/// <summary> /// <summary>
/// 作用实例的类型(多个相同的节点将拥有相同的类型) /// 调用节点方法时需要的实例(多个相同的节点将拥有相同的类型)
/// </summary> /// </summary>
[PropertyInfo] [PropertyInfo]
private Type _actingInstanceType; private Type _actingInstanceType;
@@ -77,6 +78,7 @@ namespace Serein.Library
/// </summary> /// </summary>
[PropertyInfo] [PropertyInfo]
private ParameterDetails[] _parameterDetailss; private ParameterDetails[] _parameterDetailss;
//private List<ParameterDetails> _parameterDetailss;
/// <summary> /// <summary>
/// <para>描述该方法是否存在可选参数</para> /// <para>描述该方法是否存在可选参数</para>
@@ -137,6 +139,9 @@ namespace Serein.Library
&& index < ParameterDetailss.Length) // 防止下标越界 && index < ParameterDetailss.Length) // 防止下标越界
{ {
ParameterDetailss[index] = null; // 释放对象引用 ParameterDetailss[index] = null; // 释放对象引用
var tmp = ArrayHelper.RemoteToArray<ParameterDetails>(ParameterDetailss, index); // 新增; var tmp = ArrayHelper.RemoteToArray<ParameterDetails>(ParameterDetailss, index); // 新增;
UpdateParamIndex(ref tmp); UpdateParamIndex(ref tmp);
ParameterDetailss = tmp; // 新增 ParameterDetailss = tmp; // 新增

View File

@@ -62,25 +62,6 @@ namespace Serein.Library
/// </summary> /// </summary>
[PropertyInfo(IsProtection = true)] [PropertyInfo(IsProtection = true)]
private MethodDetails _methodDetails ; private MethodDetails _methodDetails ;
/// <summary>
/// 运行时的上一节点
/// </summary>
//[PropertyInfo]
//private NodeModelBase _previousNode ;
/// <summary>
/// 当前节点执行完毕后需要执行的下一个分支的类别
/// </summary>
//[PropertyInfo]
//private ConnectionInvokeType _nextOrientation = ConnectionInvokeType.None;
/// <summary>
/// 运行时的异常信息(仅在 FlowState 为 Error 时存在对应值)
/// </summary>
[PropertyInfo]
private Exception _runingException ;
} }

View File

@@ -11,6 +11,7 @@ using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -109,7 +110,7 @@ namespace Serein.Library
{ {
// 保存的参数信息项数量大于方法本身的方法入参数量(可能存在可变入参) // 保存的参数信息项数量大于方法本身的方法入参数量(可能存在可变入参)
var length = nodeInfo.ParameterData.Length - pds.Length; // 需要扩容的长度 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; pds = this.MethodDetails.ParameterDetailss;
var startParmsPd = pds[md.ParamsArgIndex]; // 获取可变入参参数描述 var startParmsPd = pds[md.ParamsArgIndex]; // 获取可变入参参数描述
for(int i = md.ParamsArgIndex + 1; i <= md.ParamsArgIndex + length; i++) for(int i = md.ParamsArgIndex + 1; i <= md.ParamsArgIndex + length; i++)
@@ -144,6 +145,10 @@ namespace Serein.Library
} }
#endregion #endregion
#region
#endregion
#region #region
/// <summary> /// <summary>
@@ -256,7 +261,6 @@ namespace Serein.Library
newFlowData = null; newFlowData = null;
await Console.Out.WriteLineAsync($"节点[{this.MethodDetails?.MethodName}]异常:" + ex); await Console.Out.WriteLineAsync($"节点[{this.MethodDetails?.MethodName}]异常:" + ex);
context.NextOrientation = ConnectionInvokeType.IsError; context.NextOrientation = ConnectionInvokeType.IsError;
currentNode.RuningException = ex;
} }
@@ -317,7 +321,7 @@ namespace Serein.Library
{ {
throw new Exception($"节点{this.Guid}不存在方法信息请检查是否需要重写节点的ExecutingAsync"); 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}不存在对应委托"); throw new Exception($"节点{this.Guid}不存在对应委托");
} }
@@ -700,4 +704,68 @@ namespace Serein.Library
#endregion #endregion
} }
#if false
public static class NodeModelExtension
{
/// <summary>
/// 程序集更新,更新节点方法描述、以及所有入参描述的类型
/// </summary>
/// <param name="nodeModel">节点Model</param>
/// <param name="newMd">新的方法描述</param>
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
} }

View File

@@ -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
{
/// <summary>
/// 基础功能
/// </summary>
[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<string, object> 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<string, object>();
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;*/
}
}

View File

@@ -19,7 +19,7 @@ namespace Serein.Library.Utils
/// <param name="length">扩容长度</param> /// <param name="length">扩容长度</param>
/// <returns>新的数组</returns> /// <returns>新的数组</returns>
/// <exception cref="Exception"> length 传入负值</exception> /// <exception cref="Exception"> length 传入负值</exception>
public static T[] ArrayExpansion<T>(T[] original, int length) public static T[] Expansion<T>(T[] original, int length)
{ {
if(length == 0) if(length == 0)
{ {
@@ -58,7 +58,10 @@ namespace Serein.Library.Utils
public static T[] AddToArray<T>(T[] original, T newObject) public static T[] AddToArray<T>(T[] original, T newObject)
{ {
// 创建一个新数组比原数组大1 // 创建一个新数组比原数组大1
T[] newArray = ArrayHelper.ArrayExpansion(original, 1); T[] newArray = ArrayHelper.Expansion(original, 1);
original.CopyTo(newArray, 0);
// 将新对象放在最后一位 // 将新对象放在最后一位
newArray[newArray.Length - 1] = newObject; newArray[newArray.Length - 1] = newObject;
return newArray; return newArray;

View File

@@ -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<string, Type> typeCache = new Dictionary<string, Type>();
public static object Resolve(IDictionary<string, object> properties, string typeName)
{
var obj = CreateObjectWithProperties(properties, typeName);
//SetPropertyValues(obj, properties);
return obj;
}
public static bool TryResolve(IDictionary<string, object> 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<string, object> 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<Dictionary<string, object>>) // 处理数组类型
{
var nestedPropValue = (propValue as IList<Dictionary<string, object>>)[0];
var nestedType = CreateObjectWithProperties(nestedPropValue, $"{propName}Element");
propType = nestedType.GetType().MakeArrayType(); // 创建数组类型
}
else if (propValue is Dictionary<string, object> 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<string, object> properties)
{
var objType = obj.GetType();
foreach (var kvp in properties)
{
var propInfo = objType.GetProperty(kvp.Key);
object value = kvp.Value;
// 如果值是嵌套的字典类型,递归处理嵌套对象
if (value is Dictionary<string, object> 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<string, object> 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<string, object> 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<Dictionary<string, object>> 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<Dictionary<string, object>>; // 假设值是一个列表,具体处理逻辑在赋值时
}
// 处理嵌套类型的情况
if (value is Dictionary<string, object> && targetType.IsClass && !targetType.IsPrimitive)
{
// 如果目标类型是一个复杂对象,并且值是一个字典,可能是嵌套对象
return true; // 假设可以递归处理嵌套对象
}
return false;
}
}
}

View File

@@ -22,7 +22,7 @@ namespace Serein.Library.Utils.SereinExpression
public static T Evaluate(string expression, T inputValue) public static T Evaluate(string expression, T inputValue)
{ {
// 替换占位符@为输入值 // 替换占位符@为输入值
expression = expression.Replace("@", inputValue.ToString()); expression = expression.Replace("@", inputValue.ToString());
try try
@@ -51,7 +51,7 @@ namespace Serein.Library.Utils.SereinExpression
/// <exception cref="NotSupportedException"></exception> /// <exception cref="NotSupportedException"></exception>
public static object Evaluate(string expression, object targetObJ, out bool isChange) 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"); 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."); throw new NotSupportedException($"Operation {operation} is not supported.");
} }
return result; return result;
} }
private static readonly char[] separator = new char[] { '(', ')' }; private static readonly char[] separator = new char[] { '(', ')' };
private static readonly char[] separatorArray = new char[] { ',' }; private static readonly char[] separatorArray = new char[] { ',' };
/// <summary> /// <summary>
@@ -128,6 +128,7 @@ namespace Serein.Library.Utils.SereinExpression
return method.Invoke(target, parameterValues); return method.Invoke(target, parameterValues);
} }
/// <summary> /// <summary>
/// 获取值 /// 获取值
/// </summary> /// </summary>
@@ -156,56 +157,53 @@ namespace Serein.Library.Utils.SereinExpression
} }
var targetType = target?.GetType(); // 目标对象的类型 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 typetmp = target.GetType().FullName;
// 目标是键值对 // 目标是键值对
var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1); var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1);
var method = targetType.GetMethod("get_Item", BindingFlags.Public | BindingFlags.Instance); var method = targetType.GetMethod("get_Item", BindingFlags.Public | BindingFlags.Instance);
if(method != null) if (method != null)
{ {
var result = method.Invoke(target, new object[] { indexStr }); var result = method.Invoke(target, new object[] { indexStr });
if(result != null) if (result != null)
{ {
return result; target = result;
} }
} }
//var dict = target as Dictionary<string, string>; }
////var dict = (Dictionary<dynamic, dynamic>)target; #endregion
//var temp = dict[indexStr];
////if (target is Dictionary<object, object> dict)
////{
//// var temp = dict[indexStr];
////}
//var TMP2= target.GetType().GetEnumValues();
}
else else
{ {
#region #region
// 获取数组或集合对象 // 获取数组或集合对象
var arrayProperty = target?.GetType().GetProperty(arrayName);
if (arrayProperty is null) // 如果arrayName为空说明target可能是数组而不需要再获取属性了
if (!string.IsNullOrEmpty(arrayName))
{ {
var arrayField = target?.GetType().GetField(arrayName); var arrayProperty = target?.GetType().GetProperty(arrayName);
if (arrayField is null) 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 else
{ {
target = arrayField.GetValue(target); target = arrayProperty.GetValue(target);
} }
} }
else
{
target = arrayProperty.GetValue(target);
}
// 提取数组索引 // 提取数组索引
var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1); var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1);
if (!int.TryParse(indexStr, out int index)) if (!int.TryParse(indexStr, out int index))
@@ -236,7 +234,6 @@ namespace Serein.Library.Utils.SereinExpression
#endregion #endregion
} }
} }
else else
{ {
@@ -288,7 +285,7 @@ namespace Serein.Library.Utils.SereinExpression
// 检查是否包含数组索引 // 检查是否包含数组索引
var arrayIndexStart = member.IndexOf('['); var arrayIndexStart = member.IndexOf('[');
if (arrayIndexStart != -1) if (arrayIndexStart != -1)
{ {
// 解析数组名和索引 // 解析数组名和索引
var arrayName = member.Substring(0, arrayIndexStart); var arrayName = member.Substring(0, arrayIndexStart);
@@ -394,6 +391,7 @@ namespace Serein.Library.Utils.SereinExpression
return target; return target;
} }
/// <summary> /// <summary>
/// 计算数学简单表达式 /// 计算数学简单表达式
/// </summary> /// </summary>
@@ -408,7 +406,7 @@ namespace Serein.Library.Utils.SereinExpression
private static T ComputedNumber<T>(object value, string expression) where T : struct, IComparable<T> private static T ComputedNumber<T>(object value, string expression) where T : struct, IComparable<T>
{ {
T result = value.ToConvert<T>(); T result = value.ToConvert<T>();
return SerinArithmeticExpressionEvaluator<T>.Evaluate(expression, result); return SerinArithmeticExpressionEvaluator<T>.Evaluate(expression, result);
} }
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -184,7 +184,7 @@ namespace Serein.NodeFlow
//object?[]? args = [Context]; //object?[]? args = [Context];
foreach (var md in initMethods) // 初始化 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("不存在对应委托"); throw new Exception("不存在对应委托");
} }
@@ -205,7 +205,7 @@ namespace Serein.NodeFlow
{ {
//object?[]? data = [md.ActingInstance, args]; //object?[]? data = [md.ActingInstance, args];
//md.MethodDelegate.DynamicInvoke(data); //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("不存在对应委托"); throw new Exception("不存在对应委托");
} }
@@ -228,7 +228,7 @@ namespace Serein.NodeFlow
foreach (MethodDetails? md in exitMethods) 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("不存在对应委托"); throw new Exception("不存在对应委托");
} }

View File

@@ -53,40 +53,36 @@ namespace Serein.NodeFlow.Model
/// <param name="context"></param> /// <param name="context"></param>
/// <returns></returns> /// <returns></returns>
public override async Task<object?> ExecutingAsync(IDynamicContext context) public override async Task<object?> 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<ConnectionInvokeType> JudgeAsync(IDynamicContext context, SingleConditionNode node)
{ {
try 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) catch (Exception ex)
{ {
Console.WriteLine(ex.Message); Console.WriteLine(ex.Message);
context.NextOrientation = ConnectionInvokeType.IsError; context.NextOrientation = ConnectionInvokeType.IsError;
RuningException = ex; context.ExceptionOfRuning = ex;
return ConnectionInvokeType.IsError; return context.TransmissionData(this); // 条件区域透传上一节点的数据
} }
} }
public override ParameterData[] GetParameterdatas() public override ParameterData[] GetParameterdatas()
{ {
return []; return [];

View File

@@ -108,7 +108,7 @@ namespace Serein.NodeFlow.Model
catch (Exception ex) catch (Exception ex)
{ {
context.NextOrientation = ConnectionInvokeType.IsError; context.NextOrientation = ConnectionInvokeType.IsError;
RuningException = ex; context.ExceptionOfRuning = ex;
} }
Console.WriteLine($"{result} {Expression} -> " + context.NextOrientation); Console.WriteLine($"{result} {Expression} -> " + context.NextOrientation);

View File

@@ -98,7 +98,7 @@ namespace Serein.NodeFlow.Model
catch (Exception ex) catch (Exception ex)
{ {
context.NextOrientation = ConnectionInvokeType.IsError; context.NextOrientation = ConnectionInvokeType.IsError;
RuningException = ex; context.ExceptionOfRuning = ex;
return parameter; return parameter;
} }

View File

@@ -43,7 +43,7 @@ namespace Serein.NodeFlow.Model
#endregion #endregion
MethodDetails md = MethodDetails; 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("不存在对应委托"); throw new Exception("不存在对应委托");
} }
@@ -72,14 +72,14 @@ namespace Serein.NodeFlow.Model
} }
await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex); await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex);
context.NextOrientation = ConnectionInvokeType.None; context.NextOrientation = ConnectionInvokeType.None;
RuningException = ex; context.ExceptionOfRuning = ex;
return null; return null;
} }
catch (Exception ex) catch (Exception ex)
{ {
await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex); await Console.Out.WriteLineAsync($"触发器[{this.MethodDetails.MethodName}]异常:" + ex);
context.NextOrientation = ConnectionInvokeType.IsError; context.NextOrientation = ConnectionInvokeType.IsError;
RuningException = ex; context.ExceptionOfRuning = ex;
return null; return null;
} }
finally finally

View File

@@ -13,36 +13,35 @@ namespace Serein.NodeFlow
{ {
/// <summary> /// <summary>
/// /// 加载在流程中的程序集依赖
/// </summary> /// </summary>
public class FlowLibrary public class FlowLibrary
{ {
private readonly Assembly assembly; private readonly Assembly assembly;
private readonly Action actionOfUnloadAssmbly; private readonly Action actionOfUnloadAssmbly;
private string _assemblyFilePath;
public FlowLibrary(string assemblyFilePath, public FlowLibrary(Assembly assembly,
Assembly assembly,
Action actionOfUnloadAssmbly) Action actionOfUnloadAssmbly)
{ {
this._assemblyFilePath = assemblyFilePath;
this.assembly = assembly; this.assembly = assembly;
this.actionOfUnloadAssmbly = actionOfUnloadAssmbly; this.actionOfUnloadAssmbly = actionOfUnloadAssmbly;
} }
public string FullName => assembly.GetName().FullName; public string FullName => assembly.GetName().FullName;
public string Version => assembly.GetName().Version.ToString(); public string Version => assembly.GetName().Version.ToString();
/// <summary> /// <summary>
/// 加载程序集时创建的方法描述 /// 加载程序集时创建的方法描述
/// Key 方法名称
/// Value :方法详情
/// </summary> /// </summary>
public ConcurrentDictionary<string, MethodDetails> MethodDetailss { get; } = new ConcurrentDictionary<string, MethodDetails>(); public ConcurrentDictionary<string, MethodDetails> MethodDetailss { get; } = new ConcurrentDictionary<string, MethodDetails>();
/// <summary> /// <summary>
/// 管理通过Emit动态构建的委托 /// 管理通过Emit动态构建的委托
/// Key :方法名称
/// Value :方法详情
/// </summary> /// </summary>
public ConcurrentDictionary<string, DelegateDetails> DelegateDetailss { get; } = new ConcurrentDictionary<string, DelegateDetails>(); public ConcurrentDictionary<string, DelegateDetails> DelegateDetailss { get; } = new ConcurrentDictionary<string, DelegateDetails>();
@@ -57,56 +56,24 @@ namespace Serein.NodeFlow
/// </summary> /// </summary>
public void Upload() public void Upload()
{ {
DelegateDetailss.Clear();
RegisterTypes.Clear();
MethodDetailss.Clear();
actionOfUnloadAssmbly?.Invoke(); actionOfUnloadAssmbly?.Invoke();
} }
public NodeLibraryInfo ToInfo() public NodeLibraryInfo ToInfo()
{ {
return new NodeLibraryInfo return new NodeLibraryInfo
{ {
AssemblyName = FullName, AssemblyName = assembly.GetName().Name,
FileName = Path.GetFileName(_assemblyFilePath), FileName = Path.GetFileName(assembly.Location),
FilePath = _assemblyFilePath, 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<Type>();
// RegisterTypes.TryAdd(kv.Key, types);
// }
// types.AddRange(kv.Value);
// }
// var mdInfos = mdlist.Select(md => md.ToInfo()).ToList(); // 转换成方法信息
// }
//}
/// <summary> /// <summary>
/// 动态加载程序集 /// 动态加载程序集
/// </summary> /// </summary>
@@ -158,18 +125,13 @@ namespace Serein.NodeFlow
// (Type, string) // (Type, string)
// Type 具有 DynamicFlowAttribute 标记的类型 // Type 具有 DynamicFlowAttribute 标记的类型
// string 类型元数据 DynamicFlowAttribute 特性中的 Name 属性 // string 类型元数据 DynamicFlowAttribute 特性中的 Name 属性
types = types.Where(type => type.GetCustomAttribute<DynamicFlowAttribute>() is DynamicFlowAttribute dynamicFlowAttribute
&& dynamicFlowAttribute.Scan).ToList();
foreach (var type in types) foreach (var type in types)
{ {
if (type.Name.Equals("YoloFlowControl")) if (type.GetCustomAttribute<DynamicFlowAttribute>() is DynamicFlowAttribute dynamicFlowAttribute)
{
//var ab = type.GetCustomAttribute<DynamicFlowAttribute>();
//var attributes = assembly.GetCustomAttributes();
//foreach (var attribute in attributes)
//{
// Console.WriteLine(attribute.GetType().Name);
//}
}
if (type.GetCustomAttribute<DynamicFlowAttribute>() is DynamicFlowAttribute dynamicFlowAttribute && dynamicFlowAttribute.Scan == true)
{ {
scanTypes.Add((type, dynamicFlowAttribute.Name)); scanTypes.Add((type, dynamicFlowAttribute.Name));
} }

View File

@@ -31,25 +31,34 @@ namespace Serein.NodeFlow.Tool
/// </summary> /// </summary>
private readonly ConcurrentDictionary<string, FlowLibrary> _myFlowLibrarys = new ConcurrentDictionary<string, FlowLibrary>(); private readonly ConcurrentDictionary<string, FlowLibrary> _myFlowLibrarys = new ConcurrentDictionary<string, FlowLibrary>();
public (NodeLibraryInfo, List<MethodDetailsInfo>) LoadLibrary(string libraryfilePath) /// <summary>
/// 加载类库
/// </summary>
/// <param name="libraryfilePath"></param>
/// <returns></returns>
public (NodeLibraryInfo, List<MethodDetailsInfo>) LoadLibraryOfPath(string libraryfilePath)
{ {
return LoadDllNodeInfo(libraryfilePath); return LoadDllNodeInfo(libraryfilePath);
} }
public bool UnloadLibrary(string libraryName) /// <summary>
/// 卸载类库
/// </summary>
/// <param name="assemblyName"></param>
/// <returns></returns>
public bool UnloadLibrary(string assemblyName)
{ {
libraryName = libraryName.Split(',')[0]; if (_myFlowLibrarys.Remove(assemblyName, out var flowLibrary))
if (_myFlowLibrarys.Remove(libraryName, out var flowLibrary))
{ {
try try
{ {
flowLibrary.Upload(); flowLibrary.Upload(); // 尝试卸载
flowLibrary = null;
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"尝试卸载程序集[{libraryName}]发生错误:{ex}"); Console.WriteLine($"尝试卸载程序集[{assemblyName}]发生错误:{ex}");
return false; return false;
} }
@@ -144,6 +153,20 @@ namespace Serein.NodeFlow.Tool
return rsTypes; return rsTypes;
} }
/// <summary>
/// 获取某个程序集下的所有方法信息,用于保存项目时调用
/// </summary>
/// <returns></returns>
public List<MethodDetails> GetLibraryMdsOfAssmbly(string assemblyName)
{
if (_myFlowLibrarys.TryGetValue(assemblyName, out var flowLibrary))
{
return flowLibrary.MethodDetailss.Values.ToList();
}
return [];
}
/// <summary> /// <summary>
/// 获取所有方法信息,用于保存项目时调用 /// 获取所有方法信息,用于保存项目时调用
@@ -165,9 +188,6 @@ namespace Serein.NodeFlow.Tool
} }
/// <summary> /// <summary>
/// 序列化当前项目的依赖信息、节点信息,用于远程登录的场景,需要将依赖信息从本地(受控端)发送到远程(主控端) /// 序列化当前项目的依赖信息、节点信息,用于远程登录的场景,需要将依赖信息从本地(受控端)发送到远程(主控端)
/// </summary> /// </summary>
@@ -180,18 +200,38 @@ namespace Serein.NodeFlow.Tool
#region #region
/// <summary> private readonly string SereinLibraryDll = $"{nameof(Serein)}.{nameof(Serein.Library)}.dll";
/// 从文件路径中加载程序集,返回相应的信息
/// </summary>
/// <param name="dllFilePath"></param>
/// <returns></returns>
private (NodeLibraryInfo, List<MethodDetailsInfo>) LoadDllNodeInfo(string dllFilePath) private (NodeLibraryInfo, List<MethodDetailsInfo>) LoadDllNodeInfo(string dllFilePath)
{ {
#if true
var fileName = Path.GetFileName(dllFilePath); // 获取文件名 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”类库依赖 // 每个类库下面至少需要有“Serein.Library.dll”类库依赖
var flowAlc = new FlowLibraryAssemblyContext(sereinFlowLibraryPath, fileName); var flowAlc = new FlowLibraryAssemblyContext(sereinFlowLibraryPath, fileName);
Action actionUnload = () => Action actionUnload = () =>
@@ -207,8 +247,29 @@ namespace Serein.NodeFlow.Tool
actionUnload.Invoke(); actionUnload.Invoke();
throw new Exception($"程序集[{assembly.GetName().FullName}]已经加载过!"); throw new Exception($"程序集[{assembly.GetName().FullName}]已经加载过!");
} }
FlowLibrary flowLibrary = new FlowLibrary(assembly, actionUnload);
if (flowLibrary.LoadAssembly(assembly))
{
_myFlowLibrarys.TryAdd(assembly.GetName().Name, flowLibrary);
(NodeLibraryInfo, List<MethodDetailsInfo>) 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<MethodDetailsInfo>) 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)) if (flowLibrary.LoadAssembly(assembly))
{ {
_myFlowLibrarys.TryAdd(assembly.GetName().Name, flowLibrary); _myFlowLibrarys.TryAdd(assembly.GetName().Name, flowLibrary);
@@ -220,29 +281,6 @@ namespace Serein.NodeFlow.Tool
{ {
throw new Exception($"程序集[{assembly.GetName().FullName}]加载失败"); 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<MethodDetailsInfo>) 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 Default.Assemblies.FirstOrDefault(x => x.FullName == assemblyName.FullName);
} }
// return null;
// 构建依赖项的路径 // 构建依赖项的路径
//string assemblyPath = Path.Combine(AppContext.BaseDirectory, assemblyName.Name + ".dll"); //string assemblyPath = Path.Combine(AppContext.BaseDirectory, assemblyName.Name + ".dll");

View File

@@ -60,8 +60,7 @@ public static class NodeMethodDetailsHelper
// method.GetParameters(),// 方法参数 // method.GetParameters(),// 方法参数
// method.ReturnType);// 返回值 // method.ReturnType);// 返回值
//// 通过表达式树生成委托
var emitMethodType = EmitHelper.CreateDynamicMethod(methodInfo, out var methodDelegate);// 返回值
Type? returnType; Type? returnType;
bool isTask = IsGenericTask(methodInfo.ReturnType, out var taskResult); bool isTask = IsGenericTask(methodInfo.ReturnType, out var taskResult);
@@ -137,7 +136,11 @@ public static class NodeMethodDetailsHelper
// 如果存在可变参数,取最后一个元素的下标,否则为-1 // 如果存在可变参数,取最后一个元素的下标,否则为-1
ParamsArgIndex = hasParamsArg ? explicitDataOfParameters.Length - 1 : -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; methodDetails = md;
delegateDetails = dd; delegateDetails = dd;

View File

@@ -1,6 +1,11 @@
using Serein.Library; using Serein.Library;
using Serein.Library.Api; using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.Library.Utils.SereinExpression; using Serein.Library.Utils.SereinExpression;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
namespace Serein.BaseNode namespace Serein.BaseNode
{ {
@@ -10,7 +15,7 @@ namespace Serein.BaseNode
Get, Get,
Set Set
} }
[DynamicFlow(Name ="基础节点")] [DynamicFlow(Name ="[基础节点]")]
internal class SereinBaseNodes internal class SereinBaseNodes
{ {
[NodeAction(NodeType.Action,"条件节点")] [NodeAction(NodeType.Action,"条件节点")]
@@ -48,5 +53,39 @@ namespace Serein.BaseNode
} }
[NodeAction(NodeType.Action, "KV数据收集节点")]
private Dictionary<string, object> SereinKvDataCollectionNode(string argName, params object[] value)
{
var names = argName.Split(';');
var count = Math.Min(value.Length, names.Length);
var dict = new Dictionary<string, object>();
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;*/
} }
} }

View File

@@ -122,6 +122,7 @@ namespace Serein.Library.NodeGenerator
sb.AppendLine($"using System.Threading;"); sb.AppendLine($"using System.Threading;");
sb.AppendLine($"using System.Threading.Tasks;"); sb.AppendLine($"using System.Threading.Tasks;");
sb.AppendLine($"using System.Collections.Concurrent;"); sb.AppendLine($"using System.Collections.Concurrent;");
sb.AppendLine($"using System.Collections.Generic;");
sb.AppendLine($"using Serein.Library;"); sb.AppendLine($"using Serein.Library;");
sb.AppendLine($"using Serein.Library.Api;"); sb.AppendLine($"using Serein.Library.Api;");
sb.AppendLine($"using static Serein.Library.Utils.ChannelFlowInterrupt;"); sb.AppendLine($"using static Serein.Library.Utils.ChannelFlowInterrupt;");

View File

@@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.FlowStartTool", "Flo
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Library.NodeGenerator", "Serein.Library.MyGenerator\Serein.Library.NodeGenerator.csproj", "{5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.Library.NodeGenerator", "Serein.Library.MyGenerator\Serein.Library.NodeGenerator.csproj", "{5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serein.BaseNode", "Serein.BaseNode\Serein.BaseNode.csproj", "{E6C9C6F1-1BA5-4220-A7A4-ED905BB8B54F}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{5F7DE0B2-A5D3-492D-AC6C-F0C39EBEF365}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -21,6 +21,7 @@ namespace Serein.Workbench
filePath = @"F:\临时\project\linux\project.dnf"; filePath = @"F:\临时\project\linux\project.dnf";
filePath = @"F:\临时\project\linux\http\project.dnf"; filePath = @"F:\临时\project\linux\http\project.dnf";
filePath = @"F:\临时\project\yolo flow\project.dnf"; filePath = @"F:\临时\project\yolo flow\project.dnf";
filePath = @"F:\临时\project\data\project.dnf";
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
App.FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content); App.FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
App.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;// App.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;//

View File

@@ -78,17 +78,6 @@
<!--<RowDefinition Height="3*"></RowDefinition>--> <!--<RowDefinition Height="3*"></RowDefinition>-->
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!--<Grid Margin="2,2,1,5" Grid.Row="0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="保存项目" Click="ButtonSaveFile_Click" HorizontalAlignment="Left" Margin="5,5,5,5"/>
--><!--<Button Grid.Column="1" Content="加载项目" Click="ButtonLoadProjectFile_Click" HorizontalAlignment="Left" Margin="5,5,5,5"/>-->
<!--<Button Grid.Row="0" Content="卸载清空" Click="UnloadAllButton_Click" HorizontalAlignment="Right" Margin="5,5,5,5"/>--><!--
</Grid>-->
<!--暂时隐藏基础面板 Visibility="Collapsed" --> <!--暂时隐藏基础面板 Visibility="Collapsed" -->
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Auto">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">

View File

@@ -361,7 +361,7 @@ namespace Serein.Workbench
/// 节点连接关系变更 /// 节点连接关系变更
/// </summary> /// </summary>
/// <param name="eventArgs"></param> /// <param name="eventArgs"></param>
private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs) private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs)
{ {
string fromNodeGuid = eventArgs.FromNodeGuid; string fromNodeGuid = eventArgs.FromNodeGuid;
string toNodeGuid = eventArgs.ToNodeGuid; string toNodeGuid = eventArgs.ToNodeGuid;
@@ -395,16 +395,17 @@ namespace Serein.Workbench
FlowChartCanvas, FlowChartCanvas,
connectionType, connectionType,
startJunction, startJunction,
endJunction, endJunction
() => EnvDecorator.RemoveConnectInvokeAsync(fromNodeGuid, toNodeGuid, connectionType)
); );
//() => EnvDecorator.RemoveConnectInvokeAsync(fromNodeGuid, toNodeGuid, connectionType)
if (toNodeControl is FlipflopNodeControl flipflopControl if (toNodeControl is FlipflopNodeControl flipflopControl
&& flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器
{ {
NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除
} }
connection.RefreshLine(); // 添加贝塞尔曲线显示 //connection.RefreshLine(); // 添加贝塞尔曲线显示
Connections.Add(connection); Connections.Add(connection);
fromNodeControl.AddCnnection(connection); fromNodeControl.AddCnnection(connection);
toNodeControl.AddCnnection(connection); toNodeControl.AddCnnection(connection);
@@ -427,10 +428,9 @@ namespace Serein.Workbench
foreach (var connection in removeConnections) foreach (var connection in removeConnections)
{ {
connection.DeleteConnection();
Connections.Remove(connection); Connections.Remove(connection);
fromNodeControl.RemoveCnnection(connection); fromNodeControl.RemoveConnection(connection); // 移除连接
toNodeControl.RemoveCnnection(connection); toNodeControl.RemoveConnection(connection); // 移除连接
if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control))
{ {
JudgmentFlipFlopNode(control); // 连接关系变更时判断 JudgmentFlipFlopNode(control); // 连接关系变更时判断
@@ -461,6 +461,10 @@ namespace Serein.Workbench
_ => throw new Exception("窗体事件 FlowEnvironment_NodeConnectChangeEvemt 创建/删除节点之间的参数传递关系 JunctionControlBase 枚举值错误 。非预期的枚举值。") // 应该不会触发 _ => throw new Exception("窗体事件 FlowEnvironment_NodeConnectChangeEvemt 创建/删除节点之间的参数传递关系 JunctionControlBase 枚举值错误 。非预期的枚举值。") // 应该不会触发
}; };
if(IToJunction.ArgDataJunction.Length == 0)
{
}
JunctionControlBase endJunction = IToJunction.ArgDataJunction[eventArgs.ArgIndex]; JunctionControlBase endJunction = IToJunction.ArgDataJunction[eventArgs.ArgIndex];
LineType lineType = LineType.Bezier; LineType lineType = LineType.Bezier;
// 添加连接 // 添加连接
@@ -470,16 +474,8 @@ namespace Serein.Workbench
eventArgs.ArgIndex, eventArgs.ArgIndex,
eventArgs.ConnectionArgSourceType, eventArgs.ConnectionArgSourceType,
startJunction, startJunction,
endJunction, endJunction
() => EnvDecorator.RemoveConnectArgSourceAsync(fromNodeGuid, toNodeGuid, eventArgs.ArgIndex)
); );
//if (toNodeControl is FlipflopNodeControl flipflopControl
// && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器
//{
// NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除
//}
connection.RefreshLine(); // 添加贝塞尔曲线显示
Connections.Add(connection); Connections.Add(connection);
fromNodeControl.AddCnnection(connection); fromNodeControl.AddCnnection(connection);
toNodeControl.AddCnnection(connection); toNodeControl.AddCnnection(connection);
@@ -503,10 +499,9 @@ namespace Serein.Workbench
if (connection.End is ArgJunctionControl junctionControl && junctionControl.ArgIndex == eventArgs.ArgIndex) if (connection.End is ArgJunctionControl junctionControl && junctionControl.ArgIndex == eventArgs.ArgIndex)
{ {
// 找到符合删除条件的连接线 // 找到符合删除条件的连接线
connection.DeleteConnection(); // 从UI层面上移除
Connections.Remove(connection); // 从本地记录中移除 Connections.Remove(connection); // 从本地记录中移除
fromNodeControl.RemoveCnnection(connection); // 从节点持有的记录移除 fromNodeControl.RemoveConnection(connection); // 从节点持有的记录移除
toNodeControl.RemoveCnnection(connection); // 从节点持有的记录移除 toNodeControl.RemoveConnection(connection); // 从节点持有的记录移除
} }

View File

@@ -37,42 +37,41 @@ namespace Serein.Workbench.Node.View
/// <summary> /// <summary>
/// 方法入参控制点(可能有,可能没) /// 方法入参控制点(可能有,可能没)
/// </summary> /// </summary>
private JunctionControlBase[] argDataJunction; JunctionControlBase[] INodeJunction.ArgDataJunction
/// <summary> {
/// 方法入参控制点(可能有,可能没) get
/// </summary> {
JunctionControlBase[] INodeJunction.ArgDataJunction { get { // 获取 MethodDetailsControl 实例
if(argDataJunction == null) var methodDetailsControl = this.MethodDetailsControl;
var itemsControl = FindVisualChild<ItemsControl>(methodDetailsControl); // 查找 ItemsControl
if (itemsControl != null)
{ {
// 获取 MethodDetailsControl 实例 var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length];
var methodDetailsControl = this.MethodDetailsControl; var controls = new List<JunctionControlBase>();
argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length];
var itemsControl = FindVisualChild<ItemsControl>(methodDetailsControl); // 查找 ItemsControl for (int i = 0; i < itemsControl.Items.Count; i++)
if (itemsControl != null)
{ {
var controls = new List<JunctionControlBase>(); var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (container != null)
for (int i = 0; i < itemsControl.Items.Count; i++)
{ {
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; var argControl = FindVisualChild<ArgJunctionControl>(container);
if (container != null) if (argControl != null)
{ {
var argControl = FindVisualChild<ArgJunctionControl>(container); controls.Add(argControl); // 收集 ArgJunctionControl 实例
if (argControl != null)
{
controls.Add(argControl); // 收集 ArgJunctionControl 实例
}
} }
} }
argDataJunction = controls.ToArray();
} }
return argDataJunction = controls.ToArray();
} }
return argDataJunction; else
} } {
return [];
}
}
}
} }
} }

View File

@@ -4,7 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Serein.Workbench.Node.View" xmlns:local="clr-namespace:Serein.Workbench.Node.View"
MaxWidth="300"
> >
<DockPanel> <DockPanel>
<StackPanel DockPanel.Dock="Top" > <StackPanel DockPanel.Dock="Top" >

View File

@@ -32,10 +32,6 @@ namespace Serein.Workbench.Node.View
/// </summary> /// </summary>
JunctionControlBase INodeJunction.ReturnDataJunction => this.ResultJunctionControl; JunctionControlBase INodeJunction.ReturnDataJunction => this.ResultJunctionControl;
/// <summary>
/// 方法入参控制点(可能有,可能没)
/// </summary>
private JunctionControlBase[] argDataJunction;
/// <summary> /// <summary>
/// 方法入参控制点(可能有,可能没) /// 方法入参控制点(可能有,可能没)
/// </summary> /// </summary>
@@ -43,34 +39,34 @@ namespace Serein.Workbench.Node.View
{ {
get get
{ {
if (argDataJunction == null) // 获取 MethodDetailsControl 实例
var methodDetailsControl = this.MethodDetailsControl;
var itemsControl = FindVisualChild<ItemsControl>(methodDetailsControl); // 查找 ItemsControl
if (itemsControl != null)
{ {
// 获取 MethodDetailsControl 实例 var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length];
var methodDetailsControl = this.MethodDetailsControl; var controls = new List<JunctionControlBase>();
argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length];
var itemsControl = FindVisualChild<ItemsControl>(methodDetailsControl); // 查找 ItemsControl for (int i = 0; i < itemsControl.Items.Count; i++)
if (itemsControl != null)
{ {
var controls = new List<JunctionControlBase>(); var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (container != null)
for (int i = 0; i < itemsControl.Items.Count; i++)
{ {
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; var argControl = FindVisualChild<ArgJunctionControl>(container);
if (container != null) if (argControl != null)
{ {
var argControl = FindVisualChild<ArgJunctionControl>(container); controls.Add(argControl); // 收集 ArgJunctionControl 实例
if (argControl != null)
{
controls.Add(argControl); // 收集 ArgJunctionControl 实例
}
} }
} }
argDataJunction = controls.ToArray();
} }
return argDataJunction = controls.ToArray();
}
else
{
return [];
} }
return argDataJunction;
} }
} }
} }
} }

View File

@@ -26,7 +26,7 @@ namespace Serein.Workbench.Node.View
this.MouseDown += ParamsArg_OnMouseDown; // 增加或删除 this.MouseDown += ParamsArg_OnMouseDown; // 增加或删除
this.MouseMove += ParamsArgControl_MouseMove; this.MouseMove += ParamsArgControl_MouseMove;
this.MouseLeave += ParamsArgControl_MouseLeave; this.MouseLeave += ParamsArgControl_MouseLeave;
AddOrRemoveParamsAction = Add; AddOrRemoveParamsTask = AddAsync;
} }
@@ -90,11 +90,11 @@ namespace Serein.Workbench.Node.View
private bool isMouseOver; // 鼠标悬停状态 private bool isMouseOver; // 鼠标悬停状态
private Action AddOrRemoveParamsAction; // 增加或删除参数 private Func<Task> 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) private void ParamsArgControl_MouseMove(object sender, MouseEventArgs e)
@@ -111,7 +111,7 @@ namespace Serein.Workbench.Node.View
// 如果焦点仍在控件上时,则改变点击事件 // 如果焦点仍在控件上时,则改变点击事件
if (isMouseOver) if (isMouseOver)
{ {
AddOrRemoveParamsAction = Remove; AddOrRemoveParamsTask = RemoveAsync;
this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘 this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘
} }
@@ -125,20 +125,20 @@ namespace Serein.Workbench.Node.View
private void ParamsArgControl_MouseLeave(object sender, MouseEventArgs e) private void ParamsArgControl_MouseLeave(object sender, MouseEventArgs e)
{ {
isMouseOver = false; isMouseOver = false;
AddOrRemoveParamsAction = Add; // 鼠标焦点离开时恢复点击事件 AddOrRemoveParamsTask = AddAsync; // 鼠标焦点离开时恢复点击事件
cancellationTokenSource?.Cancel(); cancellationTokenSource?.Cancel();
this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘 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);
} }
} }

View File

@@ -48,22 +48,23 @@ namespace Serein.Workbench.Node.View
/// 删除了连接之后,还需要从节点中的记录移除 /// 删除了连接之后,还需要从节点中的记录移除
/// </summary> /// </summary>
/// <param name="connection"></param> /// <param name="connection"></param>
public void RemoveCnnection(ConnectionControl connection) public void RemoveConnection(ConnectionControl connection)
{ {
connectionControls.Remove(connection); connectionControls.Remove(connection);
connection.Remote(); connection.Remote();
} }
/// <summary> /// <summary>
/// 删除了连接之后,还需要从节点中的记录移除 /// 删除所有连接
/// </summary> /// </summary>
public void RemoveAllConection() public void RemoveAllConection()
{ {
foreach (var connection in this.connectionControls) foreach (var connection in this.connectionControls)
{ {
connection.Remote(); // 主动更新连线位置 connection.Remote();
} }
} }
/// <summary> /// <summary>
/// 更新与该节点有关的数据 /// 更新与该节点有关的数据
/// </summary> /// </summary>

View File

@@ -104,7 +104,6 @@ namespace Serein.Workbench.Node.View
public class ConnectionControl public class ConnectionControl
{ {
private readonly Action RemoteCallback;
/// <summary> /// <summary>
@@ -152,11 +151,9 @@ namespace Serein.Workbench.Node.View
public ConnectionControl(Canvas Canvas, public ConnectionControl(Canvas Canvas,
ConnectionInvokeType invokeType, ConnectionInvokeType invokeType,
JunctionControlBase Start, JunctionControlBase Start,
JunctionControlBase End, JunctionControlBase End)
Action remoteCallback)
{ {
this.LineType = LineType.Bezier; this.LineType = LineType.Bezier;
this.RemoteCallback = remoteCallback;
this.Canvas = Canvas; this.Canvas = Canvas;
this.InvokeType = invokeType; this.InvokeType = invokeType;
this.Start = Start; this.Start = Start;
@@ -174,13 +171,11 @@ namespace Serein.Workbench.Node.View
int argIndex, int argIndex,
ConnectionArgSourceType argSourceType, ConnectionArgSourceType argSourceType,
JunctionControlBase Start, JunctionControlBase Start,
JunctionControlBase End, JunctionControlBase End)
Action remoteCallback)
{ {
this.LineType = LineType; this.LineType = LineType;
this.RemoteCallback = remoteCallback;
this.Canvas = Canvas; this.Canvas = Canvas;
this.ArgIndex = ArgIndex; this.ArgIndex = argIndex;
this.ArgSourceType = argSourceType; this.ArgSourceType = argSourceType;
this.Start = Start; this.Start = Start;
this.End = End; this.End = End;
@@ -223,24 +218,26 @@ namespace Serein.Workbench.Node.View
private void ConfigureLineContextMenu() private void ConfigureLineContextMenu()
{ {
var contextMenu = new ContextMenu(); 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; BezierLine.ContextMenu = contextMenu;
} }
/// <summary>
/// 删除该连线
/// </summary>
/// <param name="line"></param>
public void DeleteConnection()
{
RemoteCallback?.Invoke();
}
/// <summary> /// <summary>
/// 删除该连线 /// 删除该连线
/// </summary> /// </summary>
public void Remote() public void Remote()
{ {
Canvas.Children.Remove(BezierLine); 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) ;
}
} }
/// <summary> /// <summary>
@@ -252,280 +249,26 @@ namespace Serein.Workbench.Node.View
BezierLine.UpdatePoints(startPoint, endPoint); BezierLine.UpdatePoints(startPoint, endPoint);
} }
private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心 private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心
private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心 private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心
/// <summary>
/// 刷新坐标
/// </summary>
#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 startPoint = startElement.TranslatePoint(rightCenterOfStartLocation, canvas); // 获取起始节点的中心位置
var endPoint = endElement.TranslatePoint(leftCenterOfEndLocation, canvas); // 计算终点位置 var endPoint = endElement.TranslatePoint(leftCenterOfEndLocation, canvas); // 计算终点位置
return (startPoint, endPoint); return (startPoint, endPoint);
} }
#endregion
} }
/*
/// <summary>
/// 连接控件,表示控件的连接关系
/// </summary>
public class ConnectionControl : Shape
{
private readonly Action RemoteCallback;
/// <summary>
/// 关于调用
/// </summary>
/// <param name="Canvas"></param>
/// <param name="Type"></param>
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();
}
/// <summary>
/// 关于入参
/// </summary>
/// <param name="Canvas"></param>
/// <param name="Type"></param>
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();
}
/// <summary>
/// 所在的画布
/// </summary>
public Canvas Canvas { get; }
/// <summary>
/// 调用方法类型,连接类型
/// </summary>
public ConnectionInvokeType Type { get; }
/// <summary>
/// 获取参数类型,第几个参数
/// </summary>
public int ArgIndex { get; set; } = -1;
/// <summary>
/// 参数来源(决定了连接线的样式)
/// </summary>
public ConnectionArgSourceType ConnectionArgSourceType { get; set; }
/// <summary>
/// 起始控制点
/// </summary>
public JunctionControlBase Start { get; set; }
/// <summary>
/// 目标控制点
/// </summary>
public JunctionControlBase End { get; set; }
/// <summary>
/// 配置连接曲线的右键菜单
/// </summary>
/// <param name="line"></param>
private void ConfigureLineContextMenu(ConnectionControl connection)
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection)));
connection.ContextMenu = contextMenu;
}
/// <summary>
/// 删除该连线
/// </summary>
/// <param name="line"></param>
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();
}
/// <summary>
/// 移除
/// </summary>
public void RemoveFromCanvas()
{
Canvas.Children.Remove(this); // 移除线
}
/// <summary>
/// 重新绘制
/// </summary>
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); // 置底
}
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
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); // 获取起始节点的中心位置
}
/// <summary>
/// 根据连接类型指定颜色
/// </summary>
/// <param name="currentConnectionType"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
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
}
*/