diff --git a/FlowStartTool/Program.cs b/FlowStartTool/Program.cs index 3dcdc74..5a9ea24 100644 --- a/FlowStartTool/Program.cs +++ b/FlowStartTool/Program.cs @@ -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(); diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 94e0cbc..6ce4008 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -1002,7 +1002,7 @@ namespace Serein.Library.Api /// 消息 /// 输出类型 /// 输出级别 - void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Trivial); + void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Debug); /// /// 提供设置UI上下文的能力 /// 提供设置UI上下文的能力,在WinForm/WPF项目中,在UI线程外对UI元素的修改将会导致异常 diff --git a/Library/Api/IJsonProvider.cs b/Library/Api/IJsonProvider.cs index 203455e..30e2b5f 100644 --- a/Library/Api/IJsonProvider.cs +++ b/Library/Api/IJsonProvider.cs @@ -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 /// /// JSON数据交互的Token接口,允许使用不同的JSON库进行数据处理。 /// - public interface IJsonToken + public interface IJsonToken { + /// + /// 获取 Token + /// + /// + /// + IJsonToken this[object name] { get; } + + /* /// + /// 获取 Token 数组的元素,允许通过索引访问数组中的元素。 + /// + /// + /// + IJsonToken this[int index] { get; }*/ + /// /// 获取指定名称的属性,如果存在则返回true,并通过out参数返回对应的IJsonToken对象。 /// @@ -83,6 +98,7 @@ namespace Serein.Library.Api /// public interface IJsonProvider { + /// /// JSON文本转为指定类型 /// diff --git a/Library/Enums/InfoType.cs b/Library/Enums/InfoType.cs index a399116..3db5f6c 100644 --- a/Library/Enums/InfoType.cs +++ b/Library/Enums/InfoType.cs @@ -12,9 +12,9 @@ namespace Serein.Library public enum InfoClass { /// - /// 琐碎的 + /// 调试 /// - Trivial, + Debug, /// /// 一般的 /// diff --git a/Library/Enums/NodeType.cs b/Library/Enums/NodeType.cs index 7c64628..40d25c0 100644 --- a/Library/Enums/NodeType.cs +++ b/Library/Enums/NodeType.cs @@ -37,15 +37,13 @@ namespace Serein.Library UI, /// - /// 触发器节点,必须为标记在可异步等待的方法,建议与继承了 FlowTriggerk<TEnum> 的实例对象搭配使用 + /// 触发器节点,必须为标记在可异步等待的方法 /// 方法返回值必须为Task<IFlipflopContext<TResult>>,若为其它返回值,将不会创建节点。 /// 触发器根据在分支中的位置,分为两种类型:流程分支中的触发器、全局触发器 /// 一般的触发器:存在于分支某处,也可能是分支的终点,但一定不是流程的起点与分支的起点。 /// 一般的触发器行为:在当前分支中执行一次之后不再执行,一般用于等待某个操作的响应。 - /// 一般的触发器入参:如果使用了 FlowTriggerk<TEnum> ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。 /// 全局触发器:没有上游分支、同时并非流程的起始节点。 /// 全局触发器行为:全局触发器会循环执行,直到流程结束。 - /// 一般的触发器入参:如果使用了 FlowTriggerk<TEnum> ,就会至少有一个枚举类型的参数,参数类型与 TEnum 泛型一致。 /// Flipflop, /// diff --git a/Library/FlowNode/DelegateDetails.cs b/Library/FlowNode/DelegateDetails.cs index 23f6049..b3756e5 100644 --- a/Library/FlowNode/DelegateDetails.cs +++ b/Library/FlowNode/DelegateDetails.cs @@ -159,7 +159,7 @@ namespace Serein.Library /// /// 类型信息 /// 操作类型 - 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) { diff --git a/Library/FlowNode/FlipflopContext.cs b/Library/FlowNode/FlipflopContext.cs index 24d4be7..61bc176 100644 --- a/Library/FlowNode/FlipflopContext.cs +++ b/Library/FlowNode/FlipflopContext.cs @@ -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 /// /// 触发类型 /// - public TriggerDescription Type { get; set; } /// /// 触发时传递的数据 /// public TResult Value { get; set; } - /// - /// 触发器上下文构造函数 - /// - /// - public FlipflopContext(FlipflopStateType ffState) + public FlipflopContext() { - State = ffState; + } /// - /// 触发器上下文构造函数,传入状态和数据值 + /// 成功触发器上下文,表示触发器执行成功并返回结果 /// - /// - /// - public FlipflopContext(FlipflopStateType ffState, TResult value) + /// + /// + /// + public static FlipflopContext Ok(TResult result) { - State = ffState; - Value = value; + return new FlipflopContext() + { + State = FlipflopStateType.Succeed, + Type = TriggerDescription.External, + Value = result, + }; } + /// + /// 表示触发器执行失败 + /// + /// + public static FlipflopContext Fail() + { + return new FlipflopContext() + { + State = FlipflopStateType.Fail, + Type = TriggerDescription.External, + Value = default, + }; + } + + /// + /// 表示触发器执行过程中发生了错误 + /// + /// + public static FlipflopContext Error() + { + return new FlipflopContext() + { + State = FlipflopStateType.Error, + Type = TriggerDescription.External, + Value = default, + }; + } + + /// + /// 取消触发器上下文,表示触发器被外部取消 + /// + /// + public static FlipflopContext Cancel() + { + return new FlipflopContext() + { + State = FlipflopStateType.Cancel, + Type = TriggerDescription.External, + Value = default, + }; + } + + /// + /// 超时触发器上下文,表示触发器在指定时间内未完成 + /// + /// + /// + public static FlipflopContext Overtime(FlipflopStateType state = FlipflopStateType.Fail) + { + return new FlipflopContext() + { + State = state, + Type = TriggerDescription.Overtime, + Value = default, + }; + } + + + } diff --git a/Library/FlowNode/FlowCanvasDetails.cs b/Library/FlowNode/FlowCanvasDetails.cs index c2f4d54..32aacad 100644 --- a/Library/FlowNode/FlowCanvasDetails.cs +++ b/Library/FlowNode/FlowCanvasDetails.cs @@ -98,7 +98,7 @@ namespace Serein.Library /// 起始节点 /// [DataInfo] - private IFlowNode _startNode; + private IFlowNode? _startNode; } diff --git a/Library/FlowNode/LightweightFlowEnvironment.cs b/Library/FlowNode/LightweightFlowEnvironment.cs index de8c408..eae0978 100644 --- a/Library/FlowNode/LightweightFlowEnvironment.cs +++ b/Library/FlowNode/LightweightFlowEnvironment.cs @@ -15,7 +15,6 @@ namespace Serein.Library { private readonly SortedDictionary _callNodes = new SortedDictionary(); - //private readonly Dictionary _callNodes = new Dictionary(); /// /// 索引器,允许通过字符串索引访问CallNode @@ -700,7 +699,7 @@ namespace Serein.Library } /// - 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); } diff --git a/Library/ScriptBaseFunc.cs b/Library/ScriptBaseFunc.cs index 0e01eae..24f0787 100644 --- a/Library/ScriptBaseFunc.cs +++ b/Library/ScriptBaseFunc.cs @@ -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挂载方法 + + /// + /// 转为JSON对象 + /// + /// + /// + public static IJsonToken json(string content) + { + return JsonHelper.Parse(content); + } + + + + + + + + #endregion + + /// /// 获取全局数据 /// @@ -153,11 +176,11 @@ namespace Serein.Library { return type.GetType(); } + /// - /// 记录日志信息 + /// 输出内容 /// /// - public static void log(object value) { SereinEnv.WriteLine(InfoType.INFO, value?.ToString()); @@ -168,18 +191,9 @@ namespace Serein.Library /// /// /// - 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); } } diff --git a/Library/Utils/EmitHelper.cs b/Library/Utils/EmitHelper.cs index b1933b1..02a0f69 100644 --- a/Library/Utils/EmitHelper.cs +++ b/Library/Utils/EmitHelper.cs @@ -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 /// - /// 构建集合获取委托:Func<object, object, object> + /// 构建集合取值委托:(object collection, object index) => object value + /// 支持数组、泛型集合、IDictionary 等类型 /// - /// - /// - /// - public static Func CreateCollectionGetter(Type collectionType) + public static Func 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, Dictionary 等) + 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)dm.CreateDelegate(typeof(Func)); } + + + 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; + } } } diff --git a/Library/Utils/FlowTrigger/ChannelFlowTrigger.cs b/Library/Utils/FlowTrigger/ChannelFlowTrigger.cs index 9e31180..a3a3ba9 100644 --- a/Library/Utils/FlowTrigger/ChannelFlowTrigger.cs +++ b/Library/Utils/FlowTrigger/ChannelFlowTrigger.cs @@ -25,7 +25,16 @@ namespace Serein.Library.Utils /// 对应的 Channel private Channel> GetOrCreateChannel(TSignal signal) { - return _channels.GetOrAdd(signal, _ => Channel.CreateUnbounded>()); + if(_channels.TryGetValue(signal, out var channel)) + { + return channel; + } + else + { + channel = Channel.CreateUnbounded>(); + _channels.AddOrUpdate(signal, _ => channel, (s, r) => channel = r); + return channel; + } } /// diff --git a/Library/Utils/SereinEnv.cs b/Library/Utils/SereinEnv.cs index b475451..a070b13 100644 --- a/Library/Utils/SereinEnv.cs +++ b/Library/Utils/SereinEnv.cs @@ -135,7 +135,7 @@ namespace Serein.Library /// 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); diff --git a/NodeFlow/Env/FlowControl.cs b/NodeFlow/Env/FlowControl.cs index 95670d6..9001e51 100644 --- a/NodeFlow/Env/FlowControl.cs +++ b/NodeFlow/Env/FlowControl.cs @@ -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 flowTaskManagementPool; private FlowWorkOptions flowTaskOptions; + - - private FlowWorkManagement? flowWorkManagement; private ISereinIOC? externalIOC; private Action? setDefultMemberOnReset; private bool IsUseExternalIOC = false; @@ -88,9 +89,28 @@ namespace Serein.NodeFlow.Env } } + private readonly List 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); + } + + /// public async Task StartFlowAsync(string[] canvasGuids) { + #region 校验参数 HashSet guids = new HashSet(); 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() + .ToDictionary(key => key.Guid, + value => new FlowTask + { + GetStartNode = () => value.StartNode!, + GetNodes = () => flowModelService.GetAllNodeModel(value.Guid), + IsWaitStartFlow = false + }); - #region 初始化每个画布的数据,转换为流程任务 - Dictionary 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(() => flowEnvironment); - //externalIOC.Register(); // 注册脚本接口 - - 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; } /// public async Task StartFlowAsync(string startNodeGuid) { - var sw = Stopwatch.StartNew(); var checkpoints = new Dictionary(); - - 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 } } - /*/// - /// 单独运行一个节点 - /// - /// - /// - public async Task 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; - }*/ + /// public Task 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); } + + + + + /// public void ActivateFlipflopNode(string nodeGuid) { @@ -313,6 +323,7 @@ namespace Serein.NodeFlow.Env flowTaskManagement.TerminateGlobalFlipflopRuning(flipflopNode); }*/ } + /// public void UseExternalIOC(ISereinIOC ioc, Action? setDefultMemberOnReset = null) { @@ -320,11 +331,13 @@ namespace Serein.NodeFlow.Env this.setDefultMemberOnReset = setDefultMemberOnReset; IsUseExternalIOC = true; } + /// public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType) { flowEnvironmentEvent.OnMonitorObjectChanged(new MonitorObjectEventArgs(nodeGuid, monitorData, sourceType)); } + /// public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type) { diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 62acad1..c8d25a8 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -242,7 +242,7 @@ namespace Serein.NodeFlow.Env /// 日志内容 /// 日志类别 /// 日志级别 - 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); } diff --git a/NodeFlow/Env/LocalFlowEnvironment.cs b/NodeFlow/Env/LocalFlowEnvironment.cs index 91701f5..c9e6049 100644 --- a/NodeFlow/Env/LocalFlowEnvironment.cs +++ b/NodeFlow/Env/LocalFlowEnvironment.cs @@ -127,7 +127,7 @@ namespace Serein.NodeFlow.Env /// /// 信息输出等级 /// - public InfoClass InfoClass { get; set; } = InfoClass.Trivial; + public InfoClass InfoClass { get; set; } = InfoClass.Debug; /// /// 如果没有全局触发器,且没有循环分支,流程执行完成后自动为 Completion 。 @@ -211,7 +211,7 @@ namespace Serein.NodeFlow.Env /// 日志内容 /// 日志类别 /// 日志级别 - 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) { diff --git a/NodeFlow/FlowWorkOptions.cs b/NodeFlow/FlowWorkOptions.cs index 5a5a4bb..3a81ed5 100644 --- a/NodeFlow/FlowWorkOptions.cs +++ b/NodeFlow/FlowWorkOptions.cs @@ -12,7 +12,7 @@ namespace Serein.NodeFlow /// /// 是否异步启动流程 /// - public bool IsTaskAsync { get; set; } + public bool IsWaitStartFlow { get; set; } = true; /// /// 流程起始节点 @@ -28,7 +28,7 @@ namespace Serein.NodeFlow /// /// 节点任务执行依赖 /// - public class FlowWorkOptions() + public sealed class FlowWorkOptions() { /// /// 流程IOC容器 diff --git a/NodeFlow/Model/Nodes/NodeModelBaseFunc.cs b/NodeFlow/Model/Nodes/NodeModelBaseFunc.cs index 57d6d1b..ed26f5b 100644 --- a/NodeFlow/Model/Nodes/NodeModelBaseFunc.cs +++ b/NodeFlow/Model/Nodes/NodeModelBaseFunc.cs @@ -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; diff --git a/NodeFlow/Model/Nodes/SingleFlipflopNode.cs b/NodeFlow/Model/Nodes/SingleFlipflopNode.cs index 6283a8b..e39422e 100644 --- a/NodeFlow/Model/Nodes/SingleFlipflopNode.cs +++ b/NodeFlow/Model/Nodes/SingleFlipflopNode.cs @@ -5,10 +5,22 @@ using System; namespace Serein.NodeFlow.Model.Nodes { + [FlowDataProperty(ValuePath = NodeValuePath.Node, IsNodeImp = true)] + public partial class SingleFlipflopNode + { + /// + /// 是否等待后继节点(仅对于全局触发器) + /// 如果为 true,则在触发器获取结果后,等待后继节点执行完成,才会调用触发器 + /// 如果为 false,则触发器获取到结果后,将使用 _ = Task.Run(...) 再次调用触发器 + /// + + private bool _isWaitSuccessorNodes = true; + } + /// /// 触发器节点 /// - public class SingleFlipflopNode : NodeModelBase + public partial class SingleFlipflopNode : NodeModelBase { /// /// 构造一个新的单触发器节点实例。 @@ -29,46 +41,52 @@ namespace Serein.NodeFlow.Model.Nodes /// public override async Task 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 // 而我们只需要获取到 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; } diff --git a/NodeFlow/Services/FlowWorkManagement.cs b/NodeFlow/Tool/FlowWorkManagement.cs similarity index 73% rename from NodeFlow/Services/FlowWorkManagement.cs rename to NodeFlow/Tool/FlowWorkManagement.cs index e700cd1..a64bb70 100644 --- a/NodeFlow/Services/FlowWorkManagement.cs +++ b/NodeFlow/Tool/FlowWorkManagement.cs @@ -23,12 +23,12 @@ namespace Serein.NodeFlow.Services /// /// 触发器对应的Cts /// - private ConcurrentDictionary dictGlobalFlipflop = []; + private ConcurrentDictionary _globalFlipflops = []; /// /// 结束运行时需要执行的方法 /// - private Func? ExitAction { get; set; } + private Func? _exitAction { get; set; } /// /// 初始化选项 @@ -51,60 +51,72 @@ namespace Serein.NodeFlow.Services /// public async Task RunAsync(CancellationToken token) { + var sw = Stopwatch.StartNew(); + var checkpoints = new Dictionary(); + #region 注册所有节点所属的类的类型,如果注册失败则退出 List nodes = new List(); - 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; } + /// + /// 尝试初始化 + /// + /// + /// private async Task TryInit() { var env = WorkOptions.Environment; @@ -175,6 +192,12 @@ namespace Serein.NodeFlow.Services var isSuccessful = true; return isSuccessful; } + + /// + /// 尝试加载流程 + /// + /// + /// private async Task TryLoadAsync() { var env = WorkOptions.Environment; @@ -198,6 +221,12 @@ namespace Serein.NodeFlow.Services return isSuccessful; } + + /// + /// 结束流程时调用的方法 + /// + /// + /// private async Task 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(); - // 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; } + /// + /// 调用流程中的触发器节点 + /// + /// + /// 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() + .Select(async node => + { + await RunGlobalFlipflopAsync(env, node); // 启动流程时启动全局触发器 + }); + var tasks = flipflopNodes.ToArray(); + await Task.WhenAll(tasks); } /// - /// 从某一个节点开始执行 + /// 从某个节点开始执行 /// /// /// - 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 } /// - /// 尝试添加全局触发器 + /// 运行全局触发器 /// /// /// 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 /// 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 /// 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(); } /// /// 启动全局触发器 /// - /// 需要全局监听信号的触发器 - /// 单个触发器持有的 + /// 需要全局监听信号的触发器 + /// 单个触发器持有的 /// - 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 /// /// /// - private static async Task? CallSubsequentNode(SingleFlipflopNode singleFlipFlopNode, CancellationToken singleToken, ObjectPool pool, IFlowContext context) + private static async Task CallSuccessorNodesAsync(SingleFlipflopNode singleFlipFlopNode, CancellationToken singleToken, ObjectPool 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); } /// @@ -450,7 +497,7 @@ namespace Serein.NodeFlow.Services /// public void Exit() { - ExitAction?.Invoke(); + _exitAction?.Invoke(); } diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonArrayToken.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonArrayToken.cs new file mode 100644 index 0000000..0ac901d --- /dev/null +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonArrayToken.cs @@ -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 + { + 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()); + } + + + public IEnumerable EnumerateArray() + { + foreach (var t in _array) + yield return NewtonsoftJsonTokenFactory.FromJToken(t); + } + + public T ToObject() => 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()); + 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())); + } + + public void RemoveAt(int index) + { + _array.RemoveAt(index); + } + + public void Add(IJsonToken item) + { + _array.Add(JToken.FromObject(item.ToObject())); + } + + public void Clear() + { + _array.Clear(); + } + + public bool Contains(IJsonToken item) + { + var jt = JToken.FromObject(item.ToObject()); + 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 GetEnumerator() + { + foreach (var item in _array) + yield return NewtonsoftJsonTokenFactory.FromJToken(item); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + } +} diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonObjectToken.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonObjectToken.cs new file mode 100644 index 0000000..8e7404c --- /dev/null +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonObjectToken.cs @@ -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 +{ + /// + /// 基于Newtonsoft.Json的IJsonToken实现 + /// + public sealed class NewtonsoftJsonObjectToken : IJsonToken, IDictionary + { + 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()); + } + + public IEnumerable EnumerateArray() => throw new InvalidOperationException("不是数组类型"); + + public T ToObject() => _object.ToObject(); + 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 接口实现 + + public ICollection Keys => _object.Properties().Select(p => p.Name).ToList(); + + public ICollection 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[key] = token; + } + + + public void Add(KeyValuePair item) => Add(item.Key, item.Value); + + public void Clear() => _object.RemoveAll(); + + public bool ContainsKey(string key) => _object.ContainsKey(key); + + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + foreach (var prop in _object.Properties()) + { + array[arrayIndex++] = new KeyValuePair(prop.Name, new NewtonsoftJsonObjectToken(JObject.FromObject(prop.Value))); + } + } + public IEnumerator> GetEnumerator() + { + foreach (var prop in _object.Properties()) + { + yield return new KeyValuePair(prop.Name, new NewtonsoftJsonObjectToken(JObject.FromObject(prop.Value))); + } + } + + public bool Remove(string key) => _object.Remove(key); + + + public bool Remove(KeyValuePair 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 item) + { + if (_object.TryGetValue(item.Key, out var token)) + { + var value = new NewtonsoftJsonObjectToken(JObject.FromObject(token)); + return value.Equals(item.Value); + } + return false; + } + + + #endregion + + } +} diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs index 9b4774a..0b1d248 100644 --- a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonProvider.cs @@ -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); } /// - /// 创建一个新的JSON对象。 + /// 创建一个新的JSON数组对象。 /// /// /// public IJsonToken CreateObject(IDictionary? values = null) { var jobj = values != null ? JObject.FromObject(values) : new JObject(); - return new NewtonsoftJsonToken(jobj); + return new NewtonsoftJsonObjectToken(jobj); } /// @@ -119,7 +118,7 @@ namespace Serein.Extend.NewtonsoftJson public IJsonToken CreateArray(IEnumerable? values = null) { var jarr = values != null ? JArray.FromObject(values) : new JArray(); - return new NewtonsoftJsonToken(jarr); + return new NewtonsoftJsonArrayToken(jarr); } /// @@ -129,8 +128,8 @@ namespace Serein.Extend.NewtonsoftJson /// public IJsonToken FromObject(object obj) { - var token = JToken.FromObject(obj); - return new NewtonsoftJsonToken(token); + var token = JObject.FromObject(obj); + return new NewtonsoftJsonObjectToken(token); } } } diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs deleted file mode 100644 index 345a12f..0000000 --- a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonToken.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Newtonsoft.Json.Linq; -using Serein.Library.Api; -using System.Diagnostics.CodeAnalysis; - -namespace Serein.Extend.NewtonsoftJson -{ - /// - /// 基于Newtonsoft.Json的IJsonToken实现 - /// - public sealed class NewtonsoftJsonToken : IJsonToken - { - private readonly JToken _token; - - /// - /// 使用JToken初始化一个新的NewtonsoftJsonToken实例。 - /// - /// - /// - public NewtonsoftJsonToken(JToken token) - { - _token = token ?? throw new ArgumentNullException(nameof(token)); - } - - /// - /// 尝试获取指定名称的属性值。 - /// - /// - /// - /// - 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(); - - public bool GetBoolean() => _token.Value(); - - public bool IsNull => _token.Type == JTokenType.Null || _token.Type == JTokenType.Undefined; - - public IEnumerable EnumerateArray() - { - if (_token is JArray arr) - return arr.Select(x => new NewtonsoftJsonToken(x)); - throw new InvalidOperationException("当前Token不是数组类型。"); - } - - /// - /// 将当前JSON Token转换为指定类型的对象。 - /// - /// - /// -#pragma warning disable CS8603 // 可能返回 null 引用。 - public T ToObject() => _token.ToObject(); -#pragma warning restore CS8603 // 可能返回 null 引用。 - - /// - /// 将当前JSON Token转换为指定类型的对象。 - /// - /// - /// -#pragma warning disable CS8603 // 可能返回 null 引用。 - public object ToObject(Type type) => _token.ToObject(type); -#pragma warning restore CS8603 // 可能返回 null 引用。 - - /// - /// 返回当前JSON Token的字符串表示形式。 - /// - /// - public override string ToString() => _token.ToString(); - } -} diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonTokenFactory.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonTokenFactory.cs new file mode 100644 index 0000000..4472916 --- /dev/null +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonTokenFactory.cs @@ -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) + }; + } + } + +} diff --git a/Serein.Extend.NewtonsoftJson/NewtonsoftJsonValueToken.cs b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonValueToken.cs new file mode 100644 index 0000000..9f3540b --- /dev/null +++ b/Serein.Extend.NewtonsoftJson/NewtonsoftJsonValueToken.cs @@ -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(); + public bool GetBoolean() => _token.Value(); + + public IJsonToken this[object key] => throw new InvalidOperationException("不是对象/数组类型"); + public IEnumerable EnumerateArray() => throw new InvalidOperationException("不是数组类型"); + + public T ToObject() => _token.ToObject(); + 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("不是对象类型"); + + + + + } +} diff --git a/Serein.Library.MyGenerator/FlowDataPropertyGenerator.cs b/Serein.Library.MyGenerator/FlowDataPropertyGenerator.cs index 09f8d8d..58c1539 100644 --- a/Serein.Library.MyGenerator/FlowDataPropertyGenerator.cs +++ b/Serein.Library.MyGenerator/FlowDataPropertyGenerator.cs @@ -30,7 +30,6 @@ namespace Serein.Library.NodeGenerator /// 增量生成器的上下文,用于注册生成逻辑。 public void Initialize(IncrementalGeneratorInitializationContext context) { - /* * //Debugger.Launch(); CreateSyntaxProvider : 第一个参数用于筛选特定语法节点,第二个参数则用于转换筛选出来的节点。 diff --git a/Serein.Script/Node/ValueNode/StringNode.cs b/Serein.Script/Node/ValueNode/StringNode.cs index 515df3e..7e310ea 100644 --- a/Serein.Script/Node/ValueNode/StringNode.cs +++ b/Serein.Script/Node/ValueNode/StringNode.cs @@ -42,6 +42,10 @@ namespace Serein.Script.Node case '\\': // 字面量反斜杠 output.Append('\\'); i++; // 跳过第二个 '\\' + break; + case '"': // 字符串反斜杠 + output.Append('"'); + i++; // 跳过第二个 '"' break; default: output.Append(input[i]); // 不是转义符,保留反斜杠 diff --git a/Serein.Script/SereinScriptInterpreter.cs b/Serein.Script/SereinScriptInterpreter.cs index 171aba2..a381f85 100644 --- a/Serein.Script/SereinScriptInterpreter.cs +++ b/Serein.Script/SereinScriptInterpreter.cs @@ -516,7 +516,7 @@ namespace Serein.Script /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private async Task GetCollectionValueAsync(ASTNode node, object collectionValue, object indexValue) + private async Task 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; diff --git a/Serein.Script/SereinScriptLexer.cs b/Serein.Script/SereinScriptLexer.cs index 26cbf3d..2eb2363 100644 --- a/Serein.Script/SereinScriptLexer.cs +++ b/Serein.Script/SereinScriptLexer.cs @@ -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"); + + } + /// /// 读取硬编码的文本 diff --git a/Serein.Script/SereinScriptParser.cs b/Serein.Script/SereinScriptParser.cs index 3439149..1afdac2 100644 --- a/Serein.Script/SereinScriptParser.cs +++ b/Serein.Script/SereinScriptParser.cs @@ -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; + } } + } /// @@ -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) // 从对象获取 { /* diff --git a/Serein.Script/SereinScriptTypeAnalysis.cs b/Serein.Script/SereinScriptTypeAnalysis.cs index f6d0f5d..adebee7 100644 --- a/Serein.Script/SereinScriptTypeAnalysis.cs +++ b/Serein.Script/SereinScriptTypeAnalysis.cs @@ -674,7 +674,7 @@ namespace Serein.Script /// 索引 /// 获取到的类型 /// - 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; diff --git a/Workbench/App.xaml.cs b/Workbench/App.xaml.cs index 2e67be7..ea53be4 100644 --- a/Workbench/App.xaml.cs +++ b/Workbench/App.xaml.cs @@ -51,7 +51,20 @@ namespace Serein.Workbench private void Application_Startup(object sender, StartupEventArgs e) { - var projectService = App.GetService(); +#if DEBUG && true + + try + { + var t = JsonHelper.Parse(TestJson.json); + var iss = t["PreviousNodes"]["IsSucceed"][0]; + } + catch (Exception ex) + { + + } +#endif + + var projectService = App.GetService(); if (e.Args.Length == 1) { string filePath = e.Args[0]; diff --git a/Workbench/Models/FlowLibraryInfo.cs b/Workbench/Models/FlowLibraryInfo.cs index ff81b0b..da23298 100644 --- a/Workbench/Models/FlowLibraryInfo.cs +++ b/Workbench/Models/FlowLibraryInfo.cs @@ -24,6 +24,7 @@ namespace Serein.Workbench.Models [ObservableProperty] private ObservableCollection _methodInfo; + public List ActionNodes { get => MethodInfo.Where(x => x.NodeType == NodeType.Action.ToString()).ToList(); set { } } public List FlipflopNodes { get => MethodInfo.Where(x => x.NodeType == NodeType.Flipflop.ToString()).ToList(); set { } } public List UINodes { get => MethodInfo.Where(x => x.NodeType == NodeType.UI.ToString()).ToList(); set { } } diff --git a/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs b/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs index ed76967..e859f56 100644 --- a/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs @@ -109,7 +109,7 @@ namespace Serein.Workbench.Node.ViewModel FlowCallNode.ResetTargetNode(); // 如果是不选择了,则重置一下 return; } - UploadNode.Invoke(value); + UploadNode?.Invoke(value); FlowCallNode.SetTargetNode(value.Guid); // 重新设置目标节点 } diff --git a/Workbench/TestJson.cs b/Workbench/TestJson.cs new file mode 100644 index 0000000..0365076 --- /dev/null +++ b/Workbench/TestJson.cs @@ -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}" + } + } + """; + + } +}