流程接口节点新增了对脚本节点的支持

This commit is contained in:
fengjiayi
2025-05-30 15:42:59 +08:00
parent 9c4e5b2735
commit f0eb11c914
12 changed files with 234 additions and 150 deletions

View File

@@ -135,6 +135,18 @@ namespace Serein.Library
this.NodeModel = nodeModel;
}
public ParameterDetails(ParameterData pdInfo, int argIndex)
{
this.Index = argIndex;
this.DataType = typeof(object);
this.ArgDataSourceNodeGuid = pdInfo.SourceNodeGuid;
this.ArgDataSourceType = EnumHelper.ConvertEnum<ConnectionArgSourceType>(pdInfo.SourceType);
this.DataValue = pdInfo.Value;
this.InputType = ParameterValueInputType.Input;
this.IsExplicitData = pdInfo.State;
this.Name = pdInfo.ArgName;
}
/// <summary>
/// 通过参数信息加载实体,用于加载项目文件、远程连接的场景
/// </summary>
@@ -223,7 +235,7 @@ namespace Serein.Library
}
#endregion
#region -
if (ExplicitType.IsEnum && DataType != ExplicitType)
if (ExplicitType is not null && ExplicitType.IsEnum && DataType != ExplicitType)
{
var resultEnum = Enum.Parse(ExplicitType, DataValue);
// 获取绑定的类型

View File

@@ -878,61 +878,6 @@ namespace Serein.NodeFlow.Env
return false;
}
/// <summary>
/// 从节点信息创建节点,并返回状态指示是否创建成功
/// </summary>
/// <param name="nodeInfo"></param>
/// <returns></returns>
private bool CreateNodeFromNodeInfo(NodeInfo nodeInfo)
{
if (!EnumHelper.TryConvertEnum<NodeControlType>(nodeInfo.Type, out var controlType))
{
return false;
}
#region
MethodDetails? methodDetails;
if (controlType.IsBaseNode())
{
// 加载基础节点
methodDetails = new MethodDetails();
}
else
{
if (string.IsNullOrEmpty(nodeInfo.MethodName)) return false;
// 加载方法节点
FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息
}
#endregion
var nodeModel = FlowNodeExtension.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点
if (nodeModel is null)
{
nodeInfo.Guid = string.Empty;
return false;
}
if (FlowCanvass.TryGetValue(nodeInfo.CanvasGuid, out var canvasModel))
{
// 节点与画布互相绑定
// 需要在UI线程上进行添加否则会报 “不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改”异常
nodeModel.CanvasDetails = canvasModel;
UIContextOperation?.Invoke(() => canvasModel.Nodes.Add(nodeModel));
nodeModel.LoadInfo(nodeInfo); // 创建节点model
TryAddNode(nodeModel); // 加载项目时将节点加载到环境中
}
else
{
SereinEnv.WriteLine(InfoType.ERROR, $"加载节点[{nodeInfo.Guid}]时发生异常,画布[{nodeInfo.CanvasGuid}]不存在");
return false;
}
UIContextOperation?.Invoke(() =>
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeInfo.CanvasGuid, nodeModel, nodeInfo.Position))); // 添加到UI上
return true;
}
/// <summary>
/// 从节点信息集合批量加载节点控件
@@ -1127,13 +1072,6 @@ namespace Serein.NodeFlow.Env
// 通知UI更改
UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(canvasGuid, nodeModel, position)));
// 因为需要UI先布置了元素才能通知UI变更特效
// 如果不存在流程起始控件,默认设置为流程起始控件
if (canvasModel.StartNode is null)
{
SetStartNode(canvasModel, nodeModel);
}
var nodeInfo = nodeModel.ToInfo();
return Task.FromResult(nodeInfo);
}
@@ -1764,9 +1702,88 @@ namespace Serein.NodeFlow.Env
}*/
}*/
#endregion
/// <summary>
/// 从节点信息创建节点,并返回状态指示是否创建成功
/// </summary>
/// <param name="nodeInfo"></param>
/// <returns></returns>
private bool CreateNodeFromNodeInfo(NodeInfo nodeInfo)
{
if (!EnumHelper.TryConvertEnum<NodeControlType>(nodeInfo.Type, out var controlType))
{
return false;
}
#region
MethodDetails? methodDetails;
if (controlType == NodeControlType.FlowCall)
{
if (string.IsNullOrEmpty(nodeInfo.MethodName))
{
methodDetails = new MethodDetails();
methodDetails.ParamsArgIndex = 0;
methodDetails.ParameterDetailss = new ParameterDetails[nodeInfo.ParameterData.Length];
for (int i = 0; i < methodDetails.ParameterDetailss.Length; i++)
{
var pdInfo = nodeInfo.ParameterData[i];
var t = new ParameterDetailsInfo();
var pd = new ParameterDetails(pdInfo, i);
methodDetails.ParameterDetailss[i] = pd;
}
}
else
{
// 目标节点可能是方法节点
FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息
}
}
else if (controlType.IsBaseNode())
{
// 加载基础节点
methodDetails = new MethodDetails();
}
else
{
if (string.IsNullOrEmpty(nodeInfo.MethodName)) return false;
// 加载方法节点
FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息
}
#endregion
var nodeModel = FlowNodeExtension.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点
if (nodeModel is null)
{
nodeInfo.Guid = string.Empty;
return false;
}
if (FlowCanvass.TryGetValue(nodeInfo.CanvasGuid, out var canvasModel))
{
// 节点与画布互相绑定
// 需要在UI线程上进行添加否则会报 “不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改”异常
nodeModel.CanvasDetails = canvasModel;
UIContextOperation?.Invoke(() => canvasModel.Nodes.Add(nodeModel));
nodeModel.LoadInfo(nodeInfo); // 创建节点model
TryAddNode(nodeModel); // 加载项目时将节点加载到环境中
}
else
{
SereinEnv.WriteLine(InfoType.ERROR, $"加载节点[{nodeInfo.Guid}]时发生异常,画布[{nodeInfo.CanvasGuid}]不存在");
return false;
}
UIContextOperation?.Invoke(() =>
OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeInfo.CanvasGuid, nodeModel, nodeInfo.Position))); // 添加到UI上
return true;
}
/// <summary>
/// 移除连接关系
/// </summary>

