Files
serein-flow/NodeFlow/Env/FlowEdit.cs
2025-07-04 21:31:07 +08:00

569 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Model.Operation;
using Serein.NodeFlow.Services;
using Serein.NodeFlow.Tool;
using static Serein.Library.Api.IFlowEnvironment;
namespace Serein.NodeFlow.Env
{
/// <summary>
/// 流程编辑接口实现
/// </summary>
internal class FlowEdit : IFlowEdit
{
public FlowEdit(IFlowEnvironment flowEnvironment,
IFlowEnvironmentEvent flowEnvironmentEvent,
FlowLibraryService flowLibraryManagement,
FlowOperationService flowOperationService,
FlowModelService flowModelService,
UIContextOperation UIContextOperation,
ISereinIOC sereinIOC,
NodeMVVMService nodeMVVMService)
{
this.flowEnvironment = flowEnvironment;
this.flowEnvironmentEvent = flowEnvironmentEvent;
this.flowLibraryManagement = flowLibraryManagement;
this.flowOperationService = flowOperationService;
this.flowModelService = flowModelService;
this.UIContextOperation = UIContextOperation;
NodeMVVMManagement = nodeMVVMService;
InitNodeMVVM(nodeMVVMService);
}
/// <inheritdoc/>
public NodeMVVMService NodeMVVMManagement { get; }
private readonly IFlowEnvironment flowEnvironment;
private readonly IFlowEnvironmentEvent flowEnvironmentEvent;
private readonly FlowLibraryService flowLibraryManagement;
private readonly FlowOperationService flowOperationService;
private readonly FlowModelService flowModelService;
private readonly NodeMVVMService nodeMVVMService;
/// <summary>
/// 注册基本节点类型
/// </summary>
private void InitNodeMVVM(NodeMVVMService nodeMVVMService)
{
nodeMVVMService.RegisterModel(NodeControlType.UI, typeof(SingleUINode)); // 动作节点
nodeMVVMService.RegisterModel(NodeControlType.Action, typeof(SingleActionNode)); // 动作节点
nodeMVVMService.RegisterModel(NodeControlType.Flipflop, typeof(SingleFlipflopNode)); // 触发器节点
nodeMVVMService.RegisterModel(NodeControlType.ExpOp, typeof(SingleExpOpNode)); // 表达式节点
nodeMVVMService.RegisterModel(NodeControlType.ExpCondition, typeof(SingleConditionNode)); // 条件表达式节点
nodeMVVMService.RegisterModel(NodeControlType.GlobalData, typeof(SingleGlobalDataNode)); // 全局数据节点
nodeMVVMService.RegisterModel(NodeControlType.Script, typeof(SingleScriptNode)); // 脚本节点
nodeMVVMService.RegisterModel(NodeControlType.NetScript, typeof(SingleNetScriptNode)); // 脚本节点
nodeMVVMService.RegisterModel(NodeControlType.FlowCall, typeof(SingleFlowCallNode)); // 流程调用节点
}
private UIContextOperation UIContextOperation;
/// <summary>
/// 从Guid获取画布
/// </summary>
/// <param name="nodeGuid">节点Guid</param>
/// <returns>节点Model</returns>
/// <exception cref="ArgumentNullException">无法获取节点、Guid/节点为null时报错</exception>
public bool TryGetCanvasModel(string nodeGuid, out FlowCanvasDetails canvasDetails)
{
if (string.IsNullOrEmpty(nodeGuid))
{
canvasDetails = null;
return false;
}
return flowModelService.TryGetCanvasModel(nodeGuid, out canvasDetails);
}
/// <summary>
/// 从Guid获取节点
/// </summary>
/// <param name="nodeGuid">节点Guid</param>
/// <returns>节点Model</returns>
/// <exception cref="ArgumentNullException">无法获取节点、Guid/节点为null时报错</exception>
public bool TryGetNodeModel(string nodeGuid, out IFlowNode nodeModel)
{
if (string.IsNullOrEmpty(nodeGuid))
{
nodeModel = null;
return false;
}
return flowModelService.TryGetNodeModel(nodeGuid, out nodeModel);
}
#region
/// <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(flowEnvironment, controlType, methodDetails); // 加载项目时创建节点
if (nodeModel is null)
{
nodeInfo.Guid = string.Empty;
return false;
}
if (TryGetCanvasModel(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(() =>
flowEnvironmentEvent.OnNodeCreated(new NodeCreateEventArgs(nodeInfo.CanvasGuid, nodeModel, nodeInfo.Position))); // 添加到UI上
return true;
}
/// <summary>
/// 创建节点
/// </summary>
/// <param name="nodeBase"></param>
private bool TryAddNode(IFlowNode nodeModel)
{
nodeModel.Guid ??= Guid.NewGuid().ToString();
flowModelService.AddNodeModel(nodeModel);
// 如果是触发器,则需要添加到专属集合中
/*if (nodeModel is SingleFlipflopNode flipflopNode)
{
var guid = flipflopNode.Guid;
if (!FlipflopNodes.Exists(it => it.Guid.Equals(guid)))
{
FlipflopNodes.Add(flipflopNode);
}
}*/
return true;
}
#endregion
#region
private int _add_canvas_count = 1;
public void CreateCanvas(string canvasName, int width, int height)
{
IOperation operation = new CreateCanvasOperation
{
CanvasInfo = new FlowCanvasDetailsInfo
{
Name = $"Canvas {_add_canvas_count++}",
Width = width,
Height = height,
Guid = Guid.NewGuid().ToString(),
ScaleX = 1.0f,
ScaleY = 1.0f,
}
};
flowOperationService.Execute(operation);
}
public void RemoveCanvas(string canvasGuid)
{
IOperation operation = new RemoveCanvasOperation
{
CanvasGuid = canvasGuid
};
flowOperationService.Execute(operation);
}
public void ConnectInvokeNode(string canvasGuid, string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionInvokeType invokeType)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = canvasGuid,
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
FromNodeJunctionType = fromNodeJunctionType,
ToNodeJunctionType = toNodeJunctionType,
ConnectionInvokeType = invokeType,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Create,
JunctionOfConnectionType = JunctionOfConnectionType.Invoke,
};
flowOperationService.Execute(operation);
}
public void ConnectArgSourceNode(string canvasGuid, string fromNodeGuid, string toNodeGuid, JunctionType fromNodeJunctionType, JunctionType toNodeJunctionType, ConnectionArgSourceType argSourceType, int argIndex)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = canvasGuid,
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
FromNodeJunctionType = fromNodeJunctionType,
ToNodeJunctionType = toNodeJunctionType,
ConnectionArgSourceType = argSourceType,
ArgIndex = argIndex,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Create,
JunctionOfConnectionType = JunctionOfConnectionType.Arg,
};
flowOperationService.Execute(operation);
}
public void RemoveInvokeConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = canvasGuid,
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
ConnectionInvokeType = connectionType,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Remove,
};
flowOperationService.Execute(operation);
}
public void RemoveArgSourceConnect(string canvasGuid, string fromNodeGuid, string toNodeGuid, int argIndex)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = canvasGuid,
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
ArgIndex = argIndex,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Remove
};
flowOperationService.Execute(operation);
}
public void CreateNode(string canvasGuid, NodeControlType nodeType, PositionOfUI position, MethodDetailsInfo methodDetailsInfo = null)
{
IOperation operation = new CreateNodeOperation
{
CanvasGuid = canvasGuid,
NodeControlType = nodeType,
Position = position,
MethodDetailsInfo = methodDetailsInfo
};
flowOperationService.Execute(operation);
}
public void RemoveNode(string canvasGuid, string nodeGuid)
{
IOperation operation = new RemoveNodeOperation
{
CanvasGuid = canvasGuid,
NodeGuid = nodeGuid
};
flowOperationService.Execute(operation);
}
public void PlaceNodeToContainer(string canvasGuid, string nodeGuid, string containerNodeGuid)
{
IOperation operation = new ContainerPlaceNodeOperation
{
CanvasGuid = canvasGuid,
NodeGuid = nodeGuid,
ContainerNodeGuid = containerNodeGuid
};
flowOperationService.Execute(operation);
}
public void TakeOutNodeToContainer(string canvasGuid, string nodeGuid)
{
IOperation operation = new ContainerTakeOutNodeOperation
{
CanvasGuid = canvasGuid,
NodeGuid = nodeGuid,
};
flowOperationService.Execute(operation);
}
public void SetStartNode(string canvasGuid, string nodeGuid)
{
if (!TryGetCanvasModel(canvasGuid, out var canvasModel) || !TryGetNodeModel(nodeGuid, out var newStartNodeModel))
{
return;
}
var oldNodeGuid = canvasModel.StartNode?.Guid;
/*if(TryGetNodeModel(oldNodeGuid, out var newStartNodeModel))
{
newStartNode.IsStart = false;
}*/
canvasModel.StartNode = newStartNodeModel;
//newStartNode.IsStart = true;
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnStartNodeChanged(new StartNodeChangeEventArgs(canvasGuid, oldNodeGuid, newStartNodeModel.Guid)));
return;
}
public void SetConnectPriorityInvoke(string fromNodeGuid, string toNodeGuid, ConnectionInvokeType connectionType)
{
IOperation operation = new ChangeNodeConnectionOperation
{
CanvasGuid = string.Empty, // 连接优先级不需要画布
FromNodeGuid = fromNodeGuid,
ToNodeGuid = toNodeGuid,
ConnectionInvokeType = connectionType,
ChangeType = NodeConnectChangeEventArgs.ConnectChangeType.Create
};
flowOperationService.Execute(operation);
}
public void ChangeParameter(string nodeGuid, bool isAdd, int paramIndex)
{
IOperation operation = new ChangeParameterOperation
{
NodeGuid = nodeGuid,
IsAdd = isAdd,
ParamIndex = paramIndex
};
flowOperationService.Execute(operation);
}
/// <summary>
/// 从节点信息集合批量加载节点控件
/// </summary>
/// <param name="List<NodeInfo>">节点信息</param>
/// <returns></returns>
///
public async Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos)
{
#region NodeInfo创建NodeModel
// 流程接口节点最后才创建
List<NodeInfo> flowCallNodeInfos = [];
foreach (NodeInfo? nodeInfo in nodeInfos)
{
if (nodeInfo.Type == nameof(NodeControlType.FlowCall))
{
flowCallNodeInfos.Add(nodeInfo);
}
else
{
if (!CreateNodeFromNodeInfo(nodeInfo))
{
SereinEnv.WriteLine(InfoType.WARN, $"节点创建失败。{Environment.NewLine}{nodeInfo}");
continue;
}
}
}
// 创建流程接口节点
foreach (NodeInfo? nodeInfo in flowCallNodeInfos)
{
if (!CreateNodeFromNodeInfo(nodeInfo))
{
SereinEnv.WriteLine(InfoType.WARN, $"节点创建失败。{Environment.NewLine}{nodeInfo}");
continue;
}
}
#endregion
#region
List<NodeInfo> needPlaceNodeInfos = [];
foreach (NodeInfo? nodeInfo in nodeInfos)
{
if (!string.IsNullOrEmpty(nodeInfo.ParentNodeGuid) &&
TryGetNodeModel(nodeInfo.ParentNodeGuid, out var parentNode))
{
needPlaceNodeInfos.Add(nodeInfo); // 需要重新放置的节点
}
}
foreach (NodeInfo nodeInfo in needPlaceNodeInfos)
{
if (TryGetNodeModel(nodeInfo.Guid, out var nodeModel) &&
TryGetNodeModel(nodeInfo.ParentNodeGuid, out var containerNode)
&& containerNode is INodeContainer nodeContainer)
{
var result = nodeContainer.PlaceNode(nodeModel);
if (result)
{
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnNodePlace(
new NodePlaceEventArgs(nodeInfo.CanvasGuid, nodeModel.Guid, containerNode.Guid)));
}
}
}
#endregion
await Task.Delay(100);
#region
foreach (var nodeInfo in nodeInfos)
{
var canvasGuid = nodeInfo.CanvasGuid;
if (!TryGetNodeModel(nodeInfo.Guid, out var fromNodeModel))
{
return;
}
if (fromNodeModel is null) continue;
List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes),
(ConnectionInvokeType.IsFail, nodeInfo.FalseNodes),
(ConnectionInvokeType.IsError, nodeInfo.ErrorNodes),
(ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)];
foreach ((ConnectionInvokeType connectionType, string[] toNodeGuids) item in allToNodes)
{
// 遍历当前类型分支的节点(确认连接关系)
foreach (var toNodeGuid in item.toNodeGuids)
{
if (!TryGetNodeModel(toNodeGuid, out var toNodeModel))
{
return;
}
if (toNodeModel is null)
{
// 防御性代码,加载正常保存的项目文件不会进入这里
continue;
}
ConnectInvokeNode(canvasGuid, fromNodeModel.Guid, toNodeModel.Guid, JunctionType.NextStep, JunctionType.Execute, item.connectionType);
//var isSuccessful = ConnectInvokeOfNode(canvasGuid, fromNodeModel, toNodeModel, item.connectionType); // 加载时确定节点间的连接关系
}
}
//List<(ConnectionInvokeType connectionType, string[] guids)> allToNodes = [(ConnectionInvokeType.IsSucceed,nodeInfo.TrueNodes),
// (ConnectionInvokeType.IsFail, nodeInfo.FalseNodes),
// (ConnectionInvokeType.IsError, nodeInfo.ErrorNodes),
// (ConnectionInvokeType.Upstream, nodeInfo.UpstreamNodes)];
//List<(ConnectionInvokeType, NodeModelBase[])> fromNodes = allToNodes.Where(info => info.guids.Length > 0)
// .Select(info => (info.connectionType,
// info.guids.Where(guid => NodeModels.ContainsKey(guid)).Select(guid => NodeModels[guid])
// .ToArray()))
// .ToList();
// 遍历每种类型的节点分支(四种)
//foreach ((ConnectionInvokeType connectionType, NodeModelBase[] toNodes) item in nodeInfo)
//{
// // 遍历当前类型分支的节点(确认连接关系)
// foreach (var toNode in item.toNodes)
// {
// _ = ConnectInvokeOfNode(fromNode, toNode, item.connectionType); // 加载时确定节点间的连接关系
// }
//}
}
#endregion
#region
var nodeModels = flowModelService.GetAllNodeModel();
foreach (var toNode in nodeModels)
{
var canvasGuid = toNode.CanvasDetails.Guid;
if (toNode.MethodDetails.ParameterDetailss == null)
{
continue;
}
for (var i = 0; i < toNode.MethodDetails.ParameterDetailss.Length; i++)
{
var pd = toNode.MethodDetails.ParameterDetailss[i];
if (!string.IsNullOrEmpty(pd.ArgDataSourceNodeGuid)
&& TryGetNodeModel(pd.ArgDataSourceNodeGuid, out var fromNode))
{
ConnectArgSourceNode(canvasGuid, fromNode.Guid, toNode.Guid, JunctionType.ReturnData, JunctionType.ArgData, pd.ArgDataSourceType, pd.Index);
}
}
}
#endregion
UIContextOperation?.Invoke(() =>
{
flowEnvironmentEvent.OnProjectLoaded(new ProjectLoadedEventArgs());
});
return;
}
#endregion
#region
/// <summary>
/// 定位节点
/// </summary>
/// <param name="nodeGuid"></param>
public void NodeLocate(string nodeGuid)
{
if (OperatingSystem.IsWindows())
{
UIContextOperation?.Invoke(() => flowEnvironmentEvent.OnNodeLocated(new NodeLocatedEventArgs(nodeGuid)));
}
}
#endregion
}
}