mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-02 15:50:47 +08:00
1. 重新设计了 JSON门户类的实现
2. Script脚本添加了原始字符串的实现 3. 修复了Script中无法对 \" 双引号转义的问题 4. 新增了对于集合嵌套取值的支持(目前仅是集合取值) 5. 重新设计了FlowWorkManagement任务启动的逻辑,修复了触发器无法正常运行的问题 6. 在ScriptBaseFunc中新增了 json() 本地函数,支持将字符串转为IJsonToken进行取值。 7. EmitHelper对于集合取值时,反射获取“get_item”委托时存在看你多个MethodInfo,现在可以传入子项类型,帮助匹配目标重载方法
This commit is contained in:
@@ -72,7 +72,7 @@ namespace Serein.FlowStartTool
|
||||
#endregion
|
||||
|
||||
#region 加载项目
|
||||
_ = Task.Run(async () => await flowEnv.StartFlow(flowProjectData, fileDataPath));
|
||||
_ = Task.Run( () => flowEnv.StartFlow(flowProjectData, fileDataPath));
|
||||
while (flowEnv.IsRuning)
|
||||
{
|
||||
Console.ReadKey();
|
||||
|
||||
@@ -1002,7 +1002,7 @@ namespace Serein.Library.Api
|
||||
/// <param name="message">消息</param>
|
||||
/// <param name="type">输出类型</param>
|
||||
/// <param name="class">输出级别</param>
|
||||
void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial);
|
||||
void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Debug);
|
||||
/// <summary>
|
||||
/// <para>提供设置UI上下文的能力</para>
|
||||
/// <para>提供设置UI上下文的能力,在WinForm/WPF项目中,在UI线程外对UI元素的修改将会导致异常</para>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -9,8 +10,22 @@ namespace Serein.Library.Api
|
||||
/// <summary>
|
||||
/// JSON数据交互的Token接口,允许使用不同的JSON库进行数据处理。
|
||||
/// </summary>
|
||||
public interface IJsonToken
|
||||
public interface IJsonToken
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取 Token
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
IJsonToken this[object name] { get; }
|
||||
|
||||
/* /// <summary>
|
||||
/// 获取 Token 数组的元素,允许通过索引访问数组中的元素。
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
IJsonToken this[int index] { get; }*/
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定名称的属性,如果存在则返回true,并通过out参数返回对应的IJsonToken对象。
|
||||
/// </summary>
|
||||
@@ -83,6 +98,7 @@ namespace Serein.Library.Api
|
||||
/// </summary>
|
||||
public interface IJsonProvider
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// JSON文本转为指定类型
|
||||
/// </summary>
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace Serein.Library
|
||||
public enum InfoClass
|
||||
{
|
||||
/// <summary>
|
||||
/// 琐碎的
|
||||
/// 调试
|
||||
/// </summary>
|
||||
Trivial,
|
||||
Debug,
|
||||
/// <summary>
|
||||
/// 一般的
|
||||
/// </summary>
|
||||
|
||||
@@ -37,15 +37,13 @@ namespace Serein.Library
|
||||
UI,
|
||||
|
||||
/// <summary>
|
||||
/// <para>触发器节点,必须为标记在可异步等待的方法,建议与继承了 FlowTriggerk<TEnum> 的实例对象搭配使用</para>
|
||||
/// <para>触发器节点,必须为标记在可异步等待的方法</para>
|
||||
/// <para>方法返回值必须为Task<IFlipflopContext<TResult>>,若为其它返回值,将不会创建节点。</para>
|
||||
/// <para>触发器根据在分支中的位置,分为两种类型:流程分支中的触发器、全局触发器</para>
|
||||
/// <para>一般的触发器:存在于分支某处,也可能是分支的终点,但一定不是流程的起点与分支的起点。</para>
|
||||
/// <para>一般的触发器行为:在当前分支中执行一次之后不再执行,一般用于等待某个操作的响应。</para>
|
||||
/// <para>一般的触发器入参:如果使用了 FlowTriggerk<TEnum> ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。</para>
|
||||
/// <para>全局触发器:没有上游分支、同时并非流程的起始节点。</para>
|
||||
/// <para>全局触发器行为:全局触发器会循环执行,直到流程结束。</para>
|
||||
/// <para>一般的触发器入参:如果使用了 FlowTriggerk<TEnum> ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。</para>
|
||||
/// </summary>
|
||||
Flipflop,
|
||||
/// <summary>
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace Serein.Library
|
||||
/// </summary>
|
||||
/// <param name="type">类型信息</param>
|
||||
/// <param name="emitType">操作类型</param>
|
||||
public DelegateDetails(Type type, EmitType emitType)
|
||||
public DelegateDetails(Type type, EmitType emitType, Type? itemType = null)
|
||||
{
|
||||
if (emitType == EmitType.CollectionSetter)
|
||||
{
|
||||
@@ -170,7 +170,7 @@ namespace Serein.Library
|
||||
else if (emitType == EmitType.CollectionGetter)
|
||||
{
|
||||
this.emitType = EmitType.CollectionGetter;
|
||||
collectionGetter = EmitHelper.CreateCollectionGetter(type);
|
||||
collectionGetter = EmitHelper.CreateCollectionGetter(type, itemType);
|
||||
}
|
||||
else if (emitType == EmitType.ArrayCreate)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library
|
||||
@@ -82,33 +83,92 @@ namespace Serein.Library
|
||||
/// <summary>
|
||||
/// 触发类型
|
||||
/// </summary>
|
||||
|
||||
public TriggerDescription Type { get; set; }
|
||||
/// <summary>
|
||||
/// 触发时传递的数据
|
||||
/// </summary>
|
||||
public TResult Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发器上下文构造函数
|
||||
/// </summary>
|
||||
/// <param name="ffState"></param>
|
||||
public FlipflopContext(FlipflopStateType ffState)
|
||||
public FlipflopContext()
|
||||
{
|
||||
State = ffState;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发器上下文构造函数,传入状态和数据值
|
||||
/// 成功触发器上下文,表示触发器执行成功并返回结果
|
||||
/// </summary>
|
||||
/// <param name="ffState"></param>
|
||||
/// <param name="value"></param>
|
||||
public FlipflopContext(FlipflopStateType ffState, TResult value)
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Ok<TResult>(TResult result)
|
||||
{
|
||||
State = ffState;
|
||||
Value = value;
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = FlipflopStateType.Succeed,
|
||||
Type = TriggerDescription.External,
|
||||
Value = result,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示触发器执行失败
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Fail()
|
||||
{
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = FlipflopStateType.Fail,
|
||||
Type = TriggerDescription.External,
|
||||
Value = default,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示触发器执行过程中发生了错误
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Error()
|
||||
{
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = FlipflopStateType.Error,
|
||||
Type = TriggerDescription.External,
|
||||
Value = default,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消触发器上下文,表示触发器被外部取消
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Cancel()
|
||||
{
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = FlipflopStateType.Cancel,
|
||||
Type = TriggerDescription.External,
|
||||
Value = default,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 超时触发器上下文,表示触发器在指定时间内未完成
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <returns></returns>
|
||||
public static FlipflopContext<TResult> Overtime(FlipflopStateType state = FlipflopStateType.Fail)
|
||||
{
|
||||
return new FlipflopContext<TResult>()
|
||||
{
|
||||
State = state,
|
||||
Type = TriggerDescription.Overtime,
|
||||
Value = default,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Serein.Library
|
||||
/// 起始节点
|
||||
/// </summary>
|
||||
[DataInfo]
|
||||
private IFlowNode _startNode;
|
||||
private IFlowNode? _startNode;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Serein.Library
|
||||
{
|
||||
|
||||
private readonly SortedDictionary<string, CallNode> _callNodes = new SortedDictionary<string,CallNode>();
|
||||
//private readonly Dictionary<string, CallNode> _callNodes = new Dictionary<string,CallNode>();
|
||||
|
||||
/// <summary>
|
||||
/// 索引器,允许通过字符串索引访问CallNode
|
||||
@@ -700,7 +699,7 @@ namespace Serein.Library
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
|
||||
public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial)
|
||||
public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Debug)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -134,6 +135,28 @@ namespace Serein.Library
|
||||
return obj?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
|
||||
#region JSON挂载方法
|
||||
|
||||
/// <summary>
|
||||
/// 转为JSON对象
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns></returns>
|
||||
public static IJsonToken json(string content)
|
||||
{
|
||||
return JsonHelper.Parse(content);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取全局数据
|
||||
/// </summary>
|
||||
@@ -153,11 +176,11 @@ namespace Serein.Library
|
||||
{
|
||||
return type.GetType();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录日志信息
|
||||
/// 输出内容
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
|
||||
public static void log(object value)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, value?.ToString());
|
||||
@@ -168,18 +191,9 @@ namespace Serein.Library
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task sleep(object value)
|
||||
public static async Task sleep(int value)
|
||||
{
|
||||
if (value is int @int)
|
||||
{
|
||||
Console.WriteLine($"等待{@int}ms");
|
||||
await Task.Delay(@int);
|
||||
}
|
||||
else if (value is TimeSpan timeSpan)
|
||||
{
|
||||
Console.WriteLine($"等待{timeSpan}");
|
||||
await Task.Delay(timeSpan);
|
||||
}
|
||||
await Task.Delay(value);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
@@ -504,58 +505,97 @@ namespace Serein.Library.Utils
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 构建集合获取委托:Func<object, object, object>
|
||||
/// 构建集合取值委托:(object collection, object index) => object value
|
||||
/// 支持数组、泛型集合、IDictionary 等类型
|
||||
/// </summary>
|
||||
/// <param name="collectionType"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
public static Func<object, object, object> CreateCollectionGetter(Type collectionType)
|
||||
public static Func<object, object, object> CreateCollectionGetter(Type collectionType, Type? itemType = null)
|
||||
{
|
||||
DynamicMethod dm = new DynamicMethod(
|
||||
"GetCollectionValue",
|
||||
typeof(object),
|
||||
new[] { typeof(object), typeof(object) },
|
||||
typeof(EmitHelper).Module,
|
||||
true);
|
||||
skipVisibility: true);
|
||||
|
||||
ILGenerator il = dm.GetILGenerator();
|
||||
|
||||
// 数组类型处理
|
||||
if (collectionType.IsArray)
|
||||
{
|
||||
// (object array, object index) => ((T[])array)[(int)index]
|
||||
var elementType = collectionType.GetElementType()!;
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, collectionType); // 转为真实数组类型
|
||||
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Unbox_Any, typeof(int)); // index
|
||||
|
||||
il.Emit(OpCodes.Unbox_Any, typeof(int)); // 转为int索引
|
||||
il.Emit(OpCodes.Ldelem, elementType); // 取值
|
||||
|
||||
if (elementType.IsValueType)
|
||||
il.Emit(OpCodes.Box, elementType); // 装箱
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
else
|
||||
// 非泛型 IDictionary 类型(如 Hashtable、JObject)
|
||||
else if (IsGenericDictionaryType(collectionType, out var keyType, out var valueType))
|
||||
{
|
||||
// 调用 get_Item 方法
|
||||
MethodInfo? getItem = collectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public);
|
||||
var getItem = collectionType.GetMethod("get_Item", new[] { keyType });
|
||||
if (getItem == null)
|
||||
throw new NotSupportedException($"类型 {collectionType} 不支持 get_Item。");
|
||||
throw new NotSupportedException($"{collectionType} 未实现 get_Item({keyType})");
|
||||
|
||||
var parameters = getItem.GetParameters();
|
||||
var indexType = parameters[0].ParameterType;
|
||||
var returnType = getItem.ReturnType;
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, collectionType);
|
||||
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
if (indexType.IsValueType)
|
||||
il.Emit(OpCodes.Unbox_Any, indexType);
|
||||
if (keyType.IsValueType)
|
||||
il.Emit(OpCodes.Unbox_Any, keyType);
|
||||
else
|
||||
il.Emit(OpCodes.Castclass, indexType);
|
||||
il.Emit(OpCodes.Castclass, keyType);
|
||||
|
||||
il.Emit(OpCodes.Callvirt, getItem);
|
||||
|
||||
if (returnType.IsValueType)
|
||||
il.Emit(OpCodes.Box, returnType);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
// 实现 get_Item 方法的类型(如 List<T>, Dictionary<TKey, TValue> 等)
|
||||
else
|
||||
{
|
||||
/*var methodInfos = collectionType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
|
||||
MethodInfo? getItem;
|
||||
|
||||
if (methodInfos.Length > 1)
|
||||
{
|
||||
getItem = methodInfos.Where(m => m.Name.Equals("get_Item")).Where(m =>
|
||||
{
|
||||
var ps = m.GetParameters().ToArray();
|
||||
if (ps.Length > 1) return false;
|
||||
if (ps[0].ParameterType == typeof(object)) return false;
|
||||
return true;
|
||||
}).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
//getItem = collectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public);
|
||||
getItem = methodInfos.First(m => m.Name.Equals("get_Item"));
|
||||
}
|
||||
*/
|
||||
// GetMethod(name, bindingAttr, binder: null, types, modifiers: null);
|
||||
MethodInfo? getItem = collectionType.GetMethod("get_Item", bindingAttr: BindingFlags.Instance | BindingFlags.Public, binder: null, types: [itemType], modifiers: null);
|
||||
if (getItem == null)
|
||||
throw new NotSupportedException($"类型 {collectionType} 不支持 get_Item。");
|
||||
|
||||
var indexParamType = getItem.GetParameters()[0].ParameterType;
|
||||
var returnType = getItem.ReturnType;
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, collectionType);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
|
||||
if (indexParamType.IsValueType)
|
||||
il.Emit(OpCodes.Unbox_Any, indexParamType);
|
||||
else
|
||||
il.Emit(OpCodes.Castclass, indexParamType);
|
||||
|
||||
il.Emit(OpCodes.Callvirt, getItem);
|
||||
|
||||
@@ -568,6 +608,29 @@ namespace Serein.Library.Utils
|
||||
return (Func<object, object, object>)dm.CreateDelegate(typeof(Func<object, object, object>));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static bool IsGenericDictionaryType(Type type, out Type keyType, out Type valueType)
|
||||
{
|
||||
keyType = null!;
|
||||
valueType = null!;
|
||||
|
||||
var dictInterface = type
|
||||
.GetInterfaces()
|
||||
.FirstOrDefault(t =>
|
||||
t.IsGenericType &&
|
||||
t.GetGenericTypeDefinition() == typeof(IDictionary<,>));
|
||||
|
||||
if (dictInterface != null)
|
||||
{
|
||||
var args = dictInterface.GetGenericArguments();
|
||||
keyType = args[0];
|
||||
valueType = args[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,16 @@ namespace Serein.Library.Utils
|
||||
/// <returns>对应的 Channel</returns>
|
||||
private Channel<TriggerResult<object>> GetOrCreateChannel(TSignal signal)
|
||||
{
|
||||
return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded<TriggerResult<object>>());
|
||||
if(_channels.TryGetValue(signal, out var channel))
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
else
|
||||
{
|
||||
channel = Channel.CreateUnbounded<TriggerResult<object>>();
|
||||
_channels.AddOrUpdate(signal, _ => channel, (s, r) => channel = r);
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace Serein.Library
|
||||
/// <param name="class"></param>
|
||||
public static void WriteLine(Exception ex, InfoClass @class = InfoClass.General)
|
||||
{
|
||||
if(@class == InfoClass.Trivial)
|
||||
if(@class == InfoClass.Debug)
|
||||
{
|
||||
|
||||
SereinEnv.environment.WriteLine(InfoType.ERROR, ex.ToString(), @class);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Serein.Library;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.NodeFlow.Model;
|
||||
using Serein.NodeFlow.Model.Nodes;
|
||||
using Serein.NodeFlow.Services;
|
||||
using Serein.NodeFlow.Tool;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -52,9 +54,8 @@ namespace Serein.NodeFlow.Env
|
||||
private ObjectPool<FlowWorkManagement> flowTaskManagementPool;
|
||||
private FlowWorkOptions flowTaskOptions;
|
||||
|
||||
|
||||
|
||||
|
||||
private FlowWorkManagement? flowWorkManagement;
|
||||
private ISereinIOC? externalIOC;
|
||||
private Action<ISereinIOC>? setDefultMemberOnReset;
|
||||
private bool IsUseExternalIOC = false;
|
||||
@@ -88,9 +89,28 @@ namespace Serein.NodeFlow.Env
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<FlowWorkManagement> flowWorkManagements = [];
|
||||
private FlowWorkManagement GetFWM()
|
||||
{
|
||||
var fwm = flowTaskManagementPool.Allocate();
|
||||
flowWorkManagements.Add(fwm);
|
||||
return fwm;
|
||||
}
|
||||
private void ReturnFWM(FlowWorkManagement fwm)
|
||||
{
|
||||
if (flowWorkManagements.Contains(fwm))
|
||||
{
|
||||
flowWorkManagements.Remove(fwm);
|
||||
}
|
||||
fwm.Exit();
|
||||
flowTaskManagementPool.Free(fwm);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> StartFlowAsync(string[] canvasGuids)
|
||||
{
|
||||
|
||||
#region 校验参数
|
||||
HashSet<string> guids = new HashSet<string>();
|
||||
bool isBreak = false;
|
||||
@@ -123,47 +143,49 @@ namespace Serein.NodeFlow.Env
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
// 初始化每个画布的数据,转换为流程任务
|
||||
var flowTasks = guids.Select(guid =>
|
||||
{
|
||||
if (!flowModelService.TryGetCanvasModel(guid, out var canvasModel))
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在,将不会运行。{guid}");
|
||||
return default;
|
||||
}
|
||||
if (canvasModel.StartNode is null)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在起始节点,将不会运行。{guid}");
|
||||
return default;
|
||||
}
|
||||
return canvasModel;
|
||||
})
|
||||
.Where(canvasModel => canvasModel != default && canvasModel.StartNode != null)
|
||||
.OfType<FlowCanvasDetails>()
|
||||
.ToDictionary(key => key.Guid,
|
||||
value => new FlowTask
|
||||
{
|
||||
GetStartNode = () => value.StartNode!,
|
||||
GetNodes = () => flowModelService.GetAllNodeModel(value.Guid),
|
||||
IsWaitStartFlow = false
|
||||
});
|
||||
|
||||
#region 初始化每个画布的数据,转换为流程任务
|
||||
Dictionary<string, FlowTask> flowTasks = [];
|
||||
foreach (var guid in guids)
|
||||
|
||||
if(flowTasks.Values.Count == 0)
|
||||
{
|
||||
if (!flowModelService.TryGetCanvasModel(guid, out var canvasModel))
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在,停止运行。{guid}");
|
||||
return false;
|
||||
}
|
||||
var ft = new FlowTask();
|
||||
ft.GetNodes = () => flowModelService.GetAllNodeModel(guid);
|
||||
if (canvasModel.StartNode?.Guid is null)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在起始节点,将停止运行。{guid}");
|
||||
return false;
|
||||
}
|
||||
ft.GetStartNode = () => canvasModel.StartNode;
|
||||
flowTasks.Add(guid, ft);
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
IOC.Reset();
|
||||
|
||||
// 初始化IOC
|
||||
setDefultMemberOnReset?.Invoke(IOC);
|
||||
IOC.Reset();
|
||||
IOC.Register<IFlowEnvironment>(() => flowEnvironment);
|
||||
//externalIOC.Register<IScriptFlowApi, ScriptFlowApi>(); // 注册脚本接口
|
||||
|
||||
var flowTaskOptions = new FlowWorkOptions
|
||||
{
|
||||
FlowIOC = IOC,
|
||||
Environment = flowEnvironment, // 流程
|
||||
Flows = flowTasks,
|
||||
FlowContextPool = contexts, // 上下文对象池
|
||||
AutoRegisterTypes = flowLibraryService.GetaAutoRegisterType(), // 需要自动实例化的类型
|
||||
InitMds = flowLibraryService.GetMdsOnFlowStart(NodeType.Init),
|
||||
LoadMds = flowLibraryService.GetMdsOnFlowStart(NodeType.Loading),
|
||||
ExitMds = flowLibraryService.GetMdsOnFlowStart(NodeType.Exit),
|
||||
};
|
||||
|
||||
|
||||
flowWorkManagement = new FlowWorkManagement(flowTaskOptions);
|
||||
var cts = new CancellationTokenSource();
|
||||
var flowWorkManagement = GetFWM();
|
||||
flowWorkManagement.WorkOptions.Flows = flowTasks;
|
||||
flowWorkManagement.WorkOptions.AutoRegisterTypes = flowLibraryService.GetaAutoRegisterType(); // 需要自动实例化的类型
|
||||
flowWorkManagement.WorkOptions.InitMds = flowLibraryService.GetMdsOnFlowStart(NodeType.Init);
|
||||
flowWorkManagement.WorkOptions.LoadMds = flowLibraryService.GetMdsOnFlowStart(NodeType.Loading);
|
||||
flowWorkManagement.WorkOptions.ExitMds = flowLibraryService.GetMdsOnFlowStart(NodeType.Exit);
|
||||
using var cts = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
var t = await flowWorkManagement.RunAsync(cts.Token);
|
||||
@@ -174,22 +196,18 @@ namespace Serein.NodeFlow.Env
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"流程运行完毕{Environment.NewLine}"); ;
|
||||
}
|
||||
flowTaskOptions = null;
|
||||
ReturnFWM(flowWorkManagement);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<TResult> StartFlowAsync<TResult>(string startNodeGuid)
|
||||
{
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var checkpoints = new Dictionary<string, TimeSpan>();
|
||||
|
||||
var flowTaskManagement = flowTaskManagementPool.Allocate();
|
||||
|
||||
var flowWorkManagement = GetFWM();
|
||||
if (!flowModelService.TryGetNodeModel(startNodeGuid, out IFlowNode? nodeModel))
|
||||
{
|
||||
throw new Exception($"节点不存在【{startNodeGuid}】");
|
||||
@@ -200,10 +218,10 @@ namespace Serein.NodeFlow.Env
|
||||
}
|
||||
|
||||
|
||||
var flowContextPool = flowTaskManagement.WorkOptions.FlowContextPool;
|
||||
var flowContextPool = flowWorkManagement.WorkOptions.FlowContextPool;
|
||||
var context = flowContextPool.Allocate();
|
||||
checkpoints["准备调用环境"] = sw.Elapsed;
|
||||
var flowResult = await nodeModel.StartFlowAsync(context, flowTaskManagement.WorkOptions.CancellationTokenSource.Token); // 开始运行时从选定节点开始运行
|
||||
var flowResult = await nodeModel.StartFlowAsync(context, flowWorkManagement.WorkOptions.CancellationTokenSource.Token); // 开始运行时从选定节点开始运行
|
||||
checkpoints["调用节点流程"] = sw.Elapsed;
|
||||
|
||||
var last = TimeSpan.Zero;
|
||||
@@ -241,7 +259,7 @@ namespace Serein.NodeFlow.Env
|
||||
}
|
||||
context.Reset();
|
||||
flowContextPool.Free(context);
|
||||
flowTaskManagementPool.Free(flowTaskManagement);
|
||||
ReturnFWM(flowWorkManagement); // 释放流程任务管理器
|
||||
if (flowResult.Value is TResult result)
|
||||
{
|
||||
return result;
|
||||
@@ -256,32 +274,24 @@ namespace Serein.NodeFlow.Env
|
||||
}
|
||||
}
|
||||
|
||||
/*/// <summary>
|
||||
/// 单独运行一个节点
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<object> InvokeNodeAsync(IDynamicContext context, string nodeGuid)
|
||||
{
|
||||
object result = Unit.Default;
|
||||
if (this.NodeModels.TryGetValue(nodeGuid, out var model))
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
result = await model.ExecutingAsync(context, cts.Token);
|
||||
cts?.Cancel();
|
||||
}
|
||||
return result;
|
||||
}*/
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<bool> ExitFlowAsync()
|
||||
{
|
||||
flowWorkManagement?.Exit();
|
||||
foreach(var flowWorkManagement in flowWorkManagements)
|
||||
{
|
||||
flowWorkManagement.Exit();
|
||||
}
|
||||
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnFlowRunComplete(new FlowEventArgs()));
|
||||
IOC.Reset();
|
||||
flowWorkManagement = null;
|
||||
GC.Collect();
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ActivateFlipflopNode(string nodeGuid)
|
||||
{
|
||||
@@ -313,6 +323,7 @@ namespace Serein.NodeFlow.Env
|
||||
flowTaskManagement.TerminateGlobalFlipflopRuning(flipflopNode);
|
||||
}*/
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void UseExternalIOC(ISereinIOC ioc, Action<ISereinIOC>? setDefultMemberOnReset = null)
|
||||
{
|
||||
@@ -320,11 +331,13 @@ namespace Serein.NodeFlow.Env
|
||||
this.setDefultMemberOnReset = setDefultMemberOnReset;
|
||||
IsUseExternalIOC = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType)
|
||||
{
|
||||
flowEnvironmentEvent.OnMonitorObjectChanged(new MonitorObjectEventArgs(nodeGuid, monitorData, sourceType));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type)
|
||||
{
|
||||
|
||||
@@ -242,7 +242,7 @@ namespace Serein.NodeFlow.Env
|
||||
/// <param name="message">日志内容</param>
|
||||
/// <param name="type">日志类别</param>
|
||||
/// <param name="class">日志级别</param>
|
||||
public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial)
|
||||
public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.General)
|
||||
{
|
||||
currentFlowEnvironment.WriteLine(type, message, @class);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Serein.NodeFlow.Env
|
||||
/// <summary>
|
||||
/// 信息输出等级
|
||||
/// </summary>
|
||||
public InfoClass InfoClass { get; set; } = InfoClass.Trivial;
|
||||
public InfoClass InfoClass { get; set; } = InfoClass.Debug;
|
||||
|
||||
/// <summary>
|
||||
/// 如果没有全局触发器,且没有循环分支,流程执行完成后自动为 Completion 。
|
||||
@@ -211,7 +211,7 @@ namespace Serein.NodeFlow.Env
|
||||
/// <param name="message">日志内容</param>
|
||||
/// <param name="type">日志类别</param>
|
||||
/// <param name="class">日志级别</param>
|
||||
public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial)
|
||||
public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.General)
|
||||
{
|
||||
if (@class >= this.InfoClass)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Serein.NodeFlow
|
||||
/// <summary>
|
||||
/// 是否异步启动流程
|
||||
/// </summary>
|
||||
public bool IsTaskAsync { get; set; }
|
||||
public bool IsWaitStartFlow { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 流程起始节点
|
||||
@@ -28,7 +28,7 @@ namespace Serein.NodeFlow
|
||||
/// <summary>
|
||||
/// 节点任务执行依赖
|
||||
/// </summary>
|
||||
public class FlowWorkOptions()
|
||||
public sealed class FlowWorkOptions()
|
||||
{
|
||||
/// <summary>
|
||||
/// 流程IOC容器
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Serein.NodeFlow.Model.Nodes
|
||||
if (token.IsCancellationRequested) { return null; }
|
||||
}
|
||||
|
||||
MethodDetails? md = MethodDetails;
|
||||
MethodDetails md = MethodDetails;
|
||||
if (md is null)
|
||||
{
|
||||
throw new Exception($"节点{Guid}不存在方法信息,请检查是否需要重写节点的ExecutingAsync");
|
||||
@@ -71,7 +71,7 @@ namespace Serein.NodeFlow.Model.Nodes
|
||||
|
||||
if (md.IsStatic)
|
||||
{
|
||||
object[] args = await this.GetParametersAsync(context, token);
|
||||
object[] args = md.ParameterDetailss.Length == 0 ? [] : await this.GetParametersAsync(context, token);
|
||||
var result = await dd.InvokeAsync(null, args);
|
||||
var flowReslt = FlowResult.OK(this.Guid, context, result);
|
||||
return flowReslt;
|
||||
|
||||
@@ -5,10 +5,22 @@ using System;
|
||||
|
||||
namespace Serein.NodeFlow.Model.Nodes
|
||||
{
|
||||
[FlowDataProperty(ValuePath = NodeValuePath.Node, IsNodeImp = true)]
|
||||
public partial class SingleFlipflopNode
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>是否等待后继节点(仅对于全局触发器)</para>
|
||||
/// <para>如果为 true,则在触发器获取结果后,等待后继节点执行完成,才会调用触发器</para>
|
||||
/// <para>如果为 false,则触发器获取到结果后,将使用 _ = Task.Run(...) 再次调用触发器</para>
|
||||
/// </summary>
|
||||
|
||||
private bool _isWaitSuccessorNodes = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发器节点
|
||||
/// </summary>
|
||||
public class SingleFlipflopNode : NodeModelBase
|
||||
public partial class SingleFlipflopNode : NodeModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的单触发器节点实例。
|
||||
@@ -29,46 +41,52 @@ namespace Serein.NodeFlow.Model.Nodes
|
||||
/// <exception cref="Exception"></exception>
|
||||
public override async Task<FlowResult> ExecutingAsync(IFlowContext context, CancellationToken token)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return FlowResult.Fail(Guid, context, "流程操作已取消");
|
||||
}
|
||||
|
||||
#region 执行前中断
|
||||
if (DebugSetting.IsInterrupt) // 执行触发前
|
||||
{
|
||||
string guid = Guid.ToString();
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"[{MethodDetails.MethodName}]进入中断");
|
||||
await DebugSetting.GetInterruptTask.Invoke();
|
||||
await Console.Out.WriteLineAsync($"[{MethodDetails.MethodName}]中断已取消,开始执行后继分支");
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"[{MethodDetails.MethodName}]中断已取消,开始执行后继分支");
|
||||
}
|
||||
#endregion
|
||||
|
||||
MethodDetails md = MethodDetails;
|
||||
if (!context.Env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行到某个节点
|
||||
{
|
||||
throw new Exception("不存在对应委托");
|
||||
context.Exit();
|
||||
context.ExceptionOfRuning = new FlipflopException($"无法获取到委托 {md.MethodName} 的详细信息。请检查流程配置。");
|
||||
return FlowResult.Fail(Guid, context, "不存在对应委托");
|
||||
}
|
||||
|
||||
var instance = Env.FlowControl.IOC.Get(md.ActingInstanceType);
|
||||
|
||||
var ioc = Env.FlowControl.IOC;
|
||||
var instance = ioc.Get(md.ActingInstanceType);
|
||||
if (instance is null)
|
||||
{
|
||||
Env.FlowControl.IOC.Register(md.ActingInstanceType).Build();
|
||||
instance = Env.FlowControl.IOC.Get(md.ActingInstanceType);
|
||||
ioc.Register(md.ActingInstanceType).Build();
|
||||
instance = ioc.Get(md.ActingInstanceType);
|
||||
}
|
||||
await dd.InvokeAsync(instance, [context]);
|
||||
var args = await this.GetParametersAsync(context, token);
|
||||
|
||||
var args = MethodDetails.ParameterDetailss.Length == 0 ? [] : await this.GetParametersAsync(context, token);
|
||||
|
||||
// 因为这里会返回不确定的泛型 IFlipflopContext<TRsult>
|
||||
// 而我们只需要获取到 State 和 Value(返回的数据)
|
||||
// 所以使用 dynamic 类型接收
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
dynamic dynamicFlipflopContext = await dd.InvokeAsync(instance, args);
|
||||
FlipflopStateType flipflopStateType = dynamicFlipflopContext.State;
|
||||
dynamic flipflopContext = await dd.InvokeAsync(instance, args);
|
||||
FlipflopStateType flipflopStateType = flipflopContext.State;
|
||||
context.NextOrientation = flipflopStateType.ToContentType();
|
||||
|
||||
|
||||
if (dynamicFlipflopContext.Type == TriggerDescription.Overtime)
|
||||
if (flipflopContext.Type == TriggerDescription.Overtime)
|
||||
{
|
||||
throw new FlipflopException(MethodDetails.MethodName + "触发器超时触发。Guid" + Guid);
|
||||
}
|
||||
object result = dynamicFlipflopContext.Value;
|
||||
object result = flipflopContext.Value;
|
||||
var flowReslt = FlowResult.OK(this.Guid, context, result);
|
||||
return flowReslt;
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ namespace Serein.NodeFlow.Services
|
||||
/// <summary>
|
||||
/// 触发器对应的Cts
|
||||
/// </summary>
|
||||
private ConcurrentDictionary<SingleFlipflopNode, CancellationTokenSource> dictGlobalFlipflop = [];
|
||||
private ConcurrentDictionary<SingleFlipflopNode, CancellationTokenSource> _globalFlipflops = [];
|
||||
|
||||
/// <summary>
|
||||
/// 结束运行时需要执行的方法
|
||||
/// </summary>
|
||||
private Func<Task>? ExitAction { get; set; }
|
||||
private Func<Task>? _exitAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化选项
|
||||
@@ -51,60 +51,72 @@ namespace Serein.NodeFlow.Services
|
||||
/// <returns></returns>
|
||||
public async Task<bool> RunAsync(CancellationToken token)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var checkpoints = new Dictionary<string, TimeSpan>();
|
||||
|
||||
#region 注册所有节点所属的类的类型,如果注册失败则退出
|
||||
List<IFlowNode> nodes = new List<IFlowNode>();
|
||||
foreach (var item in WorkOptions.Flows.Values)
|
||||
var flowTask = WorkOptions.Flows.Values.ToArray();
|
||||
foreach (var item in flowTask)
|
||||
{
|
||||
var temp = item.GetNodes();
|
||||
var temp = item?.GetNodes?.Invoke() ;
|
||||
if (temp is null)
|
||||
continue;
|
||||
nodes.AddRange(temp);
|
||||
}
|
||||
if (!RegisterAllType(nodes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
checkpoints["注册所有节点类型"] = sw.Elapsed; // 记录注册所有节点类型的时间
|
||||
#endregion
|
||||
|
||||
#region 调用所有流程类的Init、Load事件
|
||||
|
||||
var initState = await TryInit();
|
||||
if (!initState)
|
||||
{
|
||||
if (!initState)
|
||||
return false;
|
||||
}
|
||||
;
|
||||
checkpoints["调用Init事件"] = sw.Elapsed; // 记录调用Init事件的时间
|
||||
var loadState = await TryLoadAsync();
|
||||
if (!loadState)
|
||||
{
|
||||
if (!loadState)
|
||||
return false;
|
||||
}
|
||||
;
|
||||
checkpoints["调用Load事件"] = sw.Elapsed; // 记录调用Load事件的时间
|
||||
#endregion
|
||||
var last = TimeSpan.Zero;
|
||||
foreach (var kv in checkpoints)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.INFO, $"{kv.Key} 耗时: {(kv.Value - last).TotalMilliseconds} ms");
|
||||
last = kv.Value;
|
||||
}
|
||||
|
||||
// 开始调用流程
|
||||
foreach (var kvp in WorkOptions.Flows)
|
||||
{
|
||||
var guid = kvp.Key;
|
||||
var flow = kvp.Value;
|
||||
var flowNodes = flow.GetNodes();
|
||||
|
||||
var flowNodes = flow.GetNodes?.Invoke();
|
||||
if (flowNodes is null)
|
||||
continue;
|
||||
IFlowNode? startNode = flow.GetStartNode?.Invoke();
|
||||
// 找到流程的起始节点,开始运行
|
||||
IFlowNode startNode = flow.GetStartNode();
|
||||
if (startNode is null)
|
||||
continue;
|
||||
// 是否后台运行当前画布流程
|
||||
if (flow.IsTaskAsync)
|
||||
if (flow.IsWaitStartFlow)
|
||||
{
|
||||
_ = Task.Run(async () => await CallStartNode(startNode), token); // 后台调用流程中的触发器
|
||||
|
||||
_ = Task.Run(async () => await CallNode(startNode), token); // 后台调用流程中的触发器
|
||||
}
|
||||
else
|
||||
{
|
||||
await CallStartNode(startNode);
|
||||
await CallNode(startNode);
|
||||
|
||||
}
|
||||
_ = Task.Run(async () => await CallFlipflopNode(flow), token); // 后台调用流程中的触发器
|
||||
await CallFlipflopNode(flow); // 后台调用流程中的触发器
|
||||
}
|
||||
|
||||
// 等待流程运行完成
|
||||
await CallExit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -153,6 +165,11 @@ namespace Serein.NodeFlow.Services
|
||||
return isSuccessful;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试初始化
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private async Task<bool> TryInit()
|
||||
{
|
||||
var env = WorkOptions.Environment;
|
||||
@@ -175,6 +192,12 @@ namespace Serein.NodeFlow.Services
|
||||
var isSuccessful = true;
|
||||
return isSuccessful;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试加载流程
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private async Task<bool> TryLoadAsync()
|
||||
{
|
||||
var env = WorkOptions.Environment;
|
||||
@@ -198,6 +221,12 @@ namespace Serein.NodeFlow.Services
|
||||
return isSuccessful;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束流程时调用的方法
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private async Task<bool> CallExit()
|
||||
{
|
||||
var env = WorkOptions.Environment;
|
||||
@@ -205,8 +234,6 @@ namespace Serein.NodeFlow.Services
|
||||
var pool = WorkOptions.FlowContextPool;
|
||||
var ioc = WorkOptions.FlowIOC;
|
||||
|
||||
// var fit = ioc.Get<FlowInterruptTool>();
|
||||
// fit.CancelAllTrigger(); // 取消所有中断
|
||||
foreach (var md in mds) // 结束时
|
||||
{
|
||||
if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行初始化
|
||||
@@ -228,39 +255,44 @@ namespace Serein.NodeFlow.Services
|
||||
return isSuccessful;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用流程中的触发器节点
|
||||
/// </summary>
|
||||
/// <param name="flow"></param>
|
||||
/// <returns></returns>
|
||||
private async Task CallFlipflopNode(FlowTask flow)
|
||||
{
|
||||
var env = WorkOptions.Environment;
|
||||
var flipflopNodes = flow.GetNodes().Where(item => item is SingleFlipflopNode node
|
||||
|
||||
var nodes = flow.GetNodes?.Invoke();
|
||||
if (nodes is null)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.WARN, "流程中没有触发器节点可供执行");
|
||||
return;
|
||||
}
|
||||
var flipflopNodes = nodes.Where(item => item is SingleFlipflopNode node
|
||||
&& node.DebugSetting.IsEnable
|
||||
&& node.NotExitPreviousNode())
|
||||
.Select(item => (SingleFlipflopNode)item);
|
||||
//.ToList();// 获取需要再运行开始之前启动的触发器节点
|
||||
|
||||
if (flipflopNodes.Count() > 0)
|
||||
{
|
||||
var tasks = flipflopNodes.Select(async node =>
|
||||
{
|
||||
await RunGlobalFlipflopAsync(env, node); // 启动流程时启动全局触发器
|
||||
});
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
.OfType<SingleFlipflopNode>()
|
||||
.Select(async node =>
|
||||
{
|
||||
await RunGlobalFlipflopAsync(env, node); // 启动流程时启动全局触发器
|
||||
});
|
||||
var tasks = flipflopNodes.ToArray();
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从某一个节点开始执行
|
||||
/// 从某个节点开始执行
|
||||
/// </summary>
|
||||
/// <param name="startNode"></param>
|
||||
/// <returns></returns>
|
||||
private async Task CallStartNode(IFlowNode startNode)
|
||||
private async Task CallNode(IFlowNode startNode)
|
||||
{
|
||||
var pool = WorkOptions.FlowContextPool;
|
||||
var token = WorkOptions.CancellationTokenSource.Token;
|
||||
var context = pool.Allocate();
|
||||
context.Reset();
|
||||
await startNode.StartFlowAsync(context, token);
|
||||
context.Exit();
|
||||
context.Reset();
|
||||
pool.Free(context);
|
||||
return;
|
||||
}
|
||||
@@ -287,8 +319,6 @@ namespace Serein.NodeFlow.Services
|
||||
checkpoints["执行流程"] = sw.Elapsed;
|
||||
|
||||
context.Reset();
|
||||
checkpoints["重置流程"] = sw.Elapsed;
|
||||
|
||||
pool.Free(context);
|
||||
checkpoints["释放Context"] = sw.Elapsed;
|
||||
|
||||
@@ -307,15 +337,15 @@ namespace Serein.NodeFlow.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试添加全局触发器
|
||||
/// 运行全局触发器
|
||||
/// </summary>
|
||||
/// <param name="singleFlipFlopNode"></param>
|
||||
/// <param name="env"></param>
|
||||
public async Task RunGlobalFlipflopAsync(IFlowEnvironment env, SingleFlipflopNode singleFlipFlopNode)
|
||||
{
|
||||
if (dictGlobalFlipflop.TryAdd(singleFlipFlopNode, new CancellationTokenSource()))
|
||||
using var cts = new CancellationTokenSource();
|
||||
if (_globalFlipflops.TryAdd(singleFlipFlopNode, cts))
|
||||
{
|
||||
var cts = dictGlobalFlipflop[singleFlipFlopNode];
|
||||
await FlipflopExecuteAsync(singleFlipFlopNode, cts.Token);
|
||||
}
|
||||
}
|
||||
@@ -326,7 +356,7 @@ namespace Serein.NodeFlow.Services
|
||||
/// <param name="singleFlipFlopNode"></param>
|
||||
public void TerminateGlobalFlipflopRuning(SingleFlipflopNode singleFlipFlopNode)
|
||||
{
|
||||
if (dictGlobalFlipflop.TryRemove(singleFlipFlopNode, out var cts))
|
||||
if (_globalFlipflops.TryRemove(singleFlipFlopNode, out var cts))
|
||||
{
|
||||
if (!cts.IsCancellationRequested)
|
||||
{
|
||||
@@ -341,7 +371,7 @@ namespace Serein.NodeFlow.Services
|
||||
/// </summary>
|
||||
private void TerminateAllGlobalFlipflop()
|
||||
{
|
||||
foreach ((var node, var cts) in dictGlobalFlipflop)
|
||||
foreach ((var node, var cts) in _globalFlipflops)
|
||||
{
|
||||
if (!cts.IsCancellationRequested)
|
||||
{
|
||||
@@ -349,37 +379,50 @@ namespace Serein.NodeFlow.Services
|
||||
}
|
||||
cts.Dispose();
|
||||
}
|
||||
dictGlobalFlipflop.Clear();
|
||||
_globalFlipflops.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动全局触发器
|
||||
/// </summary>
|
||||
/// <param name="singleFlipFlopNode">需要全局监听信号的触发器</param>
|
||||
/// <param name="singleToken">单个触发器持有的</param>
|
||||
/// <param name="flipflopNode">需要全局监听信号的触发器</param>
|
||||
/// <param name="token">单个触发器持有的</param>
|
||||
/// <returns></returns>
|
||||
private async Task FlipflopExecuteAsync(SingleFlipflopNode singleFlipFlopNode,
|
||||
CancellationToken singleToken)
|
||||
private async Task FlipflopExecuteAsync(SingleFlipflopNode flipflopNode,
|
||||
CancellationToken token)
|
||||
{
|
||||
|
||||
var pool = WorkOptions.FlowContextPool;
|
||||
while (!singleToken.IsCancellationRequested && !singleToken.IsCancellationRequested)
|
||||
while (true)
|
||||
{
|
||||
if(token.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var context = pool.Allocate(); // 从上下文池取出新的实例
|
||||
try
|
||||
{
|
||||
var context = pool.Allocate(); // 启动全局触发器时新建上下文
|
||||
var newFlowData = await singleFlipFlopNode.ExecutingAsync(context, singleToken); // 获取触发器等待Task
|
||||
context.AddOrUpdateFlowData(singleFlipFlopNode.Guid, newFlowData);
|
||||
var result = await flipflopNode.ExecutingAsync(context, token); // 等待触发获取结果
|
||||
context.AddOrUpdateFlowData(flipflopNode.Guid, result);
|
||||
if (context.NextOrientation == ConnectionInvokeType.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_ = Task.Run(() => CallSubsequentNode(singleFlipFlopNode, singleToken, pool, context)); // 重新启动触发器
|
||||
await CallSuccessorNodesAsync(flipflopNode, token, pool, context);
|
||||
/*if (flipflopNode.IsWaitSuccessorNodes)
|
||||
{
|
||||
_ = Task.Run(async () => await CallSuccessorNodesAsync(flipflopNode, token, pool, context));
|
||||
}
|
||||
else
|
||||
{
|
||||
await CallSuccessorNodesAsync(flipflopNode, token, pool, context);
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
catch (FlipflopException ex)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{singleFlipFlopNode.MethodDetails.MethodName}]因非预期异常终止。"+ex.Message);
|
||||
SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{flipflopNode.MethodDetails.MethodName}]因非预期异常终止。"+ex.Message);
|
||||
if (ex.Type == FlipflopException.CancelClass.CancelFlow)
|
||||
{
|
||||
break;
|
||||
@@ -387,9 +430,15 @@ namespace Serein.NodeFlow.Services
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{singleFlipFlopNode.Guid}]异常。"+ ex.Message);
|
||||
SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{flipflopNode.Guid}]异常。"+ ex.Message);
|
||||
await Task.Delay(100);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
context.Reset();
|
||||
pool.Free(context);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -402,7 +451,7 @@ namespace Serein.NodeFlow.Services
|
||||
/// <param name="pool"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task? CallSubsequentNode(SingleFlipflopNode singleFlipFlopNode, CancellationToken singleToken, ObjectPool<IFlowContext> pool, IFlowContext context)
|
||||
private static async Task CallSuccessorNodesAsync(SingleFlipflopNode singleFlipFlopNode, CancellationToken singleToken, ObjectPool<IFlowContext> pool, IFlowContext context)
|
||||
{
|
||||
var flowState = context.NextOrientation; // 记录一下流程状态
|
||||
var nextNodes = singleFlipFlopNode.SuccessorNodes[ConnectionInvokeType.Upstream]; // 优先调用上游分支
|
||||
@@ -441,8 +490,6 @@ namespace Serein.NodeFlow.Services
|
||||
await nextNodes[i].StartFlowAsync(context, singleToken); // 启动执行触发器后继分支的节点
|
||||
}
|
||||
|
||||
context.Reset();
|
||||
pool.Free(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -450,7 +497,7 @@ namespace Serein.NodeFlow.Services
|
||||
/// </summary>
|
||||
public void Exit()
|
||||
{
|
||||
ExitAction?.Invoke();
|
||||
_exitAction?.Invoke();
|
||||
|
||||
}
|
||||
|
||||
121
Serein.Extend.NewtonsoftJson/NewtonsoftJsonArrayToken.cs
Normal file
121
Serein.Extend.NewtonsoftJson/NewtonsoftJsonArrayToken.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Extend.NewtonsoftJson
|
||||
{
|
||||
public sealed class NewtonsoftJsonArrayToken : IJsonToken, IList<IJsonToken>
|
||||
{
|
||||
private readonly JArray _array;
|
||||
public NewtonsoftJsonArrayToken(JArray array) => _array = array;
|
||||
|
||||
public bool IsNull => false;
|
||||
public bool IsObject => false;
|
||||
public bool IsArray => true;
|
||||
|
||||
public int Count => _array.Count;
|
||||
|
||||
public bool IsReadOnly => _array.IsReadOnly;
|
||||
|
||||
|
||||
public string GetString() => _array.ToString();
|
||||
public int GetInt32() => throw new InvalidOperationException("不是值类型");
|
||||
public bool GetBoolean() => throw new InvalidOperationException("不是值类型");
|
||||
|
||||
public IJsonToken this[object key] => key is int index ? this[index] : throw new InvalidOperationException("不是对象类型");
|
||||
|
||||
public IJsonToken this[int index]
|
||||
{
|
||||
get => NewtonsoftJsonTokenFactory.FromJToken(_array[index]);
|
||||
set => _array[index] = JToken.FromObject(value.ToObject<object>());
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<IJsonToken> EnumerateArray()
|
||||
{
|
||||
foreach (var t in _array)
|
||||
yield return NewtonsoftJsonTokenFactory.FromJToken(t);
|
||||
}
|
||||
|
||||
public T ToObject<T>() => throw new InvalidOperationException("不是对象类型");
|
||||
public object ToObject(Type type) => throw new InvalidOperationException("不是对象类型");
|
||||
public override string ToString() => _array.ToString();
|
||||
|
||||
public bool TryGetValue(string name, out IJsonToken token) => throw new InvalidOperationException("不是对象类型");
|
||||
public IJsonToken? GetValue(string name) => throw new InvalidOperationException("不是对象类型");
|
||||
|
||||
public int IndexOf(IJsonToken item)
|
||||
{
|
||||
var jt = JToken.FromObject(item.ToObject<object>());
|
||||
for (int i = 0; i < _array.Count; i++)
|
||||
{
|
||||
if (JToken.DeepEquals(_array[i], jt))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
public void Insert(int index, IJsonToken item)
|
||||
{
|
||||
_array.Insert(index, JToken.FromObject(item.ToObject<object>()));
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
_array.RemoveAt(index);
|
||||
}
|
||||
|
||||
public void Add(IJsonToken item)
|
||||
{
|
||||
_array.Add(JToken.FromObject(item.ToObject<object>()));
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_array.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(IJsonToken item)
|
||||
{
|
||||
var jt = JToken.FromObject(item.ToObject<object>());
|
||||
return _array.Any(x => JToken.DeepEquals(x, jt));
|
||||
}
|
||||
|
||||
public void CopyTo(IJsonToken[] array, int arrayIndex)
|
||||
{
|
||||
foreach (var item in _array)
|
||||
{
|
||||
array[arrayIndex++] = NewtonsoftJsonTokenFactory.FromJToken(item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(IJsonToken item)
|
||||
{
|
||||
int index = IndexOf(item);
|
||||
if (index >= 0)
|
||||
{
|
||||
_array.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<IJsonToken> GetEnumerator()
|
||||
{
|
||||
foreach (var item in _array)
|
||||
yield return NewtonsoftJsonTokenFactory.FromJToken(item);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
134
Serein.Extend.NewtonsoftJson/NewtonsoftJsonObjectToken.cs
Normal file
134
Serein.Extend.NewtonsoftJson/NewtonsoftJsonObjectToken.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Utils;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Serein.Extend.NewtonsoftJson
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于Newtonsoft.Json的IJsonToken实现
|
||||
/// </summary>
|
||||
public sealed class NewtonsoftJsonObjectToken : IJsonToken, IDictionary<string, IJsonToken>
|
||||
{
|
||||
private readonly JObject _object;
|
||||
public NewtonsoftJsonObjectToken(JObject obj) => _object = obj;
|
||||
|
||||
public bool IsNull => false;
|
||||
public bool IsObject => true;
|
||||
public bool IsArray => false;
|
||||
|
||||
public string GetString() => _object.ToString();
|
||||
public int GetInt32() => throw new InvalidOperationException("不是值类型");
|
||||
public bool GetBoolean() => throw new InvalidOperationException("不是值类型");
|
||||
|
||||
public IJsonToken this[object key] => key is string name ? this[name] : throw new InvalidOperationException("不是数组类型");
|
||||
|
||||
public IJsonToken this[string key]
|
||||
{
|
||||
get => _object.TryGetValue(key, out var value) ? NewtonsoftJsonTokenFactory.FromJToken(value) : throw new KeyNotFoundException(key);
|
||||
set => _object[key] = JToken.FromObject(value.ToObject<object>());
|
||||
}
|
||||
|
||||
public IEnumerable<IJsonToken> EnumerateArray() => throw new InvalidOperationException("不是数组类型");
|
||||
|
||||
public T ToObject<T>() => _object.ToObject<T>();
|
||||
public object ToObject(Type type) => _object.ToObject(type);
|
||||
public override string ToString() => _object.ToString();
|
||||
public bool TryGetValue(string name, [NotNullWhen(true)] out IJsonToken? token)
|
||||
{
|
||||
if (_object.TryGetValue(name, out JToken? value))
|
||||
{
|
||||
token = NewtonsoftJsonTokenFactory.FromJToken(value);
|
||||
return true;
|
||||
}
|
||||
token = null;
|
||||
return false;
|
||||
}
|
||||
public IJsonToken? GetValue(string name)
|
||||
{
|
||||
if (_object.TryGetValue(name, out JToken? value))
|
||||
{
|
||||
var token = NewtonsoftJsonTokenFactory.FromJToken(value);
|
||||
return token;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#region IDictionary<string, IJsonToken> 接口实现
|
||||
|
||||
public ICollection<string> Keys => _object.Properties().Select(p => p.Name).ToList();
|
||||
|
||||
public ICollection<IJsonToken> Values => _object.Properties().Select(p => new NewtonsoftJsonObjectToken(JObject.FromObject(p.Value)) as IJsonToken).ToList();
|
||||
|
||||
public int Count => _object.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(string key, IJsonToken value)
|
||||
{
|
||||
var token = JToken.FromObject(value.ToObject<object>());
|
||||
_object[key] = token;
|
||||
}
|
||||
|
||||
|
||||
public void Add(KeyValuePair<string, IJsonToken> item) => Add(item.Key, item.Value);
|
||||
|
||||
public void Clear() => _object.RemoveAll();
|
||||
|
||||
public bool ContainsKey(string key) => _object.ContainsKey(key);
|
||||
|
||||
|
||||
public void CopyTo(KeyValuePair<string, IJsonToken>[] array, int arrayIndex)
|
||||
{
|
||||
foreach (var prop in _object.Properties())
|
||||
{
|
||||
array[arrayIndex++] = new KeyValuePair<string, IJsonToken>(prop.Name, new NewtonsoftJsonObjectToken(JObject.FromObject(prop.Value)));
|
||||
}
|
||||
}
|
||||
public IEnumerator<KeyValuePair<string, IJsonToken>> GetEnumerator()
|
||||
{
|
||||
foreach (var prop in _object.Properties())
|
||||
{
|
||||
yield return new KeyValuePair<string, IJsonToken>(prop.Name, new NewtonsoftJsonObjectToken(JObject.FromObject(prop.Value)));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(string key) => _object.Remove(key);
|
||||
|
||||
|
||||
public bool Remove(KeyValuePair<string, IJsonToken> item)
|
||||
{
|
||||
if (_object.TryGetValue(item.Key, out var token))
|
||||
{
|
||||
var existing = new NewtonsoftJsonObjectToken(JObject.FromObject(token));
|
||||
if (existing.Equals(item.Value))
|
||||
{
|
||||
return _object.Remove(item.Key);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, IJsonToken> item)
|
||||
{
|
||||
if (_object.TryGetValue(item.Key, out var token))
|
||||
{
|
||||
var value = new NewtonsoftJsonObjectToken(JObject.FromObject(token));
|
||||
return value.Equals(item.Value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -95,19 +95,18 @@ namespace Serein.Extend.NewtonsoftJson
|
||||
|
||||
public IJsonToken Parse(string json)
|
||||
{
|
||||
var token = JToken.Parse(json);
|
||||
return new NewtonsoftJsonToken(token);
|
||||
return NewtonsoftJsonTokenFactory.Parse(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的JSON对象。
|
||||
/// 创建一个新的JSON数组对象。
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <returns></returns>
|
||||
public IJsonToken CreateObject(IDictionary<string, object>? values = null)
|
||||
{
|
||||
var jobj = values != null ? JObject.FromObject(values) : new JObject();
|
||||
return new NewtonsoftJsonToken(jobj);
|
||||
return new NewtonsoftJsonObjectToken(jobj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,7 +118,7 @@ namespace Serein.Extend.NewtonsoftJson
|
||||
public IJsonToken CreateArray(IEnumerable<object>? values = null)
|
||||
{
|
||||
var jarr = values != null ? JArray.FromObject(values) : new JArray();
|
||||
return new NewtonsoftJsonToken(jarr);
|
||||
return new NewtonsoftJsonArrayToken(jarr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,8 +128,8 @@ namespace Serein.Extend.NewtonsoftJson
|
||||
/// <returns></returns>
|
||||
public IJsonToken FromObject(object obj)
|
||||
{
|
||||
var token = JToken.FromObject(obj);
|
||||
return new NewtonsoftJsonToken(token);
|
||||
var token = JObject.FromObject(obj);
|
||||
return new NewtonsoftJsonObjectToken(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Serein.Extend.NewtonsoftJson
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于Newtonsoft.Json的IJsonToken实现
|
||||
/// </summary>
|
||||
public sealed class NewtonsoftJsonToken : IJsonToken
|
||||
{
|
||||
private readonly JToken _token;
|
||||
|
||||
/// <summary>
|
||||
/// 使用JToken初始化一个新的NewtonsoftJsonToken实例。
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public NewtonsoftJsonToken(JToken token)
|
||||
{
|
||||
_token = token ?? throw new ArgumentNullException(nameof(token));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取指定名称的属性值。
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetValue(string name, [NotNullWhen(true)] out IJsonToken? token)
|
||||
{
|
||||
if (_token is JObject obj && obj.TryGetValue(name, out JToken? value))
|
||||
{
|
||||
token = new NewtonsoftJsonToken(value);
|
||||
return true;
|
||||
}
|
||||
token = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public IJsonToken? GetValue(string name)
|
||||
{
|
||||
if (_token is JObject obj && obj.TryGetValue(name, out JToken? value))
|
||||
{
|
||||
return new NewtonsoftJsonToken(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetString() => (_token.Type == JTokenType.Null ? null : _token.ToString()) ?? string.Empty;
|
||||
|
||||
public int GetInt32() => _token.Value<int>();
|
||||
|
||||
public bool GetBoolean() => _token.Value<bool>();
|
||||
|
||||
public bool IsNull => _token.Type == JTokenType.Null || _token.Type == JTokenType.Undefined;
|
||||
|
||||
public IEnumerable<IJsonToken> EnumerateArray()
|
||||
{
|
||||
if (_token is JArray arr)
|
||||
return arr.Select(x => new NewtonsoftJsonToken(x));
|
||||
throw new InvalidOperationException("当前Token不是数组类型。");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前JSON Token转换为指定类型的对象。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
#pragma warning disable CS8603 // 可能返回 null 引用。
|
||||
public T ToObject<T>() => _token.ToObject<T>();
|
||||
#pragma warning restore CS8603 // 可能返回 null 引用。
|
||||
|
||||
/// <summary>
|
||||
/// 将当前JSON Token转换为指定类型的对象。
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
#pragma warning disable CS8603 // 可能返回 null 引用。
|
||||
public object ToObject(Type type) => _token.ToObject(type);
|
||||
#pragma warning restore CS8603 // 可能返回 null 引用。
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前JSON Token的字符串表示形式。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString() => _token.ToString();
|
||||
}
|
||||
}
|
||||
30
Serein.Extend.NewtonsoftJson/NewtonsoftJsonTokenFactory.cs
Normal file
30
Serein.Extend.NewtonsoftJson/NewtonsoftJsonTokenFactory.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Extend.NewtonsoftJson
|
||||
{
|
||||
public static class NewtonsoftJsonTokenFactory
|
||||
{
|
||||
public static IJsonToken Parse(string json)
|
||||
{
|
||||
var jt = JToken.Parse(json);
|
||||
return FromJToken(jt);
|
||||
}
|
||||
|
||||
public static IJsonToken FromJToken(JToken token)
|
||||
{
|
||||
return token.Type switch
|
||||
{
|
||||
JTokenType.Object => new NewtonsoftJsonObjectToken((JObject)token),
|
||||
JTokenType.Array => new NewtonsoftJsonArrayToken((JArray)token),
|
||||
_ => new NewtonsoftJsonValueToken(token)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
46
Serein.Extend.NewtonsoftJson/NewtonsoftJsonValueToken.cs
Normal file
46
Serein.Extend.NewtonsoftJson/NewtonsoftJsonValueToken.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Extend.NewtonsoftJson
|
||||
{
|
||||
public sealed class NewtonsoftJsonValueToken : IJsonToken
|
||||
{
|
||||
private readonly JToken _token;
|
||||
|
||||
public NewtonsoftJsonValueToken(JToken token)
|
||||
{
|
||||
_token = token ?? throw new ArgumentNullException(nameof(token));
|
||||
}
|
||||
|
||||
public bool IsNull => _token.Type == JTokenType.Null || _token.Type == JTokenType.Undefined;
|
||||
public bool IsObject => false;
|
||||
public bool IsArray => false;
|
||||
|
||||
|
||||
|
||||
public string GetString() => _token.Type == JTokenType.Null ? string.Empty : _token.ToString();
|
||||
public int GetInt32() => _token.Value<int>();
|
||||
public bool GetBoolean() => _token.Value<bool>();
|
||||
|
||||
public IJsonToken this[object key] => throw new InvalidOperationException("不是对象/数组类型");
|
||||
public IEnumerable<IJsonToken> EnumerateArray() => throw new InvalidOperationException("不是数组类型");
|
||||
|
||||
public T ToObject<T>() => _token.ToObject<T>();
|
||||
public object ToObject(Type type) => _token.ToObject(type);
|
||||
public override string ToString() => _token.ToString();
|
||||
|
||||
public bool TryGetValue(string name, out IJsonToken token) => throw new InvalidOperationException("不是对象类型");
|
||||
|
||||
public IJsonToken? GetValue(string name) => throw new InvalidOperationException("不是对象类型");
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ namespace Serein.Library.NodeGenerator
|
||||
/// <param name="context">增量生成器的上下文,用于注册生成逻辑。</param>
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
|
||||
/*
|
||||
* //Debugger.Launch();
|
||||
CreateSyntaxProvider : 第一个参数用于筛选特定语法节点,第二个参数则用于转换筛选出来的节点。
|
||||
|
||||
@@ -42,6 +42,10 @@ namespace Serein.Script.Node
|
||||
case '\\': // 字面量反斜杠
|
||||
output.Append('\\');
|
||||
i++; // 跳过第二个 '\\'
|
||||
break;
|
||||
case '"': // 字符串反斜杠
|
||||
output.Append('"');
|
||||
i++; // 跳过第二个 '"'
|
||||
break;
|
||||
default:
|
||||
output.Append(input[i]); // 不是转义符,保留反斜杠
|
||||
|
||||
@@ -516,7 +516,7 @@ namespace Serein.Script
|
||||
/// <param name="indexValue"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private async Task<object?> GetCollectionValueAsync(ASTNode node, object collectionValue, object indexValue)
|
||||
private async Task<object?> GetCollectionValueAsync(CollectionIndexNode node, object collectionValue, object indexValue)
|
||||
{
|
||||
if (ASTDelegateDetails.TryGetValue(node, out DelegateDetails? delegateDetails))
|
||||
{
|
||||
@@ -530,8 +530,9 @@ namespace Serein.Script
|
||||
{
|
||||
return chars[index];
|
||||
}
|
||||
var itemType = symbolInfos[node.Index];
|
||||
var collectionType = collectionValue.GetType(); // 目标对象的类型
|
||||
delegateDetails = new DelegateDetails(collectionType, DelegateDetails.EmitType.CollectionGetter);
|
||||
delegateDetails = new DelegateDetails(collectionType, DelegateDetails.EmitType.CollectionGetter, itemType);
|
||||
ASTDelegateDetails[node] = delegateDetails; // 缓存委托
|
||||
var result = await delegateDetails.InvokeAsync(collectionValue, [indexValue]);
|
||||
return result;
|
||||
|
||||
@@ -214,6 +214,14 @@
|
||||
// 识别字符串字面量
|
||||
if (currentChar == '"')
|
||||
{
|
||||
if (_input[_index + 1] == '"'
|
||||
&& _input[_index + 2] == '"')
|
||||
{
|
||||
var value = _input.Slice(_index, 4).ToString();
|
||||
|
||||
// 原始字符串
|
||||
return ReadRawString();
|
||||
}
|
||||
return ReadString();
|
||||
}
|
||||
|
||||
@@ -440,6 +448,28 @@
|
||||
return token;
|
||||
}
|
||||
|
||||
private Token ReadRawString()
|
||||
{
|
||||
// skip opening triple quotes
|
||||
_index += 3;
|
||||
|
||||
var start = _index;
|
||||
while (_index + 2 < _input.Length)
|
||||
{
|
||||
if (_input[_index] == '"' && _input[_index + 1] == '"' && _input[_index + 2] == '"')
|
||||
{
|
||||
var value = _input.Slice(start, _index - start).ToString();
|
||||
_index += 3; // skip closing """
|
||||
return CreateToken(TokenType.String, value);
|
||||
}
|
||||
|
||||
_index++;
|
||||
}
|
||||
|
||||
throw new Exception("Unterminated raw string literal");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 读取硬编码的文本
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Serein.Library.Utils;
|
||||
using Serein.Script.Node;
|
||||
using Serein.Script.Node.FlowControl;
|
||||
using System.Collections;
|
||||
|
||||
namespace Serein.Script
|
||||
{
|
||||
@@ -255,6 +256,7 @@ namespace Serein.Script
|
||||
if (JudgmentOperator(_currentToken, "=")) break; // 退出
|
||||
var peekToken = _currentToken; // _lexer.PeekToken(); // 获取下一个token开始判断
|
||||
source = nodes[^1]; // 重定向节点
|
||||
if (peekToken.Type == TokenType.Identifier) throw new Exception($"无法从对象获取成员,当前Token类型为 {peekToken.Type}。");
|
||||
if (peekToken.Type == TokenType.Dot) // 从对象获取
|
||||
{
|
||||
/*
|
||||
@@ -352,27 +354,51 @@ namespace Serein.Script
|
||||
public CollectionIndexNode ParseCollectionIndexNode(ASTNode sourceNode)
|
||||
{
|
||||
var collectionToken = _currentToken;
|
||||
string collectionName = _currentToken.Value; // 集合名称
|
||||
NextToken(TokenType.SquareBracketsLeft); // 消耗集合名称
|
||||
NextToken(); // 消耗 "[" 集合标识符的左中括号
|
||||
ASTNode indexNode = ParserExpression(); // 解析获取索引Node
|
||||
NextToken(); // 消耗 "]" 集合标识符的右中括号
|
||||
|
||||
if(sourceNode is IdentifierNode)
|
||||
if(_currentToken.Type == TokenType.SquareBracketsLeft)
|
||||
{
|
||||
var collectionIndexNode = new CollectionIndexNode(sourceNode, indexNode);
|
||||
collectionIndexNode.SetTokenInfo(collectionToken); // 表示获取集合第几个索引
|
||||
return collectionIndexNode;
|
||||
// 集合中获取集合
|
||||
NextToken(); // 消耗 "[" 集合标识符的左中括号
|
||||
ASTNode indexNode = ParserExpression(); // 解析获取索引Node
|
||||
NextToken(); // 消耗 "]" 集合标识符的右中括号
|
||||
|
||||
if (sourceNode is IdentifierNode)
|
||||
{
|
||||
var collectionIndexNode = new CollectionIndexNode(sourceNode, indexNode);
|
||||
collectionIndexNode.SetTokenInfo(collectionToken); // 表示获取集合第几个索引
|
||||
return collectionIndexNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
var collectionIndexNode = new CollectionIndexNode(sourceNode, indexNode);
|
||||
collectionIndexNode.SetTokenInfo(collectionToken); // 表示获取集合第几个索引
|
||||
return collectionIndexNode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var memberAccessNode = new MemberAccessNode(sourceNode, collectionName).SetTokenInfo(_currentToken); // 表示集合从上一轮获取到的成员获取
|
||||
var collectionIndexNode = new CollectionIndexNode(memberAccessNode, indexNode);
|
||||
collectionIndexNode.SetTokenInfo(collectionToken); // 表示获取集合第几个索引
|
||||
return collectionIndexNode;
|
||||
string collectionName = _currentToken.Value; // 集合名称
|
||||
NextToken(TokenType.SquareBracketsLeft); // 消耗集合名称
|
||||
NextToken(); // 消耗 "[" 集合标识符的左中括号
|
||||
ASTNode indexNode = ParserExpression(); // 解析获取索引Node
|
||||
NextToken(); // 消耗 "]" 集合标识符的右中括号
|
||||
|
||||
if (sourceNode is IdentifierNode)
|
||||
{
|
||||
var collectionIndexNode = new CollectionIndexNode(sourceNode, indexNode);
|
||||
collectionIndexNode.SetTokenInfo(collectionToken); // 表示获取集合第几个索引
|
||||
return collectionIndexNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var memberAccessNode = new MemberAccessNode(sourceNode, collectionName).SetTokenInfo(_currentToken); // 表示集合从上一轮获取到的成员获取
|
||||
var collectionIndexNode = new CollectionIndexNode(memberAccessNode, indexNode);
|
||||
collectionIndexNode.SetTokenInfo(collectionToken); // 表示获取集合第几个索引
|
||||
return collectionIndexNode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -939,10 +965,6 @@ namespace Serein.Script
|
||||
{
|
||||
var peekToken = _currentToken; // _lexer.PeekToken(); // 获取下一个token开始判断
|
||||
source = nodes[^1]; // 重定向节点
|
||||
if(source.StartIndex == 501)
|
||||
{
|
||||
|
||||
}
|
||||
if (peekToken.Type == TokenType.Dot) // 从对象获取
|
||||
{
|
||||
/*
|
||||
|
||||
@@ -674,7 +674,7 @@ namespace Serein.Script
|
||||
/// <param name="indexType">索引</param>
|
||||
/// <param name="resultType">获取到的类型</param>
|
||||
/// <returns></returns>
|
||||
public static bool TryGetIndexerType(Type collectionType, out Type indexType, out Type resultType)
|
||||
public static bool TryGetIndexerType(Type collectionType, out Type indexType, out Type resultType)
|
||||
{
|
||||
indexType = null!;
|
||||
resultType = null!;
|
||||
@@ -724,6 +724,7 @@ namespace Serein.Script
|
||||
|
||||
if (indexer != null)
|
||||
{
|
||||
var @params = indexer.GetIndexParameters();
|
||||
var param = indexer.GetIndexParameters()[0];
|
||||
indexType = param.ParameterType;
|
||||
resultType = indexer.PropertyType;
|
||||
|
||||
@@ -51,7 +51,20 @@ namespace Serein.Workbench
|
||||
|
||||
private void Application_Startup(object sender, StartupEventArgs e)
|
||||
{
|
||||
var projectService = App.GetService<FlowProjectService>();
|
||||
#if DEBUG && true
|
||||
|
||||
try
|
||||
{
|
||||
var t = JsonHelper.Parse(TestJson.json);
|
||||
var iss = t["PreviousNodes"]["IsSucceed"][0];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
var projectService = App.GetService<FlowProjectService>();
|
||||
if (e.Args.Length == 1)
|
||||
{
|
||||
string filePath = e.Args[0];
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Serein.Workbench.Models
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<MethodDetailsInfo> _methodInfo;
|
||||
|
||||
|
||||
public List<MethodDetailsInfo> ActionNodes { get => MethodInfo.Where(x => x.NodeType == NodeType.Action.ToString()).ToList(); set { } }
|
||||
public List<MethodDetailsInfo> FlipflopNodes { get => MethodInfo.Where(x => x.NodeType == NodeType.Flipflop.ToString()).ToList(); set { } }
|
||||
public List<MethodDetailsInfo> UINodes { get => MethodInfo.Where(x => x.NodeType == NodeType.UI.ToString()).ToList(); set { } }
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Serein.Workbench.Node.ViewModel
|
||||
FlowCallNode.ResetTargetNode(); // 如果是不选择了,则重置一下
|
||||
return;
|
||||
}
|
||||
UploadNode.Invoke(value);
|
||||
UploadNode?.Invoke(value);
|
||||
FlowCallNode.SetTargetNode(value.Guid); // 重新设置目标节点
|
||||
}
|
||||
|
||||
|
||||
64
Workbench/TestJson.cs
Normal file
64
Workbench/TestJson.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Workbench
|
||||
{
|
||||
internal class TestJson
|
||||
{
|
||||
public static string json = """
|
||||
{
|
||||
"CanvasGuid": "3d18d198-1ef7-4de5-870c-a072da47c182",
|
||||
"Guid": "abe4ae47-b0e0-4616-afe1-621ec7b15370",
|
||||
"IsPublic": false,
|
||||
"AssemblyName": null,
|
||||
"MethodName": null,
|
||||
"Label": null,
|
||||
"Type": "Script",
|
||||
"PreviousNodes": {
|
||||
"Upstream": [],
|
||||
"IsSucceed": [
|
||||
"24c74a00-974e-48b1-b0dc-ae8eb1cde7d4"
|
||||
],
|
||||
"IsFail": [],
|
||||
"IsError": []
|
||||
},
|
||||
"SuccessorNodes": {
|
||||
"Upstream": [],
|
||||
"IsSucceed": [],
|
||||
"IsFail": [],
|
||||
"IsError": []
|
||||
},
|
||||
"TrueNodes": null,
|
||||
"FalseNodes": null,
|
||||
"UpstreamNodes": null,
|
||||
"ErrorNodes": null,
|
||||
"ParameterData": [
|
||||
{
|
||||
"State": false,
|
||||
"SourceNodeGuid": "24c74a00-974e-48b1-b0dc-ae8eb1cde7d4",
|
||||
"SourceType": "GetOtherNodeData",
|
||||
"ArgName": "user",
|
||||
"Value": ""
|
||||
}
|
||||
],
|
||||
"ParentNodeGuid": null,
|
||||
"ChildNodeGuids": [],
|
||||
"Position": {
|
||||
"X": 509.6,
|
||||
"Y": 351.7
|
||||
},
|
||||
"IsInterrupt": false,
|
||||
"IsEnable": true,
|
||||
"IsProtectionParameter": false,
|
||||
"CustomData": {
|
||||
"Script": "if (user.Info.Age >= 35) {\r\n user.Info.Name = \"[失业]\" + user.Info.Name;\r\n return user.Info.Name+\" \"+user.Info.Age+\"岁\";\r\n} else {\r\n user.Info.Name = \"[牛马]\" + user.Info.Name;\r\n return user.Info.Name+\" \"+user.Info.Age+\"岁\";\r\n}"
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user