View File

@@ -20,10 +20,7 @@ namespace Serein.NodeFlow
/// <returns></returns>
public static bool IsBaseNode(this NodeControlType nodeControlType)
{
if(nodeControlType == NodeControlType.FlowCall)
{
return false;
}
var nodeDesc = EnumHelper.GetAttribute<NodeControlType, DescriptionAttribute>(nodeControlType);
if("base".Equals(nodeDesc?.Description, StringComparison.OrdinalIgnoreCase))
{

View File

@@ -1,6 +1,7 @@
using Newtonsoft.Json.Linq;
using Serein.Library;
using Serein.Library.Api;
using Serein.Script;
using System;
using System.Collections.Generic;
using System.Dynamic;
@@ -88,10 +89,6 @@ namespace Serein.NodeFlow.Model
// 取消设置接口节点
targetNode.PropertyChanged -= TargetNode_PropertyChanged;
this.MethodDetails = new MethodDetails();
/*foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
this.SuccessorNodes[ctType] = new List<NodeModelBase>();
}*/
}
else
{
@@ -99,20 +96,21 @@ namespace Serein.NodeFlow.Model
if(!this.IsShareParam
&& CacheMethodDetails is not null
&& targetNode.MethodDetails.AssemblyName.Equals(CacheMethodDetails.AssemblyName)
&& targetNode.MethodDetails.MethodName.Equals(CacheMethodDetails.MethodName))
&& targetNode.MethodDetails is not null
&& targetNode.MethodDetails.AssemblyName == CacheMethodDetails.AssemblyName
&& targetNode.MethodDetails.MethodName == CacheMethodDetails.MethodName)
{
this.MethodDetails = CacheMethodDetails;
}
else
{
CacheMethodDetails = targetNode.MethodDetails.CloneOfNode(this); // 从目标节点复制一份
targetNode.PropertyChanged += TargetNode_PropertyChanged;
this.MethodDetails = CacheMethodDetails;
/*foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
if (targetNode.MethodDetails is not null)
{
this.SuccessorNodes[ctType] = targetNode.SuccessorNodes[ctType];
}*/
CacheMethodDetails = targetNode.MethodDetails.CloneOfNode(this); // 从目标节点复制一份
targetNode.PropertyChanged += TargetNode_PropertyChanged;
this.MethodDetails = CacheMethodDetails;
}
}
}
@@ -127,11 +125,20 @@ namespace Serein.NodeFlow.Model
}
if (value)
{
CacheMethodDetails = this.MethodDetails;
this.MethodDetails = targetNode.MethodDetails;
CacheMethodDetails = targetNode.MethodDetails.CloneOfNode(this);
this.MethodDetails = CacheMethodDetails;
}
else
{
if(targetNode.ControlType == NodeControlType.Script)
{
// 脚本节点入参需不可编辑入参数量、入参名称
foreach (var item in CacheMethodDetails.ParameterDetailss)
{
item.IsParams = false;
}
}
this.MethodDetails = CacheMethodDetails;
}
@@ -176,52 +183,6 @@ namespace Serein.NodeFlow.Model
}
/// <summary>
/// 需要调用其它流程图中的某个节点
/// </summary>
/// <param name="context"></param>
/// <param name="token"></param>
/// <returns></returns>
public override async Task<FlowResult> ExecutingAsync(IDynamicContext context, CancellationToken token)
{
if (!UploadTargetNode())
{
throw new ArgumentNullException();
}
if (IsShareParam)
{
this.MethodDetails = targetNode.MethodDetails;
}
this.SuccessorNodes = targetNode.SuccessorNodes;
var flowData = await base.ExecutingAsync(context, token);
if (IsShareParam)
{
// 设置运行时上一节点
// 此处代码与SereinFlow.Library.FlowNode.ParameterDetails
// ToMethodArgData()方法中判断流程接口节点分支逻辑耦合
// 不要轻易修改
context.AddOrUpdate(targetNode, flowData);
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
if (this.SuccessorNodes[ctType] == null) continue;
foreach (var node in this.SuccessorNodes[ctType])
{
if (node.DebugSetting.IsEnable)
{
context.SetPreviousNode(node, this);
}
}
}
}
return flowData;
}
/// <summary>
/// 保存全局变量的数据
/// </summary>
@@ -265,7 +226,61 @@ namespace Serein.NodeFlow.Model
CacheMethodDetails = null;
}
/// <summary>
/// 需要调用其它流程图中的某个节点
/// </summary>
/// <param name="context"></param>
/// <param name="token"></param>
/// <returns></returns>
public override async Task<FlowResult> ExecutingAsync(IDynamicContext context, CancellationToken token)
{
if (!UploadTargetNode())
{
throw new ArgumentNullException();
}
if (IsShareParam)
{
this.MethodDetails = targetNode.MethodDetails;
}
this.SuccessorNodes = targetNode.SuccessorNodes;
FlowResult flowData = await (targetNode.ControlType switch
{
NodeControlType.Script => ((SingleScriptNode)targetNode).ExecutingAsync(this, context, token),
_ => base.ExecutingAsync(context, token)
});
if (IsShareParam)
{
// 设置运行时上一节点
// 此处代码与SereinFlow.Library.FlowNode.ParameterDetails
// ToMethodArgData()方法中判断流程接口节点分支逻辑耦合
// 不要轻易修改
context.AddOrUpdate(targetNode, flowData);
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
if (this.SuccessorNodes[ctType] == null) continue;
foreach (var node in this.SuccessorNodes[ctType])
{
if (node.DebugSetting.IsEnable)
{
context.SetPreviousNode(node, this);
}
}
}
}
return flowData;
}
}
}

