1. 重新设计了 JSON门户类的实现

2. Script脚本添加了原始字符串的实现
3. 修复了Script中无法对  \" 双引号转义的问题
4. 新增了对于集合嵌套取值的支持(目前仅是集合取值)
5. 重新设计了FlowWorkManagement任务启动的逻辑,修复了触发器无法正常运行的问题
6. 在ScriptBaseFunc中新增了 json() 本地函数,支持将字符串转为IJsonToken进行取值。
7. EmitHelper对于集合取值时,反射获取“get_item”委托时存在看你多个MethodInfo,现在可以传入子项类型,帮助匹配目标重载方法
This commit is contained in:
fengjiayi
2025-07-31 23:59:31 +08:00
parent 5f6a58168a
commit 1bccccc835
36 changed files with 948 additions and 335 deletions

View File

@@ -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();

View File

@@ -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>

View File

@@ -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>

View File

@@ -12,9 +12,9 @@ namespace Serein.Library
public enum InfoClass
{
/// <summary>
/// 琐碎的
/// 调试
/// </summary>
Trivial,
Debug,
/// <summary>
/// 一般的
/// </summary>

View File

@@ -37,15 +37,13 @@ namespace Serein.Library
UI,
/// <summary>
/// <para>触发器节点,必须为标记在可异步等待的方法,建议与继承了 FlowTriggerk&lt;TEnum&gt; 的实例对象搭配使用</para>
/// <para>触发器节点,必须为标记在可异步等待的方法</para>
/// <para>方法返回值必须为Task&lt;IFlipflopContext&lt;TResult&gt;&gt;,若为其它返回值,将不会创建节点。</para>
/// <para>触发器根据在分支中的位置,分为两种类型:流程分支中的触发器、全局触发器</para>
/// <para>一般的触发器:存在于分支某处,也可能是分支的终点,但一定不是流程的起点与分支的起点。</para>
/// <para>一般的触发器行为:在当前分支中执行一次之后不再执行,一般用于等待某个操作的响应。</para>
/// <para>一般的触发器入参:如果使用了 FlowTriggerk&lt;TEnum&gt; ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。</para>
/// <para>全局触发器:没有上游分支、同时并非流程的起始节点。</para>
/// <para>全局触发器行为:全局触发器会循环执行,直到流程结束。</para>
/// <para>一般的触发器入参:如果使用了 FlowTriggerk&lt;TEnum&gt; ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。</para>
/// </summary>
Flipflop,
/// <summary>

View File

@@ -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)
{

View File

@@ -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,
};
}
}

View File

@@ -98,7 +98,7 @@ namespace Serein.Library
/// 起始节点
/// </summary>
[DataInfo]
private IFlowNode _startNode;
private IFlowNode? _startNode;
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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&lt;object, object, object&gt;
/// 构建集合取委托:(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;
}
}
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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容器

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 InitLoad事件
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();
}

View 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();
}
}
}

View 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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View 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)
};
}
}
}

View 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("不是对象类型");
}
}

View File

@@ -30,7 +30,6 @@ namespace Serein.Library.NodeGenerator
/// <param name="context">增量生成器的上下文,用于注册生成逻辑。</param>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
/*
* //Debugger.Launch();
CreateSyntaxProvider : 第一个参数用于筛选特定语法节点,第二个参数则用于转换筛选出来的节点。

View File

@@ -42,6 +42,10 @@ namespace Serein.Script.Node
case '\\': // 字面量反斜杠
output.Append('\\');
i++; // 跳过第二个 '\\'
break;
case '"': // 字符串反斜杠
output.Append('"');
i++; // 跳过第二个 '"'
break;
default:
output.Append(input[i]); // 不是转义符,保留反斜杠

View File

@@ -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;

View File

@@ -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>
/// 读取硬编码的文本

View File

@@ -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) // 从对象获取
{
/*

View File

@@ -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;

View File

@@ -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];

View File

@@ -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 { } }

View File

@@ -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
View 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}"
}
}
""";
}
}