From 0d89ac1415130d8ee922d19d790fdef6cba94a50 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Mon, 4 Aug 2025 22:38:20 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E8=84=9A=E6=9C=AC=E8=BD=ACc#=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E4=BA=86?= =?UTF-8?q?[Flipflop]=E8=A7=A6=E5=8F=91=E5=99=A8=E8=8A=82=E7=82=B9=202.=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86Script.StringNode=E8=BD=ACC#?= =?UTF-8?q?=E4=B8=AD=E5=AD=98=E5=9C=A8=E5=A4=9A=E4=BD=99=E7=9A=84=E8=BD=AC?= =?UTF-8?q?=E4=B9=89=E7=AC=A6=E7=9A=84=E9=97=AE=E9=A2=98=203.=20=E4=B8=BAI?= =?UTF-8?q?FlowControl=E6=B7=BB=E5=8A=A0=E4=BA=86Task=20StratNodeAsync(str?= =?UTF-8?q?ing)=E7=9A=84=E6=8E=A5=E5=8F=A3=EF=BC=8C=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E5=9C=A8=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E6=B5=81=E7=A8=8B=E6=8E=A7=E5=88=B6=204.=20?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BA=86=E5=85=B3=E4=BA=8ELightweight?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E7=8E=AF=E5=A2=83=E7=9A=84=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Api/IFlowControl.cs | 7 + Library/Api/IFlowEnvironment.cs | 7 +- Library/FlowNode/Env/CallNode.cs | 332 +++++++ Library/FlowNode/Env/FlowCallTree.cs | 76 ++ Library/FlowNode/Env/IFlowCallTree.cs | 35 + .../FlowNode/Env/LightweightFlowControl.cs | 190 ++++ .../Env/LightweightFlowEnvironment.cs | 146 ++++ .../Env/LightweightFlowEnvironmentEvent.cs | 138 +++ Library/FlowNode/FlowCanvasDetails.cs | 6 - Library/FlowNode/JunctionModel.cs | 2 +- .../FlowNode/LightweightFlowEnvironment.cs | 824 ------------------ Library/Serein.Library.csproj | 1 + NodeFlow/Env/FlowControl.cs | 61 ++ NodeFlow/Env/FlowEnvironment.cs | 2 - NodeFlow/Services/FlowCoreGenerateService.cs | 259 ++++-- Serein.Script/SereinScriptToCsharpScript.cs | 19 +- 16 files changed, 1180 insertions(+), 925 deletions(-) create mode 100644 Library/FlowNode/Env/CallNode.cs create mode 100644 Library/FlowNode/Env/FlowCallTree.cs create mode 100644 Library/FlowNode/Env/IFlowCallTree.cs create mode 100644 Library/FlowNode/Env/LightweightFlowControl.cs create mode 100644 Library/FlowNode/Env/LightweightFlowEnvironment.cs create mode 100644 Library/FlowNode/Env/LightweightFlowEnvironmentEvent.cs delete mode 100644 Library/FlowNode/LightweightFlowEnvironment.cs diff --git a/Library/Api/IFlowControl.cs b/Library/Api/IFlowControl.cs index 65494fa..86510a8 100644 --- a/Library/Api/IFlowControl.cs +++ b/Library/Api/IFlowControl.cs @@ -40,6 +40,13 @@ namespace Serein.Library.Api /// /// Task StartFlowAsync(string startNodeGuid); + + /// + /// 从选定的节点开始运行 + /// + /// + /// + Task StartFlowAsync(string startNodeGuid); /// /// 结束运行 diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 12e946a..af907c8 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -1,10 +1,5 @@ - - -using Serein.Library.FlowNode; -using Serein.Library.Utils; +using Serein.Library.Utils; using System; -using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; namespace Serein.Library.Api diff --git a/Library/FlowNode/Env/CallNode.cs b/Library/FlowNode/Env/CallNode.cs new file mode 100644 index 0000000..cb214dc --- /dev/null +++ b/Library/FlowNode/Env/CallNode.cs @@ -0,0 +1,332 @@ +using Serein.Library.Api; +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Serein.Library +{ + /// + /// 调用节点,代表一个流程中的调用点,可以是一个Action或一个异步函数。 + /// + + public class CallNode + { + + private Func taskFunc; + private Action action; + + /// + /// 创建一个新的调用节点,使用指定的节点Guid。 + /// + /// + public CallNode(string nodeGuid) + { + Guid = nodeGuid; + Init(); + } + + /// + /// 创建一个新的调用节点,使用指定的节点Guid和Action。 + /// + /// + /// + public CallNode(string nodeGuid, Action action) + { + Guid = nodeGuid; + this.action = action; + Init(); + } + + /// + /// 创建一个新的调用节点,使用指定的节点Guid和异步函数。 + /// + /// + /// + public CallNode(string nodeGuid, Func func) + { + Guid = nodeGuid; + this.taskFunc = func; + Init(); + } + + /// + /// 初始化调用节点,设置默认的子节点和后继节点字典。 + /// + private void Init() + { + //PreviousNodes = new Dictionary>(); + SuccessorNodes = new Dictionary>(); + foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) + { + //PreviousNodes[ctType] = new List(); + SuccessorNodes[ctType] = new List(); + } + } + + + private enum ActionType + { + Action, + Task, + } + private ActionType actionType = ActionType.Action; + + /// + /// 设置调用节点的Action,表示该节点执行一个同步操作。 + /// + /// + public void SetAction(Action action) + { + this.action = action; + actionType = ActionType.Action; + } + + /// + /// 设置调用节点的异步函数,表示该节点执行一个异步操作。 + /// + /// + public void SetAction(Func taskFunc) + { + this.taskFunc = taskFunc; + actionType = ActionType.Task; + } + + + /// + /// 对应的节点 + /// + public string Guid { get; } + +#if false + + /// + /// 不同分支的父节点(流程调用) + /// + public Dictionary> PreviousNodes { get; private set; } + +#endif + /// + /// 不同分支的子节点(流程调用) + /// + public Dictionary> SuccessorNodes { get; private set; } + + /// + /// 子节点数组,分为四个分支:上游、成功、失败、错误,每个分支最多支持16个子节点。 + /// + public CallNode[][] ChildNodes { get; private set; } = new CallNode[][] + { + new CallNode[MaxChildNodeCount], + new CallNode[MaxChildNodeCount], + new CallNode[MaxChildNodeCount], + new CallNode[MaxChildNodeCount] + }; + private const int MaxChildNodeCount = 16; // 每个分支最多支持16个子节点 + + + /// + /// 获取指定类型的子节点数量。 + /// + /// + /// + public int GetCount(ConnectionInvokeType type) + { + if (type == ConnectionInvokeType.Upstream) return UpstreamNodeCount; + if (type == ConnectionInvokeType.IsSucceed) return IsSuccessorNodeCount; + if (type == ConnectionInvokeType.IsFail) return IsFailNodeCount; + if (type == ConnectionInvokeType.IsError) return IsErrorNodeCount; + return 0; + } + + /// + /// 获取当前节点的子节点数量。 + /// + public int UpstreamNodeCount { get; private set; } = 0; + + /// + /// 获取当前节点的成功后继子节点数量。 + /// + public int IsSuccessorNodeCount { get; private set; } = 0; + /// + /// 获取当前节点的失败后继子节点数量。 + /// + public int IsFailNodeCount { get; private set; } = 0; + /// + /// 获取当前节点的错误后继子节点数量。 + /// + public int IsErrorNodeCount { get; private set; } = 0; + + /// + /// 添加一个上游子节点到当前节点。 + /// + /// + /// + public CallNode AddChildNodeUpstream(CallNode callNode) + { + var connectionInvokeType = ConnectionInvokeType.Upstream; + ChildNodes[(int)connectionInvokeType][UpstreamNodeCount++] = callNode; + SuccessorNodes[connectionInvokeType].Add(callNode); + return this; + } + + /// + /// 添加一个成功后继子节点到当前节点。 + /// + /// + /// + public CallNode AddChildNodeSucceed(CallNode callNode) + { + ChildNodes[0][UpstreamNodeCount++] = callNode; + + var connectionInvokeType = ConnectionInvokeType.IsSucceed; + ChildNodes[(int)connectionInvokeType][IsSuccessorNodeCount++] = callNode; + SuccessorNodes[connectionInvokeType].Add(callNode); + + return this; + } + /// + /// 添加一个失败后继子节点到当前节点。 + /// + /// + /// + public CallNode AddChildNodeFail(CallNode callNode) + { + var connectionInvokeType = ConnectionInvokeType.IsFail; + ChildNodes[(int)connectionInvokeType][IsFailNodeCount++] = callNode; + SuccessorNodes[connectionInvokeType].Add(callNode); + + return this; + } + + /// + /// 添加一个错误后继子节点到当前节点。 + /// + /// + /// + public CallNode AddChildNodeError(CallNode callNode) + { + var connectionInvokeType = ConnectionInvokeType.IsError; + ChildNodes[(int)connectionInvokeType][IsErrorNodeCount++] = callNode; + SuccessorNodes[connectionInvokeType].Add(callNode); + return this; + } + + + /// + /// 调用 + /// + /// + /// + /// + /// + public async Task InvokeAsync(IFlowContext context, CancellationToken token) + { + if (token.IsCancellationRequested) + { + return; + } + if (actionType == ActionType.Action) + { + action.Invoke(context); + } + else if (actionType == ActionType.Task) + { + await taskFunc.Invoke(context); + } + else + { + throw new InvalidOperationException($"生成了错误的CallNode。【{Guid}】"); + } + } + + private static readonly ObjectPool> _stackPool = new ObjectPool>(() => new Stack()); + + + /// + /// 开始执行 + /// + /// + /// 流程运行 + /// + public async Task StartFlowAsync(IFlowContext context, CancellationToken token) + { + var stack = _stackPool.Allocate(); + stack.Push(this); + while (true) + { + if (token.IsCancellationRequested) + { + throw new Exception($"流程执行被取消,未能获取到流程结果。"); + } + + #region 执行相关 + // 从栈中弹出一个节点作为当前节点进行处理 + var currentNode = stack.Pop(); + context.NextOrientation = ConnectionInvokeType.None; // 重置上下文状态 + FlowResult flowResult = null; + try + { + context.NextOrientation = ConnectionInvokeType.IsSucceed; // 默认执行成功 + await currentNode.InvokeAsync(context, token); + } + catch (Exception ex) + { + flowResult = FlowResult.Fail(currentNode.Guid, context, ex.Message); + context.Env.WriteLine(InfoType.ERROR, $"节点[{currentNode}]异常:" + ex); + context.NextOrientation = ConnectionInvokeType.IsError; + context.ExceptionOfRuning = ex; + } + #endregion + + #region 执行完成时更新栈 + // 首先将指定类别后继分支的所有节点逆序推入栈中 + var nextNodes = currentNode.SuccessorNodes[context.NextOrientation]; + for (int index = nextNodes.Count - 1; index >= 0; index--) + { + var node = nextNodes[index]; + context.SetPreviousNode(node.Guid, currentNode.Guid); + stack.Push(node); + } + + // 然后将指上游分支的所有节点逆序推入栈中 + var upstreamNodes = currentNode.SuccessorNodes[ConnectionInvokeType.Upstream]; + for (int index = upstreamNodes.Count - 1; index >= 0; index--) + { + var node = upstreamNodes[index]; + context.SetPreviousNode(node.Guid, currentNode.Guid); + stack.Push(node); + } + #endregion + + #region 执行完成后检查 + + if (stack.Count == 0) + { + _stackPool.Free(stack); + flowResult = context.GetFlowData(currentNode.Guid); + return flowResult; // 说明流程到了终点 + } + + if (context.RunState == RunState.Completion) + { + + _stackPool.Free(stack); + context.Env.WriteLine(InfoType.INFO, $"流程执行到节点[{currentNode.Guid}]时提前结束,将返回当前执行结果。"); + flowResult = context.GetFlowData(currentNode.Guid); + return flowResult; // 流程执行完成,返回结果 + } + + if (token.IsCancellationRequested) + { + _stackPool.Free(stack); + throw new Exception($"流程执行到节点[{currentNode.Guid}]时被取消,未能获取到流程结果。"); + } + + + #endregion + } + + } + } +} diff --git a/Library/FlowNode/Env/FlowCallTree.cs b/Library/FlowNode/Env/FlowCallTree.cs new file mode 100644 index 0000000..abf9510 --- /dev/null +++ b/Library/FlowNode/Env/FlowCallTree.cs @@ -0,0 +1,76 @@ +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Serein.Library +{ + /// + /// 流程调用树,管理所有的调用节点 + /// + public class FlowCallTree : IFlowCallTree + { + + private readonly SortedDictionary _callNodes = new SortedDictionary(); + + /// + public List StartNodes { get; set; } + + /// + public List GlobalFlipflopNodes { get; set; } + + + /// + /// 索引器,允许通过字符串索引访问CallNode + /// + /// + /// + public CallNode this[string index] + { + get + { + _callNodes.TryGetValue(index, out CallNode callNode); + return callNode; + } + set + { + // 设置指定索引的值 + _callNodes.Add(index, value); + } + } + + + + /// + /// 添加一个调用节点到流程调用树中 + /// + /// + /// + public void AddCallNode(string nodeGuid, Action action) + { + var node = new CallNode(nodeGuid, action); + _callNodes[nodeGuid] = node; + } + + /// + /// 添加一个调用节点到流程调用树中,使用异步函数 + /// + /// + /// + public void AddCallNode(string nodeGuid, Func func) + { + var node = new CallNode(nodeGuid, func); + _callNodes[nodeGuid] = node; + } + + /// + /// 获取指定Key的CallNode,如果不存在则返回null + /// + /// + /// + public CallNode Get(string key) + { + return _callNodes.TryGetValue(key, out CallNode callNode) ? callNode : null; + } + } +} diff --git a/Library/FlowNode/Env/IFlowCallTree.cs b/Library/FlowNode/Env/IFlowCallTree.cs new file mode 100644 index 0000000..03ed04e --- /dev/null +++ b/Library/FlowNode/Env/IFlowCallTree.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Serein.Library +{ + /// + /// 流程调用树接口,提供获取CallNode的方法。 + /// + public interface IFlowCallTree + { + /// + /// 起始节点 + /// + List StartNodes { get; } + + /// + /// 全局触发器节点列表 + /// + List GlobalFlipflopNodes { get; } + + /// + /// 初始化并启动流程调用树,异步执行。 + /// + /// + Task InitAndStartAsync(CancellationToken token); + + /// + /// 获取指定Key的CallNode,如果不存在则返回null。 + /// + /// + /// + CallNode Get(string key); + } +} diff --git a/Library/FlowNode/Env/LightweightFlowControl.cs b/Library/FlowNode/Env/LightweightFlowControl.cs new file mode 100644 index 0000000..e6f361b --- /dev/null +++ b/Library/FlowNode/Env/LightweightFlowControl.cs @@ -0,0 +1,190 @@ +using Serein.Library.Api; +using Serein.Library.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Serein.Library +{ + /// + /// 轻量级流程控制器 + /// + public class LightweightFlowControl : IFlowControl + { + private readonly IFlowCallTree flowCallTree; + private readonly IFlowEnvironment flowEnvironment; + + /// + /// 轻量级流程上下文池,使用对象池模式来管理流程上下文的创建和回收。 + /// + public static Serein.Library.Utils.ObjectPool FlowContextPool { get; set; } + + /// + /// 单例IOC容器,用于依赖注入和服务定位。 + /// + public ISereinIOC IOC => throw new NotImplementedException(); + + /// + /// 轻量级流程控制器构造函数,接受流程调用树和流程环境作为参数。 + /// + /// + /// + public LightweightFlowControl(IFlowCallTree flowCallTree, IFlowEnvironment flowEnvironment) + { + this.flowCallTree = flowCallTree; + this.flowEnvironment = flowEnvironment; + ((LightweightFlowEnvironment)flowEnvironment).FlowControl = this; + + FlowContextPool = new Utils.ObjectPool(() => + { + return new FlowContext(flowEnvironment); + }); + } + + + + /// + public Task InvokeAsync(string apiGuid, Dictionary dict) + { + throw new NotImplementedException(); + } + /// + public Task InvokeAsync(string apiGuid, Dictionary dict) + { + throw new NotImplementedException(); + } + + + //private readonly DefaultObjectPool _stackPool = new DefaultObjectPool(new DynamicContext(this)); + + /// + public async Task StartFlowAsync(string startNodeGuid) + { + IFlowContext context = Serein.Library.LightweightFlowControl.FlowContextPool.Allocate(); + CancellationTokenSource cts = new CancellationTokenSource(); + FlowResult flowResult; +#if DEBUG + flowResult = await BenchmarkHelpers.BenchmarkAsync(async () => + { + var node = flowCallTree.Get(startNodeGuid); + var flowResult = await node.StartFlowAsync(context, cts.Token); + return flowResult; + }); +#else + var node = flowCallTree.Get(startNodeGuid); + try + { + flowResult = await node.StartFlowAsync(context, cts.Token); + } + catch (global::System.Exception) + { + throw; + } + finally + { + context.Reset(); + FlowContextPool.Free(context); + } +#endif + + cts?.Cancel(); + cts?.Dispose(); + if (flowResult.Value is TResult result) + { + return result; + } + else if (flowResult is FlowResult && flowResult is TResult result2) + { + return result2; + } + else + { + throw new ArgumentNullException($"类型转换失败,流程返回数据与泛型不匹配,当前返回类型为[{flowResult.Value.GetType().FullName}]。"); + } + } + + /// + public async Task StartFlowAsync(string startNodeGuid) + { + IFlowContext context = Serein.Library.LightweightFlowControl.FlowContextPool.Allocate(); + CancellationTokenSource cts = new CancellationTokenSource(); + FlowResult flowResult; +#if DEBUG + flowResult = await BenchmarkHelpers.BenchmarkAsync(async () => + { + var node = flowCallTree.Get(startNodeGuid); + var flowResult = await node.StartFlowAsync(context, cts.Token); + return flowResult; + }); +#else + var node = flowCallTree.Get(startNodeGuid); + try + { + flowResult = await node.StartFlowAsync(context, cts.Token); + } + catch (global::System.Exception) + { + throw; + } + finally + { + context.Reset(); + FlowContextPool.Free(context); + } +#endif + + cts?.Cancel(); + cts?.Dispose(); + } + + /// + public Task StartFlowAsync(string[] canvasGuids) + { + throw new NotImplementedException(); + } + + /// + public Task ExitFlowAsync() + { + throw new NotImplementedException(); + } + + #region 无须实现 + /// + public void ActivateFlipflopNode(string nodeGuid) + { + throw new NotImplementedException(); + } + /// + public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType) + { + throw new NotImplementedException(); + } + + + /// + public void TerminateFlipflopNode(string nodeGuid) + { + throw new NotImplementedException(); + } + /// + public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type) + { + throw new NotImplementedException(); + } + /// + public void UseExternalIOC(ISereinIOC ioc) + { + throw new NotImplementedException(); + } + /// + public void UseExternalIOC(ISereinIOC ioc, Action setDefultMemberOnReset = null) + { + throw new NotImplementedException(); + } + #endregion + } +} diff --git a/Library/FlowNode/Env/LightweightFlowEnvironment.cs b/Library/FlowNode/Env/LightweightFlowEnvironment.cs new file mode 100644 index 0000000..9cc3a58 --- /dev/null +++ b/Library/FlowNode/Env/LightweightFlowEnvironment.cs @@ -0,0 +1,146 @@ +using Serein.Library.Api; +using Serein.Library.Utils; +using System; +using System.Threading.Tasks; + +namespace Serein.Library +{ + + /// + /// 轻量级流程环境实现 + /// + public class LightweightFlowEnvironment : IFlowEnvironment + { + /// + /// 轻量级流程环境构造函数,接受一个流程环境事件接口。 + /// + /// + public LightweightFlowEnvironment(IFlowEnvironmentEvent lightweightFlowEnvironmentEvent) + { + Event = lightweightFlowEnvironmentEvent; + } + /// + + public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Debug) + { + Console.WriteLine(message); + } + + /// + public ISereinIOC IOC => throw new NotImplementedException(); + /// + public IFlowEdit FlowEdit => throw new NotImplementedException(); + /// + public IFlowControl FlowControl { get; set; } + /// + public IFlowEnvironmentEvent Event { get; private set; } + /// + public string EnvName => throw new NotImplementedException(); + /// + public string ProjectFileLocation => throw new NotImplementedException(); + /// + public bool _IsGlobalInterrupt => throw new NotImplementedException(); + /// + public bool IsControlRemoteEnv => throw new NotImplementedException(); + /// + public InfoClass InfoClass { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + /// + public RunState FlowState { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + /// + public IFlowEnvironment CurrentEnv => throw new NotImplementedException(); + /// + public UIContextOperation UIContextOperation => throw new NotImplementedException(); + + /* public Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token) + { + throw new NotImplementedException(); + }*/ + /// + public void ExitRemoteEnv() + { + throw new NotImplementedException(); + } + /// + public Task GetEnvInfoAsync() + { + throw new NotImplementedException(); + } + /// + public SereinProjectData GetProjectInfoAsync() + { + throw new NotImplementedException(); + } + /// + public void LoadAllNativeLibraryOfRuning(string path, bool isRecurrence = true) + { + throw new NotImplementedException(); + } + /// + public void LoadLibrary(string dllPath) + { + throw new NotImplementedException(); + } + /// + public bool LoadNativeLibraryOfRuning(string file) + { + throw new NotImplementedException(); + } + /// + public void LoadProject(string filePath) + { + throw new NotImplementedException(); + } + /// + public Task LoadProjetAsync(string filePath) + { + throw new NotImplementedException(); + } + /// + public Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value) + { + throw new NotImplementedException(); + } + /// + public void SaveProject() + { + throw new NotImplementedException(); + } + /// + public void SetUIContextOperation(UIContextOperation uiContextOperation) + { + throw new NotImplementedException(); + } + /// + public Task StartRemoteServerAsync(int port = 7525) + { + throw new NotImplementedException(); + } + /// + public void StopRemoteServer() + { + throw new NotImplementedException(); + } + /// + public bool TryGetDelegateDetails(string assemblyName, string methodName, out DelegateDetails del) + { + throw new NotImplementedException(); + } + /// + public bool TryGetMethodDetailsInfo(string assemblyName, string methodName, out MethodDetailsInfo mdInfo) + { + throw new NotImplementedException(); + } + /// + public bool TryGetNodeModel(string nodeGuid, out IFlowNode nodeModel) + { + throw new NotImplementedException(); + } + /// + public bool TryUnloadLibrary(string assemblyFullName) + { + throw new NotImplementedException(); + } + + + } +} diff --git a/Library/FlowNode/Env/LightweightFlowEnvironmentEvent.cs b/Library/FlowNode/Env/LightweightFlowEnvironmentEvent.cs new file mode 100644 index 0000000..2b1a7be --- /dev/null +++ b/Library/FlowNode/Env/LightweightFlowEnvironmentEvent.cs @@ -0,0 +1,138 @@ +using Serein.Library.Api; + +namespace Serein.Library +{ + /// + /// 轻量级流程环境事件实现 + /// + public class LightweightFlowEnvironmentEvent : IFlowEnvironmentEvent + { + /// + public event LoadDllHandler DllLoad; + /// + public event ProjectLoadedHandler ProjectLoaded; + /// + public event ProjectSavingHandler ProjectSaving; + /// + public event NodeConnectChangeHandler NodeConnectChanged; + /// + public event CanvasCreateHandler CanvasCreated; + /// + public event CanvasRemoveHandler CanvasRemoved; + /// + public event NodeCreateHandler NodeCreated; + /// + public event NodeRemoveHandler NodeRemoved; + /// + public event NodePlaceHandler NodePlace; + /// + public event NodeTakeOutHandler NodeTakeOut; + /// + public event StartNodeChangeHandler StartNodeChanged; + /// + public event FlowRunCompleteHandler FlowRunComplete; + /// + public event MonitorObjectChangeHandler MonitorObjectChanged; + /// + public event NodeInterruptStateChangeHandler NodeInterruptStateChanged; + /// + public event ExpInterruptTriggerHandler InterruptTriggered; + /// + public event IOCMembersChangedHandler IOCMembersChanged; + /// + public event NodeLocatedHandler NodeLocated; + /// + public event EnvOutHandler EnvOutput; + /// + public void OnDllLoad(LoadDllEventArgs eventArgs) + { + DllLoad?.Invoke(eventArgs); + } + /// + public void OnProjectLoaded(ProjectLoadedEventArgs eventArgs) + { + ProjectLoaded?.Invoke(eventArgs); + } + /// + public void OnProjectSaving(ProjectSavingEventArgs eventArgs) + { + ProjectSaving?.Invoke(eventArgs); + } + /// + public void OnNodeConnectChanged(NodeConnectChangeEventArgs eventArgs) + { + NodeConnectChanged?.Invoke(eventArgs); + } + /// + public void OnCanvasCreated(CanvasCreateEventArgs eventArgs) + { + CanvasCreated?.Invoke(eventArgs); + } + /// + public void OnCanvasRemoved(CanvasRemoveEventArgs eventArgs) + { + CanvasRemoved?.Invoke(eventArgs); + } + /// + public void OnNodeCreated(NodeCreateEventArgs eventArgs) + { + NodeCreated?.Invoke(eventArgs); + } + /// + public void OnNodeRemoved(NodeRemoveEventArgs eventArgs) + { + NodeRemoved?.Invoke(eventArgs); + } + /// + public void OnNodePlace(NodePlaceEventArgs eventArgs) + { + NodePlace?.Invoke(eventArgs); + } + /// + public void OnNodeTakeOut(NodeTakeOutEventArgs eventArgs) + { + NodeTakeOut?.Invoke(eventArgs); + } + /// + public void OnStartNodeChanged(StartNodeChangeEventArgs eventArgs) + { + StartNodeChanged?.Invoke(eventArgs); + } + /// + public void OnFlowRunComplete(FlowEventArgs eventArgs) + { + FlowRunComplete?.Invoke(eventArgs); + } + /// + public void OnMonitorObjectChanged(MonitorObjectEventArgs eventArgs) + { + MonitorObjectChanged?.Invoke(eventArgs); + } + /// + public void OnNodeInterruptStateChanged(NodeInterruptStateChangeEventArgs eventArgs) + { + NodeInterruptStateChanged?.Invoke(eventArgs); + } + /// + public void OnInterruptTriggered(InterruptTriggerEventArgs eventArgs) + { + InterruptTriggered?.Invoke(eventArgs); + } + /// + public void OnIOCMembersChanged(IOCMembersChangedEventArgs eventArgs) + { + IOCMembersChanged?.Invoke(eventArgs); + } + /// + public void OnNodeLocated(NodeLocatedEventArgs eventArgs) + { + NodeLocated?.Invoke(eventArgs); + } + /// + public void OnEnvOutput(InfoType type, string value) + { + EnvOutput?.Invoke(type, value); + } + + } +} diff --git a/Library/FlowNode/FlowCanvasDetails.cs b/Library/FlowNode/FlowCanvasDetails.cs index 32aacad..f066594 100644 --- a/Library/FlowNode/FlowCanvasDetails.cs +++ b/Library/FlowNode/FlowCanvasDetails.cs @@ -1,11 +1,5 @@ using Serein.Library.Api; -using Serein.Library.FlowNode; -using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Serein.Library { diff --git a/Library/FlowNode/JunctionModel.cs b/Library/FlowNode/JunctionModel.cs index d1bfd76..9718eca 100644 --- a/Library/FlowNode/JunctionModel.cs +++ b/Library/FlowNode/JunctionModel.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Serein.Library.FlowNode +namespace Serein.Library { diff --git a/Library/FlowNode/LightweightFlowEnvironment.cs b/Library/FlowNode/LightweightFlowEnvironment.cs deleted file mode 100644 index eae0978..0000000 --- a/Library/FlowNode/LightweightFlowEnvironment.cs +++ /dev/null @@ -1,824 +0,0 @@ -using Serein.Library.Api; -using Serein.Library.Utils; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Serein.Library -{ - - /// - /// 流程调用树,管理所有的调用节点 - /// - public class FlowCallTree : IFlowCallTree - { - - private readonly SortedDictionary _callNodes = new SortedDictionary(); - - /// - /// 索引器,允许通过字符串索引访问CallNode - /// - /// - /// - public CallNode this[string index] - { - get - { - _callNodes.TryGetValue(index, out CallNode callNode); - return callNode; - } - set - { - // 设置指定索引的值 - _callNodes.Add(index, value); - } - } - - /// - /// 添加一个调用节点到流程调用树中 - /// - /// - /// - public void AddCallNode(string nodeGuid, Action action) - { - var node = new CallNode(nodeGuid, action); - _callNodes[nodeGuid] = node; - } - - /// - /// 添加一个调用节点到流程调用树中,使用异步函数 - /// - /// - /// - public void AddCallNode(string nodeGuid, Func func) - { - var node = new CallNode(nodeGuid, func); - _callNodes[nodeGuid] = node; - } - - /// - /// 获取指定Key的CallNode,如果不存在则返回null - /// - /// - /// - public CallNode Get(string key) - { - return _callNodes.TryGetValue(key, out CallNode callNode) ? callNode : null; - } - } - - - - - /// - /// 调用节点,代表一个流程中的调用点,可以是一个Action或一个异步函数。 - /// - - public class CallNode - { - - private Func taskFunc; - private Action action; - - /// - /// 创建一个新的调用节点,使用指定的节点Guid。 - /// - /// - public CallNode(string nodeGuid) - { - Guid = nodeGuid; - Init(); - } - - /// - /// 创建一个新的调用节点,使用指定的节点Guid和Action。 - /// - /// - /// - public CallNode(string nodeGuid, Action action) - { - Guid = nodeGuid; - this.action = action; - Init(); - } - - /// - /// 创建一个新的调用节点,使用指定的节点Guid和异步函数。 - /// - /// - /// - public CallNode(string nodeGuid, Func func) - { - Guid = nodeGuid; - this.taskFunc = func; - Init(); - } - - /// - /// 初始化调用节点,设置默认的子节点和后继节点字典。 - /// - private void Init() - { - //PreviousNodes = new Dictionary>(); - SuccessorNodes = new Dictionary>(); - foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) - { - //PreviousNodes[ctType] = new List(); - SuccessorNodes[ctType] = new List(); - } - } - - - private enum ActionType - { - Action, - Task, - } - private ActionType actionType = ActionType.Action; - - /// - /// 设置调用节点的Action,表示该节点执行一个同步操作。 - /// - /// - public void SetAction(Action action) - { - this.action = action; - actionType = ActionType.Action; - } - - /// - /// 设置调用节点的异步函数,表示该节点执行一个异步操作。 - /// - /// - public void SetAction(Func taskFunc) - { - this.taskFunc = taskFunc; - actionType = ActionType.Task; - } - - - /// - /// 对应的节点 - /// - public string Guid { get; } - -#if false - - /// - /// 不同分支的父节点(流程调用) - /// - public Dictionary> PreviousNodes { get; private set; } - -#endif - /// - /// 不同分支的子节点(流程调用) - /// - public Dictionary> SuccessorNodes { get; private set; } - - /// - /// 子节点数组,分为四个分支:上游、成功、失败、错误,每个分支最多支持16个子节点。 - /// - public CallNode[][] ChildNodes { get; private set; } = new CallNode[][] - { - new CallNode[MaxChildNodeCount], - new CallNode[MaxChildNodeCount], - new CallNode[MaxChildNodeCount], - new CallNode[MaxChildNodeCount] - }; - private const int MaxChildNodeCount = 16; // 每个分支最多支持16个子节点 - - - /// - /// 获取指定类型的子节点数量。 - /// - /// - /// - public int GetCount(ConnectionInvokeType type) - { - if (type == ConnectionInvokeType.Upstream) return UpstreamNodeCount; - if (type == ConnectionInvokeType.IsSucceed) return IsSuccessorNodeCount; - if (type == ConnectionInvokeType.IsFail) return IsFailNodeCount; - if (type == ConnectionInvokeType.IsError) return IsErrorNodeCount; - return 0; - } - - /// - /// 获取当前节点的子节点数量。 - /// - public int UpstreamNodeCount { get; private set; } = 0; - - /// - /// 获取当前节点的成功后继子节点数量。 - /// - public int IsSuccessorNodeCount { get; private set; } = 0; - /// - /// 获取当前节点的失败后继子节点数量。 - /// - public int IsFailNodeCount { get; private set; } = 0; - /// - /// 获取当前节点的错误后继子节点数量。 - /// - public int IsErrorNodeCount { get; private set; } = 0; - - /// - /// 添加一个上游子节点到当前节点。 - /// - /// - /// - public CallNode AddChildNodeUpstream(CallNode callNode) - { - var connectionInvokeType = ConnectionInvokeType.Upstream; - ChildNodes[(int)connectionInvokeType][UpstreamNodeCount++] = callNode; - SuccessorNodes[connectionInvokeType].Add(callNode); - return this; - } - - /// - /// 添加一个成功后继子节点到当前节点。 - /// - /// - /// - public CallNode AddChildNodeSucceed(CallNode callNode) - { - ChildNodes[0][UpstreamNodeCount++] = callNode; - - var connectionInvokeType = ConnectionInvokeType.IsSucceed; - ChildNodes[(int)connectionInvokeType][IsSuccessorNodeCount++] = callNode; - SuccessorNodes[connectionInvokeType].Add(callNode); - - return this; - } - /// - /// 添加一个失败后继子节点到当前节点。 - /// - /// - /// - public CallNode AddChildNodeFail(CallNode callNode) - { - var connectionInvokeType = ConnectionInvokeType.IsFail; - ChildNodes[(int)connectionInvokeType][IsFailNodeCount++] = callNode; - SuccessorNodes[connectionInvokeType].Add(callNode); - - return this; - } - - /// - /// 添加一个错误后继子节点到当前节点。 - /// - /// - /// - public CallNode AddChildNodeError(CallNode callNode) - { - var connectionInvokeType = ConnectionInvokeType.IsError; - ChildNodes[(int)connectionInvokeType][IsErrorNodeCount++] = callNode; - SuccessorNodes[connectionInvokeType].Add(callNode); - return this; - } - - - /// - /// 调用 - /// - /// - /// - /// - /// - public async Task InvokeAsync(IFlowContext context, CancellationToken token) - { - if (token.IsCancellationRequested) - { - return; - } - if (actionType == ActionType.Action) - { - action.Invoke(context); - } - else if (actionType == ActionType.Task) - { - await taskFunc.Invoke(context); - } - else - { - throw new InvalidOperationException($"生成了错误的CallNode。【{Guid}】"); - } - } - - private static readonly ObjectPool> _stackPool = new ObjectPool>(() => new Stack()); - - - /// - /// 开始执行 - /// - /// - /// 流程运行 - /// - public async Task StartFlowAsync(IFlowContext context, CancellationToken token) - { - var stack = _stackPool.Allocate(); - stack.Push(this); - while (true) - { - if (token.IsCancellationRequested) - { - throw new Exception($"流程执行被取消,未能获取到流程结果。"); - } - - #region 执行相关 - // 从栈中弹出一个节点作为当前节点进行处理 - var currentNode = stack.Pop(); - context.NextOrientation = ConnectionInvokeType.None; // 重置上下文状态 - FlowResult flowResult = null; - try - { - context.NextOrientation = ConnectionInvokeType.IsSucceed; // 默认执行成功 - await currentNode.InvokeAsync(context, token); - } - catch (Exception ex) - { - flowResult = FlowResult.Fail(currentNode.Guid, context, ex.Message); - context.Env.WriteLine(InfoType.ERROR, $"节点[{currentNode}]异常:" + ex); - context.NextOrientation = ConnectionInvokeType.IsError; - context.ExceptionOfRuning = ex; - } - #endregion - - #region 执行完成时更新栈 - // 首先将指定类别后继分支的所有节点逆序推入栈中 - var nextNodes = currentNode.SuccessorNodes[context.NextOrientation]; - for (int index = nextNodes.Count - 1; index >= 0; index--) - { - var node = nextNodes[index]; - context.SetPreviousNode(node.Guid, currentNode.Guid); - stack.Push(node); - } - - // 然后将指上游分支的所有节点逆序推入栈中 - var upstreamNodes = currentNode.SuccessorNodes[ConnectionInvokeType.Upstream]; - for (int index = upstreamNodes.Count - 1; index >= 0; index--) - { - var node = upstreamNodes[index]; - context.SetPreviousNode(node.Guid, currentNode.Guid); - stack.Push(node); - } - #endregion - - #region 执行完成后检查 - - if (stack.Count == 0) - { - _stackPool.Free(stack); - flowResult = context.GetFlowData(currentNode.Guid); - return flowResult; // 说明流程到了终点 - } - - if (context.RunState == RunState.Completion) - { - - _stackPool.Free(stack); - context.Env.WriteLine(InfoType.INFO, $"流程执行到节点[{currentNode.Guid}]时提前结束,将返回当前执行结果。"); - flowResult = context.GetFlowData(currentNode.Guid); - return flowResult; // 流程执行完成,返回结果 - } - - if (token.IsCancellationRequested) - { - _stackPool.Free(stack); - throw new Exception($"流程执行到节点[{currentNode.Guid}]时被取消,未能获取到流程结果。"); - } - - - #endregion - } - - } - } - - - /// - /// 流程调用树接口,提供获取CallNode的方法。 - /// - public interface IFlowCallTree - { - /// - /// 获取指定Key的CallNode,如果不存在则返回null。 - /// - /// - /// - CallNode Get(string key); - } - - /// - /// 轻量级流程控制器 - /// - public class LightweightFlowControl : IFlowControl - { - private readonly IFlowCallTree flowCallTree; - private readonly IFlowEnvironment flowEnvironment; - - /// - /// 轻量级流程上下文池,使用对象池模式来管理流程上下文的创建和回收。 - /// - public static Serein.Library.Utils.ObjectPool FlowContextPool { get; set; } - - /// - /// 单例IOC容器,用于依赖注入和服务定位。 - /// - public ISereinIOC IOC => throw new NotImplementedException(); - - /// - /// 轻量级流程控制器构造函数,接受流程调用树和流程环境作为参数。 - /// - /// - /// - public LightweightFlowControl(IFlowCallTree flowCallTree, IFlowEnvironment flowEnvironment) - { - this.flowCallTree = flowCallTree; - this.flowEnvironment = flowEnvironment; - FlowContextPool = new Utils.ObjectPool(() => - { - return new FlowContext(flowEnvironment); - }); - } - - /// - public Task InvokeAsync(string apiGuid, Dictionary dict) - { - throw new NotImplementedException(); - } - /// - public Task InvokeAsync(string apiGuid, Dictionary dict) - { - throw new NotImplementedException(); - } - - - //private readonly DefaultObjectPool _stackPool = new DefaultObjectPool(new DynamicContext(this)); - - /// - public async Task StartFlowAsync(string startNodeGuid) - { - IFlowContext context = Serein.Library.LightweightFlowControl.FlowContextPool.Allocate(); - CancellationTokenSource cts = new CancellationTokenSource(); - FlowResult flowResult; -#if DEBUG - flowResult = await BenchmarkHelpers.BenchmarkAsync(async () => - { - var node = flowCallTree.Get(startNodeGuid); - var flowResult = await node.StartFlowAsync(context, cts.Token); - return flowResult; - }); -#else - var node = flowCallTree.Get(startNodeGuid); - try - { - flowResult = await node.StartFlowAsync(context, cts.Token); - } - catch (global::System.Exception) - { - throw; - } - finally - { - context.Reset(); - FlowContextPool.Free(context); - } -#endif - - cts?.Cancel(); - cts?.Dispose(); - if (flowResult.Value is TResult result) - { - return result; - } - else if (flowResult is FlowResult && flowResult is TResult result2) - { - return result2; - } - else - { - throw new ArgumentNullException($"类型转换失败,流程返回数据与泛型不匹配,当前返回类型为[{flowResult.Value.GetType().FullName}]。"); - } - } - - /// - public Task StartFlowAsync(string[] canvasGuids) - { - throw new NotImplementedException(); - } - - /// - public Task ExitFlowAsync() - { - throw new NotImplementedException(); - } - - #region 无须实现 - /// - public void ActivateFlipflopNode(string nodeGuid) - { - throw new NotImplementedException(); - } - /// - public void MonitorObjectNotification(string nodeGuid, object monitorData, MonitorObjectEventArgs.ObjSourceType sourceType) - { - throw new NotImplementedException(); - } - - - /// - public void TerminateFlipflopNode(string nodeGuid) - { - throw new NotImplementedException(); - } - /// - public void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type) - { - throw new NotImplementedException(); - } - /// - public void UseExternalIOC(ISereinIOC ioc) - { - throw new NotImplementedException(); - } - /// - public void UseExternalIOC(ISereinIOC ioc, Action setDefultMemberOnReset = null) - { - throw new NotImplementedException(); - } - #endregion - } - - - /// - /// 轻量级流程环境事件实现 - /// - public class LightweightFlowEnvironmentEvent : IFlowEnvironmentEvent - { - /// - public event LoadDllHandler DllLoad; - /// - public event ProjectLoadedHandler ProjectLoaded; - /// - public event ProjectSavingHandler ProjectSaving; - /// - public event NodeConnectChangeHandler NodeConnectChanged; - /// - public event CanvasCreateHandler CanvasCreated; - /// - public event CanvasRemoveHandler CanvasRemoved; - /// - public event NodeCreateHandler NodeCreated; - /// - public event NodeRemoveHandler NodeRemoved; - /// - public event NodePlaceHandler NodePlace; - /// - public event NodeTakeOutHandler NodeTakeOut; - /// - public event StartNodeChangeHandler StartNodeChanged; - /// - public event FlowRunCompleteHandler FlowRunComplete; - /// - public event MonitorObjectChangeHandler MonitorObjectChanged; - /// - public event NodeInterruptStateChangeHandler NodeInterruptStateChanged; - /// - public event ExpInterruptTriggerHandler InterruptTriggered; - /// - public event IOCMembersChangedHandler IOCMembersChanged; - /// - public event NodeLocatedHandler NodeLocated; - /// - public event EnvOutHandler EnvOutput; - /// - public void OnDllLoad(LoadDllEventArgs eventArgs) - { - DllLoad?.Invoke(eventArgs); - } - /// - public void OnProjectLoaded(ProjectLoadedEventArgs eventArgs) - { - ProjectLoaded?.Invoke(eventArgs); - } - /// - public void OnProjectSaving(ProjectSavingEventArgs eventArgs) - { - ProjectSaving?.Invoke(eventArgs); - } - /// - public void OnNodeConnectChanged(NodeConnectChangeEventArgs eventArgs) - { - NodeConnectChanged?.Invoke(eventArgs); - } - /// - public void OnCanvasCreated(CanvasCreateEventArgs eventArgs) - { - CanvasCreated?.Invoke(eventArgs); - } - /// - public void OnCanvasRemoved(CanvasRemoveEventArgs eventArgs) - { - CanvasRemoved?.Invoke(eventArgs); - } - /// - public void OnNodeCreated(NodeCreateEventArgs eventArgs) - { - NodeCreated?.Invoke(eventArgs); - } - /// - public void OnNodeRemoved(NodeRemoveEventArgs eventArgs) - { - NodeRemoved?.Invoke(eventArgs); - } - /// - public void OnNodePlace(NodePlaceEventArgs eventArgs) - { - NodePlace?.Invoke(eventArgs); - } - /// - public void OnNodeTakeOut(NodeTakeOutEventArgs eventArgs) - { - NodeTakeOut?.Invoke(eventArgs); - } - /// - public void OnStartNodeChanged(StartNodeChangeEventArgs eventArgs) - { - StartNodeChanged?.Invoke(eventArgs); - } - /// - public void OnFlowRunComplete(FlowEventArgs eventArgs) - { - FlowRunComplete?.Invoke(eventArgs); - } - /// - public void OnMonitorObjectChanged(MonitorObjectEventArgs eventArgs) - { - MonitorObjectChanged?.Invoke(eventArgs); - } - /// - public void OnNodeInterruptStateChanged(NodeInterruptStateChangeEventArgs eventArgs) - { - NodeInterruptStateChanged?.Invoke(eventArgs); - } - /// - public void OnInterruptTriggered(InterruptTriggerEventArgs eventArgs) - { - InterruptTriggered?.Invoke(eventArgs); - } - /// - public void OnIOCMembersChanged(IOCMembersChangedEventArgs eventArgs) - { - IOCMembersChanged?.Invoke(eventArgs); - } - /// - public void OnNodeLocated(NodeLocatedEventArgs eventArgs) - { - NodeLocated?.Invoke(eventArgs); - } - /// - public void OnEnvOutput(InfoType type, string value) - { - EnvOutput?.Invoke(type, value); - } - - } - - /// - /// 轻量级流程环境实现 - /// - public class LightweightFlowEnvironment : IFlowEnvironment - { - /// - /// 轻量级流程环境构造函数,接受一个流程环境事件接口。 - /// - /// - public LightweightFlowEnvironment(IFlowEnvironmentEvent lightweightFlowEnvironmentEvent) - { - this.Event = lightweightFlowEnvironmentEvent; - } - /// - - public void WriteLine(InfoType type, string message, InfoClass @class = InfoClass.Debug) - { - Console.WriteLine(message); - } - - /// - public ISereinIOC IOC => throw new NotImplementedException(); - /// - public IFlowEdit FlowEdit => throw new NotImplementedException(); - /// - public IFlowControl FlowControl => throw new NotImplementedException(); - /// - public IFlowEnvironmentEvent Event { get; private set; } - /// - public string EnvName => throw new NotImplementedException(); - /// - public string ProjectFileLocation => throw new NotImplementedException(); - /// - public bool _IsGlobalInterrupt => throw new NotImplementedException(); - /// - public bool IsControlRemoteEnv => throw new NotImplementedException(); - /// - public InfoClass InfoClass { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - /// - public RunState FlowState { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - /// - public IFlowEnvironment CurrentEnv => throw new NotImplementedException(); - /// - public UIContextOperation UIContextOperation => throw new NotImplementedException(); - - /* public Task<(bool, RemoteMsgUtil)> ConnectRemoteEnv(string addres, int port, string token) - { - throw new NotImplementedException(); - }*/ - /// - public void ExitRemoteEnv() - { - throw new NotImplementedException(); - } - /// - public Task GetEnvInfoAsync() - { - throw new NotImplementedException(); - } - /// - public SereinProjectData GetProjectInfoAsync() - { - throw new NotImplementedException(); - } - /// - public void LoadAllNativeLibraryOfRuning(string path, bool isRecurrence = true) - { - throw new NotImplementedException(); - } - /// - public void LoadLibrary(string dllPath) - { - throw new NotImplementedException(); - } - /// - public bool LoadNativeLibraryOfRuning(string file) - { - throw new NotImplementedException(); - } - /// - public void LoadProject(string filePath) - { - throw new NotImplementedException(); - } - /// - public Task LoadProjetAsync(string filePath) - { - throw new NotImplementedException(); - } - /// - public Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value) - { - throw new NotImplementedException(); - } - /// - public void SaveProject() - { - throw new NotImplementedException(); - } - /// - public void SetUIContextOperation(UIContextOperation uiContextOperation) - { - throw new NotImplementedException(); - } - /// - public Task StartRemoteServerAsync(int port = 7525) - { - throw new NotImplementedException(); - } - /// - public void StopRemoteServer() - { - throw new NotImplementedException(); - } - /// - public bool TryGetDelegateDetails(string assemblyName, string methodName, out DelegateDetails del) - { - throw new NotImplementedException(); - } - /// - public bool TryGetMethodDetailsInfo(string assemblyName, string methodName, out MethodDetailsInfo mdInfo) - { - throw new NotImplementedException(); - } - /// - public bool TryGetNodeModel(string nodeGuid, out IFlowNode nodeModel) - { - throw new NotImplementedException(); - } - /// - public bool TryUnloadLibrary(string assemblyFullName) - { - throw new NotImplementedException(); - } - - - } -} diff --git a/Library/Serein.Library.csproj b/Library/Serein.Library.csproj index 96328fd..9a0c6ae 100644 --- a/Library/Serein.Library.csproj +++ b/Library/Serein.Library.csproj @@ -59,6 +59,7 @@ + diff --git a/NodeFlow/Env/FlowControl.cs b/NodeFlow/Env/FlowControl.cs index 9001e51..7d5ba80 100644 --- a/NodeFlow/Env/FlowControl.cs +++ b/NodeFlow/Env/FlowControl.cs @@ -274,6 +274,67 @@ namespace Serein.NodeFlow.Env } } + + /// + public async Task StartFlowAsync(string startNodeGuid) + { + var sw = Stopwatch.StartNew(); + var checkpoints = new Dictionary(); + var flowWorkManagement = GetFWM(); + if (!flowModelService.TryGetNodeModel(startNodeGuid, out IFlowNode? nodeModel)) + { + throw new Exception($"节点不存在【{startNodeGuid}】"); + } + if(nodeModel is SingleFlipflopNode) + { + throw new Exception("不能从[Flipflop]节点开始"); + } + + + var flowContextPool = flowWorkManagement.WorkOptions.FlowContextPool; + var context = flowContextPool.Allocate(); + checkpoints["准备调用环境"] = sw.Elapsed; + var flowResult = await nodeModel.StartFlowAsync(context, flowWorkManagement.WorkOptions.CancellationTokenSource.Token); // 开始运行时从选定节点开始运行 + checkpoints["调用节点流程"] = sw.Elapsed; + + var last = TimeSpan.Zero; + foreach (var kv in checkpoints) + { + SereinEnv.WriteLine(InfoType.INFO, $"{kv.Key} 耗时: {(kv.Value - last).TotalMilliseconds} ms"); + last = kv.Value; + } + //await BenchmarkHelpers.BenchmarkAsync(flowTaskManagement.StartFlowInSelectNodeAsync(nodeModel)); + if (context.IsRecordInvokeInfo) + { + var invokeInfos = context.GetAllInvokeInfos(); + _ = Task.Delay(100).ContinueWith(async (task) => + { + await task; + if (invokeInfos.Count < 255) + { + foreach (var info in invokeInfos) + { + SereinEnv.WriteLine(InfoType.INFO, info.ToString()); + } + } + else + { + double total = 0; + for (int i = 0; i < invokeInfos.Count; i++) + { + total += invokeInfos[i].TS.TotalSeconds; + } + SereinEnv.WriteLine(InfoType.INFO, $"运行次数:{invokeInfos.Count}"); + SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{total / invokeInfos.Count}"); + SereinEnv.WriteLine(InfoType.INFO, $"总耗时:{total}"); + } + }); + } + context.Reset(); + flowContextPool.Free(context); + ReturnFWM(flowWorkManagement); // 释放流程任务管理器 + } + /// public Task ExitFlowAsync() diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index c8d25a8..07c3bf1 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -1,10 +1,8 @@ using Serein.Extend.NewtonsoftJson; using Serein.Library; using Serein.Library.Api; -using Serein.Library.FlowNode; using Serein.Library.Utils; using Serein.NodeFlow.Services; -using System.Reflection; namespace Serein.NodeFlow.Env { diff --git a/NodeFlow/Services/FlowCoreGenerateService.cs b/NodeFlow/Services/FlowCoreGenerateService.cs index 8bdfb5e..b30226c 100644 --- a/NodeFlow/Services/FlowCoreGenerateService.cs +++ b/NodeFlow/Services/FlowCoreGenerateService.cs @@ -1,15 +1,12 @@ -using Serein.Library; +using Microsoft.CodeAnalysis; +using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; -using Serein.NodeFlow.Model; using Serein.NodeFlow.Model.Infos; using Serein.NodeFlow.Model.Nodes; using Serein.Script; -using Serein.Script.Node; -using System.ComponentModel.DataAnnotations; using System.Reflection; using System.Runtime.CompilerServices; -using System.Security.Cryptography; using System.Text; namespace Serein.NodeFlow.Services @@ -45,6 +42,7 @@ namespace Serein.NodeFlow.Services var flowNodes = flowModelService.GetAllNodeModel().ToArray(); + var flowCanvass = flowModelService.GetAllCanvasModel().ToArray(); // 收集程序集信息 foreach (var node in flowNodes) { @@ -53,10 +51,8 @@ namespace Serein.NodeFlow.Services { assemblyFlowClasss.Add(instanceType); } - } - var scriptNodes = flowModelService.GetAllNodeModel().Where(n => n.ControlType == NodeControlType.Script).OfType().ToArray(); GenerateScript_InitSereinScriptMethodInfos(scriptNodes); // 初始化脚本方法 @@ -73,9 +69,17 @@ namespace Serein.NodeFlow.Services string flowApiInterfaceName = $"IFlowApiInvoke"; // 类名 stringBuilder.AppendCode(0, $"public class {flowTemplateClassName} : {flowApiInterfaceName}, global::{typeof(IFlowCallTree).FullName}"); stringBuilder.AppendCode(0, $"{{"); + + // 生成 IFlowCallTree 接口 + var listNodes = $"global::System.Collections.Generic.List<{typeof(CallNode).FullName}>"; + stringBuilder.AppendCode(1, $"public {listNodes} {nameof(IFlowCallTree.StartNodes)} {{ get; }} = new {listNodes}();"); + stringBuilder.AppendCode(1, $"public {listNodes} {nameof(IFlowCallTree.GlobalFlipflopNodes)} {{get; }} = new {listNodes}();"); + + GenerateCtor(stringBuilder, flowTemplateClassName, assemblyFlowClasss); // 生成构造方法 GenerateInitMethod(stringBuilder); // 生成初始化方法 - GenerateCallTree(stringBuilder, flowNodes); // 生成调用树 + GenerateCallTree(stringBuilder, flowNodes, flowCanvass); // 生成调用树 + Generate_InitAndStart(stringBuilder); // 生成 InitAndStartAsync GenerateNodeIndexLookup(stringBuilder, flowTemplateClassName, flowNodes); // 初始化节点缓存 // 生成每个节点的方法 @@ -85,7 +89,7 @@ namespace Serein.NodeFlow.Services } // 生成实现流程接口的实现方法 - var flowApiInfos = flowApiMethodInfos.Values.ToArray(); + var flowApiInfos = _flowApiMethodInfos.Values.ToArray(); foreach (var info in flowApiInfos) { stringBuilder.AppendCode(2, info.ToObjPoolSignature()); @@ -98,7 +102,7 @@ namespace Serein.NodeFlow.Services // 载入脚本节点转换的C#代码(载入类) - var scriptInfos = scriptMethodInfos.Values.ToArray(); + var scriptInfos = _scriptMethodInfos.Values.ToArray(); foreach (var info in scriptInfos) { stringBuilder.AppendCode(2, info.CsharpCode); @@ -168,10 +172,11 @@ namespace Serein.NodeFlow.Services if (flowNode.ControlType == NodeControlType.Action && flowNode is SingleActionNode actionNode) { - CreateMethodCore_Action(sb_main, actionNode, flowContextTypeName, flowContext); + CreateMethodCore_ActionOrFliplop(sb_main, actionNode, flowContextTypeName, flowContext); } - else if (flowNode.ControlType == NodeControlType.Flipflop) + else if (flowNode.ControlType == NodeControlType.Flipflop && flowNode is SingleFlipflopNode flipflopNode) { + CreateMethodCore_ActionOrFliplop(sb_main, flipflopNode, flowContextTypeName, flowContext); } else if (flowNode.ControlType == NodeControlType.Script && flowNode is SingleScriptNode singleScriptNode) { @@ -196,7 +201,6 @@ namespace Serein.NodeFlow.Services throw new Exception("无法为该节点生成调用逻辑"); } - /// /// 生成初始化方法(用于执行构造函数中无法完成的操作) /// @@ -215,16 +219,17 @@ namespace Serein.NodeFlow.Services /// /// /// - private void GenerateCallTree(StringBuilder sb, IFlowNode[] flowNodes) + private void GenerateCallTree(StringBuilder sb, IFlowNode[] flowNodes, FlowCanvasDetails[] flowCanvass) { // Get("0fa6985b-4b63-4499-80b2-76401669292d").AddChildNodeSucceed(Get("acdbe7ea-eb27-4a3e-9cc9-c48f642ee4f5")); sb.AppendCode(2, $"private void {nameof(GenerateCallTree)}()"); sb.AppendCode(2, $"{{"); - + #region 设置节点回调 foreach (var node in flowNodes) { var nodeMethod = node.ToNodeMethodName(); // 节点对应的方法名称 - if (node.ControlType == NodeControlType.Action + if (node.ControlType == NodeControlType.Action + || node.ControlType == NodeControlType.Flipflop || node.ControlType == NodeControlType.FlowCall || node.ControlType == NodeControlType.Script ) @@ -241,6 +246,8 @@ namespace Serein.NodeFlow.Services } + #endregion + #region 设置调用顺序 var cts = NodeStaticConfig.ConnectionTypes; foreach (var node in flowNodes) { @@ -287,9 +294,29 @@ namespace Serein.NodeFlow.Services } } + #endregion + + #region 实现接口 + var startNodeGuids = flowCanvass.Where(canvas => canvas.StartNode is not null).Select(canvas => canvas.StartNode!.Guid).ToList(); + foreach (var startNodeGuid in startNodeGuids) + { + sb.AppendCode(3, $"{nameof(IFlowCallTree.StartNodes)}.Add(Get(\"{startNodeGuid}\")); // 添加起始节点"); + } + + var flipflopNodeGuids = flowNodes.Where(node => node.ControlType == NodeControlType.Flipflop) + .OfType() + .Where(node => node.IsRoot()) + .ToList(); + foreach (var flipflopNodeGuid in flipflopNodeGuids) + { + sb.AppendCode(3, $"{nameof(IFlowCallTree.GlobalFlipflopNodes)}.Add(Get(\"{flipflopNodeGuid.Guid}\")); // 添加全局触发器节点"); + } + #endregion sb.AppendCode(2, $"}}"); sb.AppendLine(); + + /*string? dynamicContextTypeName = typeof(IDynamicContext).FullName; string? flowContext = nameof(flowContext); var callTreeType = typeof(IFlowCallTree); @@ -400,6 +427,43 @@ namespace Serein.NodeFlow.Services sb.AppendCode(2, $"}}"); } + private void Generate_InitAndStart(StringBuilder sb) + { + string value = +""" +/// +/// 初始化并启动流程控制器,遍历所有的起始节点并启动对应的流程,同时处理全局触发器节点。 +/// +/// +public async global::System.Threading.Tasks.Task InitAndStartAsync(global::System.Threading.CancellationToken token) +{ + var startNodes = StartNodes.ToArray(); + foreach (var startNode in startNodes) + { + await flowEnvironment.FlowControl.StartFlowAsync(startNode.Guid); + } + var globalFlipflopNodes = GlobalFlipflopNodes.ToArray(); + var tasks = globalFlipflopNodes.Select(async node => + { + while (!token.IsCancellationRequested) + { + try + { + await flowEnvironment.FlowControl.StartFlowAsync(node.Guid); + } + catch (global::Serein.Library.FlipflopException ex) + { + if (ex.Type == global::Serein.Library.FlipflopException.CancelClass.CancelFlow) + break; + } + } + }); + await Task.WhenAll(tasks); +} +"""; + sb.AppendLine(value); + } + #region 节点方法生成 /// @@ -410,7 +474,7 @@ namespace Serein.NodeFlow.Services /// /// /// - private void CreateMethodCore_Action(StringBuilder sb_main, SingleActionNode actionNode, string? flowContextTypeName, string flowContext) + private void CreateMethodCore_ActionOrFliplop(StringBuilder sb_main, IFlowNode actionNode, string? flowContextTypeName, string flowContext) { if (!flowLibraryService.TryGetMethodInfo(actionNode.MethodDetails.AssemblyName, actionNode.MethodDetails.MethodName, @@ -476,10 +540,18 @@ namespace Serein.NodeFlow.Services #region 非显式设置的参数以正常方式获取 if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) { - var previousNode = $"previousNode{index}"; + var valueType = pd.IsParams ? $"global::{pd.DataType.GetFriendlyName()}" : $"global::{paramtTypeFullName}"; - sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{actionNode.Guid}\");"); // 获取运行时上一节点Guid - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); + if (typeof(IFlowContext).IsAssignableFrom(pd.DataType)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {flowContext}; // 使用流程上下文"); + } + else + { + var previousNode = $"previousNode{index}"; + sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{actionNode.Guid}\");"); // 获取运行时上一节点Guid + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); + } } else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) { @@ -604,14 +676,14 @@ namespace Serein.NodeFlow.Services /// private void CreateMethodCore_FlowCall(StringBuilder sb_main, SingleFlowCallNode flowCallNode, string? flowContextTypeName, string flowContext) { - if (!flowApiMethodInfos.TryGetValue(flowCallNode, out var flowApiMethodInfo)) + if (!_flowApiMethodInfos.TryGetValue(flowCallNode, out var flowApiMethodInfo)) { return; } if (flowCallNode.TargetNode is SingleScriptNode singleScriptNode) { - if (!scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) + if (!_scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) { return; } @@ -796,7 +868,7 @@ namespace Serein.NodeFlow.Services { - if (!scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) + if (!_scriptMethodInfos.TryGetValue(singleScriptNode, out var scriptMethodInfo)) { return; } @@ -854,10 +926,22 @@ namespace Serein.NodeFlow.Services #region 非显式设置的参数以正常方式获取 if (pd.ArgDataSourceType == ConnectionArgSourceType.GetPreviousNodeData) { - var previousNode = $"previousNode{index}"; var valueType = pd.IsParams ? $"global::{pd.DataType.GetFriendlyName()}" : $"global::{paramtTypeFullName}"; - sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{singleScriptNode.Guid}\");"); // 获取运行时上一节点Guid - sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); + if (typeof(IFlowContext).IsAssignableFrom(pd.DataType)) + { + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {flowContext}; // 使用流程上下文"); + } + else + { + var previousNode = $"previousNode{index}"; + sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{singleScriptNode.Guid}\");"); // 获取运行时上一节点Guid + sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); + } + + // var previousNode = $"previousNode{index}"; + // var valueType = pd.IsParams ? $"global::{pd.DataType.GetFriendlyName()}" : $"global::{paramtTypeFullName}"; + // sb_invoke_login.AppendCode(3, $"global::System.String {previousNode} = {flowContext}.GetPreviousNode(\"{singleScriptNode.Guid}\");"); // 获取运行时上一节点Guid + // sb_invoke_login.AppendCode(3, $"{valueType} value{index} = {previousNode} == null ? default : ({valueType}){flowContext}.{nameof(IFlowContext.GetFlowData)}({previousNode}).Value; // 获取运行时上一节点的数据"); } else if (pd.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) { @@ -973,56 +1057,24 @@ namespace Serein.NodeFlow.Services #endregion + #region 全局触发器与分支触发器生成 + + /*private Dictionary _flipflopNodeInfos = []; + + private class SereinFlipflopMethodInfo(bool isGlobal) + { + public bool IsGlobal { get; } = isGlobal; + } + private void GenerateFlipflop_InitSereinFlipflopMethodInfos(SingleFlipflopNode[]) + { + }*/ + + #endregion + #region 全局节点的代码生成 - private Dictionary globalDataInfos = []; + private Dictionary _globalDataInfos = []; private const string FlowGlobalData = nameof(FlowGlobalData); - - private void GenerateGlobalData_InitSereinGlobalDataInfos(SingleGlobalDataNode[] globalDataNodes) - { - foreach(var node in globalDataNodes) - { - var keyName = node.KeyName; - var dataNode = node.DataNode; - if(dataNode is null) - { - throw new Exception($"全局数据节点[{node}]没有指定数据来源节点"); - } - var type = dataNode.MethodDetails.ReturnType; - if (type is null || type == typeof(void)) - { - throw new Exception($"全局数据节点[{node}]无返回值"); - } - globalDataInfos[node] = new SereinGlobalDataInfo - { - Node = node, - DataSourceNode = dataNode, - KeyName = keyName, - DataType = type, - }; - } - } - - /// - /// 生成数据实体类 - /// - /// - private void GenerateGlobalData_ToClass(StringBuilder sb) - { - var infos = globalDataInfos.Values.ToArray(); - sb.AppendCode(1, $"public sealed class {FlowGlobalData}"); - sb.AppendCode(1, $"{{"); - foreach (var info in infos) - { - var xmlDescription = $"{$"全局数据,来源于[{info.Node.MethodDetails?.MethodName}]{info.Node.Guid}".ToXmlComments(2)}"; - sb.AppendCode(2, xmlDescription); - sb.AppendCode(2, $"public global::{info.DataType.FullName} {info.KeyName} {{ get; set; }};"); - } - sb.AppendLine(); - sb.AppendCode(1, $"}}"); - } - - private class SereinGlobalDataInfo { /// @@ -1046,13 +1098,58 @@ namespace Serein.NodeFlow.Services } + private void GenerateGlobalData_InitSereinGlobalDataInfos(SingleGlobalDataNode[] globalDataNodes) + { + foreach(var node in globalDataNodes) + { + var keyName = node.KeyName; + var dataNode = node.DataNode; + if(dataNode is null) + { + throw new Exception($"全局数据节点[{node}]没有指定数据来源节点"); + } + var type = dataNode.MethodDetails.ReturnType; + if (type is null || type == typeof(void)) + { + throw new Exception($"全局数据节点[{node}]无返回值"); + } + _globalDataInfos[node] = new SereinGlobalDataInfo + { + Node = node, + DataSourceNode = dataNode, + KeyName = keyName, + DataType = type, + }; + } + } + + /// + /// 生成数据实体类 + /// + /// + private void GenerateGlobalData_ToClass(StringBuilder sb) + { + var infos = _globalDataInfos.Values.ToArray(); + sb.AppendCode(1, $"public sealed class {FlowGlobalData}"); + sb.AppendCode(1, $"{{"); + foreach (var info in infos) + { + var xmlDescription = $"{$"全局数据,来源于[{info.Node.MethodDetails?.MethodName}]{info.Node.Guid}".ToXmlComments(2)}"; + sb.AppendCode(2, xmlDescription); + sb.AppendCode(2, $"public global::{info.DataType.FullName} {info.KeyName} {{ get; set; }};"); + } + sb.AppendLine(); + sb.AppendCode(1, $"}}"); + } + + #endregion #region 脚本节点的代码生成 - private Dictionary scriptMethodInfos = []; + private Dictionary _scriptMethodInfos = []; private void GenerateScript_InitSereinScriptMethodInfos(SingleScriptNode[] flowCallNodes) { - scriptMethodInfos.Clear(); + _scriptMethodInfos.Clear(); bool isError = false; foreach(var node in flowCallNodes) { @@ -1065,7 +1162,7 @@ namespace Serein.NodeFlow.Services } else { - scriptMethodInfos[node] = info; + _scriptMethodInfos[node] = info; } } @@ -1082,7 +1179,7 @@ namespace Serein.NodeFlow.Services /// 流程接口节点与对应的流程方法信息 /// - private Dictionary flowApiMethodInfos = []; + private Dictionary _flowApiMethodInfos = []; /// /// 生成流程接口方法信息 @@ -1090,7 +1187,7 @@ namespace Serein.NodeFlow.Services /// private void GenerateFlowApi_InitFlowApiMethodInfos(SingleFlowCallNode[] flowCallNodes) { - flowApiMethodInfos.Clear(); + _flowApiMethodInfos.Clear(); flowCallNodes = flowCallNodes.Where(node => !string.IsNullOrWhiteSpace(node.TargetNodeGuid) && !flowModelService.ContainsCanvasModel(node.TargetNodeGuid)) .ToArray(); // 筛选流程接口节点,只生成有效的 @@ -1100,7 +1197,7 @@ namespace Serein.NodeFlow.Services var info = flowCallNode.ToFlowApiMethodInfo(); if (info is not null) { - flowApiMethodInfos[flowCallNode] = info; + _flowApiMethodInfos[flowCallNode] = info; } } } @@ -1123,7 +1220,7 @@ namespace Serein.NodeFlow.Services sb.AppendCode(1, $"public interface IFlowApiInvoke"); sb.AppendCode(1, $"{{"); - var infos = flowApiMethodInfos.Values.ToArray(); + var infos = _flowApiMethodInfos.Values.ToArray(); foreach (var info in infos) { var xmlDescription = $"{$"流程接口,{info.NodeModel.MethodDetails.MethodAnotherName}".ToXmlComments(2)}"; @@ -1151,7 +1248,7 @@ namespace Serein.NodeFlow.Services /// private void GenerateFlowApi_ApiParamClass(StringBuilder sb) { - var infos = flowApiMethodInfos.Values.ToArray(); + var infos = _flowApiMethodInfos.Values.ToArray(); foreach (var info in infos) { sb.AppendLine(info.ToParamterClassSignature()); diff --git a/Serein.Script/SereinScriptToCsharpScript.cs b/Serein.Script/SereinScriptToCsharpScript.cs index 34a1b8c..64eb0e9 100644 --- a/Serein.Script/SereinScriptToCsharpScript.cs +++ b/Serein.Script/SereinScriptToCsharpScript.cs @@ -230,18 +230,27 @@ namespace Serein.Script else { Append($"\""); - foreach (var s in sp) + for (int index = 0; index < sp.Length; index++) { + string? s = sp[index]; var content = EscapeForCSharpString(s); - if(OperatingSystem.IsWindows()) + if(index == 0) { - Append($"\\r\\n{content}"); + Append(content); } - else if (OperatingSystem.IsLinux()) + else { - Append($"\\n{content}"); + if (OperatingSystem.IsWindows()) + { + Append($"\\r\\n{content}"); + } + else if (OperatingSystem.IsLinux()) + { + Append($"\\n{content}"); + } } + } Append($"\""); }