View File

@@ -33,17 +33,17 @@ namespace Serein.NodeFlow.Model
/// </summary>
public override bool IsBase => true;
private IScriptFlowApi ScriptFlowApi { get; }
private IScriptFlowApi ScriptFlowApi;
private ASTNode mainNode;
private SereinScriptInterpreter ScriptInterpreter;
private bool IsScriptChanged = false;
/// <summary>
/// 构建流程脚本节点
/// </summary>
/// <param name="environment"></param>
public SingleScriptNode(IFlowEnvironment environment):base(environment)
{
//ScriptFlowApi = environment.IOC.Get<ScriptFlowApi>();
ScriptFlowApi = new ScriptFlowApi(environment, this);
ScriptInterpreter = new SereinScriptInterpreter();
}
@@ -64,7 +64,19 @@ namespace Serein.NodeFlow.Model
}
}
/// <summary>
/// 代码改变后
/// </summary>
/// <param name="value"></param>
/// <exception cref="NotImplementedException"></exception>
partial void OnScriptChanged(string value)
{
IsScriptChanged = true;
}
/// <summary>
/// 节点创建时
/// </summary>
public override void OnCreating()
{
MethodInfo? method = this.GetType().GetMethod(nameof(GetFlowApi));
@@ -90,7 +102,7 @@ namespace Serein.NodeFlow.Model
InputType = ParameterValueInputType.Input,
Items = null,
IsParams = true,
Description = "脚本节点入参"
//Description = "脚本节点入参"
};
@@ -166,13 +178,35 @@ namespace Serein.NodeFlow.Model
/// <param name="context"></param>
/// <returns></returns>
public override async Task<FlowResult> ExecutingAsync(IDynamicContext context, CancellationToken token)
{
return await ExecutingAsync(this, context, token);
}
/// <summary>
/// 流程接口提供参数进行调用脚本节点
/// </summary>
/// <param name="flowCallNode"></param>
/// <param name="context"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<FlowResult> ExecutingAsync(NodeModelBase flowCallNode, IDynamicContext context, CancellationToken token)
{
if (token.IsCancellationRequested) return new FlowResult(this, context);
var @params = await this.GetParametersAsync(context, token);
if(token.IsCancellationRequested) return new FlowResult(this, context);
var @params = await flowCallNode.GetParametersAsync(context, token);
if (token.IsCancellationRequested) return new FlowResult(this, context);
//context.AddOrUpdate($"{context.Guid}_{this.Guid}_Params", @params[0]); // 后面再改
ReloadScript();// 每次都重新解析
if (IsScriptChanged)
{
lock (@params) {
if (IsScriptChanged)
{
ReloadScript();// 每次都重新解析
IsScriptChanged = false;
}
}
}
IScriptInvokeContext scriptContext = new ScriptInvokeContext(context);
@@ -180,13 +214,12 @@ namespace Serein.NodeFlow.Model
{
for (int i = 0; i < agrDatas.Length; i++)
{
var argName = MethodDetails.ParameterDetailss[i].Name;
var argName = flowCallNode.MethodDetails.ParameterDetailss[i].Name;
var argData = agrDatas[i];
scriptContext.SetVarValue(argName, argData);
}
}
FlowRunCompleteHandler onFlowStop = (e) =>
{
scriptContext.OnExit();
@@ -200,10 +233,8 @@ namespace Serein.NodeFlow.Model
var result = await ScriptInterpreter.InterpretAsync(scriptContext, mainNode); // 从入口节点执行
envEvent.OnFlowRunComplete -= onFlowStop;
return new FlowResult(this, context, result);
//SereinEnv.WriteLine(InfoType.INFO, "FlowContext Guid : " + context.Guid);
}
#region
public IScriptFlowApi GetFlowApi()

View File

@@ -126,7 +126,7 @@ namespace Serein.Workbench
filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\banyunqi\project.dnf";
filePath = @"F:\临时\project\project.dnf";
filePath = @"F:\TempFile\flow\qrcode\project.dnf";
filePath = @"F:\TempFile\flow\temp\project.dnf";
filePath = @"F:\TempFile\flow\temp2\project.dnf";
if (File.Exists(filePath))
{
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容

View File

@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Data;
namespace Serein.Workbench.Converters
{

View File

@@ -66,7 +66,7 @@
<local:ExecuteJunctionControl Grid.Column="0" MyNode="{Binding NodeModel}" x:Name="ExecuteJunctionControl" HorizontalAlignment="Left" Grid.RowSpan="2"/>
<StackPanel Grid.Column="1" Grid.RowSpan="2" Orientation="Horizontal">
<TextBlock Text="[流程接口]" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding NodeModel.DisplayName, Mode=TwoWay}" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>

View File

@@ -19,12 +19,14 @@ namespace Serein.Workbench.Node.View
base.ViewModel = new FlowCallNodeControlViewModel(new SingleFlowCallNode(env));
base.ViewModel.IsEnabledOnView = false;
DataContext = base.ViewModel;
base.ViewModel.NodeModel.DisplayName = "[流程接口]";
InitializeComponent();
}
public FlowCallNodeControl(FlowCallNodeControlViewModel viewModel) : base(viewModel)
{
DataContext = viewModel;
ViewModel = viewModel;
viewModel.NodeModel.DisplayName = "[流程接口]";
InitializeComponent();
ViewModel.UploadMethodDetailsControl = UploadMethodDetailsControl;

View File

@@ -31,7 +31,7 @@
<local:ExecuteJunctionControl Grid.Column="0" MyNode="{Binding NodeModel}" x:Name="ExecuteJunctionControl" HorizontalAlignment="Left" Grid.RowSpan="2"/>
<Border Grid.Column="1" BorderThickness="1" HorizontalAlignment="Stretch">
<TextBlock Text="脚本节点" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Text="{Binding NodeModel.DisplayName, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<local:NextStepJunctionControl Grid.Column="2" MyNode="{Binding NodeModel}" x:Name="NextStepJunctionControl" HorizontalAlignment="Right" Grid.RowSpan="2"/>
@@ -43,6 +43,7 @@
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
@@ -73,7 +74,18 @@
<Border Grid.Column="2" BorderThickness="1">
<local:ResultJunctionControl Grid.Column="2" MyNode="{Binding NodeModel}" x:Name="ResultJunctionControl" HorizontalAlignment="Right"/>
</Border>
</Grid>
<StackPanel Grid.Row="4" Background="Azure" Orientation="Horizontal" Margin="3">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding NodeModel.IsPublic, Mode=TwoWay}"/>
<TextBlock Text="全局公开"/>
</StackPanel>
</StackPanel>
<!--<RichTextBox x:Name="richTextBox" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"

View File

@@ -35,11 +35,13 @@ namespace Serein.Workbench.Node.View
base.ViewModel = new ScriptNodeControlViewModel(new SingleScriptNode(env));
base.ViewModel.IsEnabledOnView = false;
base.DataContext = viewModel;
viewModel.NodeModel.DisplayName = "[脚本节点]";
InitializeComponent();
}
public ScriptNodeControl(ScriptNodeControlViewModel viewModel) : base(viewModel)
{
DataContext = viewModel;
viewModel.NodeModel.DisplayName = "[脚本节点]";
InitializeComponent();
}

View File

@@ -31,8 +31,9 @@ namespace Serein.Workbench.Node.ViewModel
{
var cts = new CancellationTokenSource();
var result = await NodeModel.ExecutingAsync(new Library.DynamicContext(nodeModel.Env), cts.Token);
var data = result.Value;
cts.Cancel();
SereinEnv.WriteLine(InfoType.INFO, result?.ToString());
SereinEnv.WriteLine(InfoType.INFO, data?.ToString());
}
catch (Exception ex)
{