680 lines
27 KiB
C#
680 lines
27 KiB
C#
// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved.
|
|
// This code can only be used under the standard Unity Asset Store End User License Agreement
|
|
// A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Doozy.Runtime.Common.Extensions;
|
|
using Doozy.Runtime.Common.Utils;
|
|
using Doozy.Runtime.Nody.Nodes.System;
|
|
using Doozy.Runtime.UIManager.ScriptableObjects;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
// ReSharper disable MemberCanBeProtected.Global
|
|
// ReSharper disable UnusedMember.Global
|
|
// ReSharper disable MemberCanBePrivate.Global
|
|
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
|
// ReSharper disable PartialTypeWithSinglePart
|
|
// ReSharper disable ClassWithVirtualMembersNeverInherited.Global
|
|
|
|
namespace Doozy.Runtime.Nody
|
|
{
|
|
/// <summary> Scriptable object class used a container for nodes </summary>
|
|
[CreateAssetMenu(menuName = "Doozy/Flow Graph", fileName = "Flow Graph", order = -1000)]
|
|
public partial class FlowGraph : ScriptableObject
|
|
{
|
|
/// <summary> Pointer to the UIManagerInputSettings instance </summary>
|
|
public static UIManagerInputSettings inputSettings => UIManagerInputSettings.instance;
|
|
/// <summary> True is Multiplayer Mode is enabled </summary>
|
|
public static bool multiplayerMode => inputSettings.multiplayerMode;
|
|
/// <summary> Default player index value (used for global user) </summary>
|
|
public static int defaultPlayerIndex => inputSettings.defaultPlayerIndex;
|
|
|
|
[SerializeField]
|
|
public Vector3 EditorPosition = Vector3.zero;
|
|
/// <summary> Used by the editor to keep track of the graph position inside the Nody window </summary>
|
|
internal Vector3 editorPosition
|
|
{
|
|
get => EditorPosition;
|
|
set => EditorPosition = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
public Vector3 EditorScale = Vector3.one;
|
|
/// <summary> Used by the editor to keep track of the graph scale (zoom) inside the Nody window </summary>
|
|
internal Vector3 editorScale
|
|
{
|
|
get => EditorScale;
|
|
set => EditorScale = value;
|
|
}
|
|
|
|
[SerializeField] private string Id;
|
|
/// <summary> Flow Graph Id </summary>
|
|
public string id
|
|
{
|
|
get => Id;
|
|
set => Id = value;
|
|
}
|
|
|
|
[SerializeField] private string GraphName;
|
|
/// <summary> Name of the graph </summary>
|
|
public string graphName
|
|
{
|
|
get => GraphName;
|
|
set => GraphName = value;
|
|
}
|
|
|
|
[SerializeField] private string GraphDescription;
|
|
/// <summary> Description for the graph </summary>
|
|
public string graphDescription
|
|
{
|
|
get => GraphDescription;
|
|
set => GraphDescription = value;
|
|
}
|
|
|
|
[SerializeField] private bool IsSubGraph;
|
|
/// <summary> Flag used to mark the graph as a sub-graph </summary>
|
|
public bool isSubGraph
|
|
{
|
|
get => IsSubGraph;
|
|
set => IsSubGraph = value;
|
|
}
|
|
|
|
[SerializeField] protected GraphState GraphState = GraphState.Idle;
|
|
/// <summary> Current graph state </summary>
|
|
public GraphState graphState
|
|
{
|
|
get => GraphState;
|
|
set
|
|
{
|
|
GraphState = value;
|
|
OnStateChanged?.Invoke(value);
|
|
}
|
|
}
|
|
|
|
/// <summary> Called every time the graph changes its state </summary>
|
|
public GraphStateEvent OnStateChanged = new GraphStateEvent();
|
|
|
|
/// <summary> Called when the flow graph starts or restarts </summary>
|
|
public UnityEvent OnStart = new UnityEvent();
|
|
|
|
/// <summary> Called when the flow graph is stopped </summary>
|
|
public UnityEvent OnStop = new UnityEvent();
|
|
|
|
/// <summary> Called when the flow graph is paused </summary>
|
|
public UnityEvent OnPause = new UnityEvent();
|
|
|
|
/// <summary> Called when the flow graph is resumed </summary>
|
|
public UnityEvent OnResume = new UnityEvent();
|
|
|
|
/// <summary> Called when the flow graph is reset </summary>
|
|
public UnityEvent OnBackFlow = new UnityEvent();
|
|
|
|
[SerializeField] private List<FlowNode> Nodes;
|
|
/// <summary> All the nodes in the graph </summary>
|
|
public List<FlowNode> nodes
|
|
{
|
|
get => Nodes;
|
|
private set => Nodes = value;
|
|
}
|
|
|
|
/// <summary> All the global nodes in the graph </summary>
|
|
public IEnumerable<FlowNode> globalNodes =>
|
|
Nodes.Where(node => node.nodeType == NodeType.Global);
|
|
|
|
[SerializeField] private FlowNode RootNode;
|
|
/// <summary>
|
|
/// The first node that becomes active.
|
|
/// <para/> If this is a graph it will be a Start Node
|
|
/// <para/> Id this is a sub-graph it will be an Enter Node </summary>
|
|
public FlowNode rootNode
|
|
{
|
|
get => RootNode;
|
|
set => RootNode = value;
|
|
}
|
|
|
|
[SerializeField] private FlowNode ActiveNode;
|
|
/// <summary> The node that is currently active </summary>
|
|
public FlowNode activeNode
|
|
{
|
|
get => ActiveNode;
|
|
private set => ActiveNode = value;
|
|
}
|
|
|
|
/// <summary> The node that was previously active </summary>
|
|
public FlowNode previousActiveNode { get; private set; }
|
|
|
|
/// <summary> The port that lead to the previously active node </summary>
|
|
public FlowPort previousActivePort { get; private set; }
|
|
|
|
/// <summary> The sub-graph that is currently active (can be null) </summary>
|
|
public FlowGraph activeSubGraph { get; private set; }
|
|
|
|
/// <summary> The parent graph that contains this graph (if this is a sub-graph) (can be null) </summary>
|
|
public FlowGraph parentGraph { get; private set; }
|
|
|
|
/// <summary> All the input ports in the graph </summary>
|
|
public List<FlowPort> inputPorts
|
|
{
|
|
get
|
|
{
|
|
var list = new List<FlowPort>();
|
|
foreach (FlowNode node in Nodes)
|
|
list.AddRange(node.inputPorts);
|
|
return list;
|
|
}
|
|
}
|
|
|
|
/// <summary> All the output ports in the graph </summary>
|
|
public List<FlowPort> outputPorts
|
|
{
|
|
get
|
|
{
|
|
var list = new List<FlowPort>();
|
|
foreach (FlowNode node in Nodes)
|
|
list.AddRange(node.outputPorts);
|
|
return list;
|
|
}
|
|
}
|
|
|
|
/// <summary> All the ports in the graph (input and output) </summary>
|
|
public List<FlowPort> ports
|
|
{
|
|
get
|
|
{
|
|
var list = new List<FlowPort>();
|
|
foreach (FlowNode node in Nodes)
|
|
{
|
|
list.AddRange(node.inputPorts);
|
|
list.AddRange(node.outputPorts);
|
|
}
|
|
return list;
|
|
}
|
|
}
|
|
|
|
/// <summary> Current controller for the graph </summary>
|
|
public FlowController controller { get; internal set; }
|
|
|
|
/// <summary> Construct a new FlowGraph </summary>
|
|
public FlowGraph()
|
|
{
|
|
Id = Guid.NewGuid().ToString();
|
|
GraphName = ObjectNames.NicifyVariableName(nameof(FlowGraph));
|
|
Nodes = new List<FlowNode>();
|
|
}
|
|
|
|
/// <summary> Reset the graph editor settings used inside the Nody window </summary>
|
|
internal void ResetEditorSettings()
|
|
{
|
|
EditorPosition = Vector3.zero;
|
|
EditorScale = Vector3.one;
|
|
}
|
|
|
|
/// <summary> Reset the graph </summary>
|
|
public void ResetGraph()
|
|
{
|
|
ClearHistory();
|
|
previousActiveNode = null;
|
|
activeNode = null;
|
|
nodes.ForEach(n => n.ResetNode());
|
|
CleanGraph();
|
|
graphState = GraphState.Idle;
|
|
}
|
|
|
|
private void CleanGraph()
|
|
{
|
|
nodes.ForEach(n =>
|
|
{
|
|
foreach (FlowPort inputPort in n.inputPorts)
|
|
foreach (string otherPortId in inputPort.connections.ToList()
|
|
.Where(otherPortId => GetPortById(otherPortId) == null))
|
|
inputPort.connections.Remove(otherPortId);
|
|
|
|
foreach (FlowPort outputPort in n.outputPorts)
|
|
foreach (string otherPortId in outputPort.connections.ToList()
|
|
.Where(otherPortId => GetPortById(otherPortId) == null))
|
|
outputPort.connections.Remove(otherPortId);
|
|
});
|
|
}
|
|
|
|
/// <summary> Activate the given node </summary>
|
|
/// <param name="node"> Target node (next active node) </param>
|
|
/// <param name="fromPort"> Port that activated this node (can be null) </param>
|
|
public void SetActiveNode(FlowNode node, FlowPort fromPort = null)
|
|
{
|
|
if (node == null) return;
|
|
if (activeNode != null)
|
|
activeNode.OnExit();
|
|
|
|
history.Push(new GraphHistory(previousActiveNode, previousActivePort, activeNode));
|
|
previousActiveNode = activeNode;
|
|
previousActivePort = fromPort;
|
|
activeNode = node;
|
|
|
|
activeNode.OnEnter();
|
|
activeNode.Ping(FlowDirection.Forward);
|
|
fromPort?.Ping(FlowDirection.Forward);
|
|
}
|
|
|
|
/// <summary> Go back to the previous node (if possible) by activating the node in the graph </summary>
|
|
public void GoBack() =>
|
|
GoBack(defaultPlayerIndex);
|
|
|
|
/// <summary> Go back to the previous node (if possible) by activating the node in the graph </summary>
|
|
/// <param name="playerIndex"> Player index </param>
|
|
public void GoBack(int playerIndex)
|
|
{
|
|
if (history.Count == 0) return; //cannot go back
|
|
|
|
//node type check -> block going back to special nodes
|
|
switch (previousActiveNode)
|
|
{
|
|
case StartNode _:
|
|
case EnterNode _:
|
|
return;
|
|
}
|
|
|
|
if
|
|
(
|
|
multiplayerMode && //check if multiplayer mode is enabled
|
|
playerIndex != defaultPlayerIndex && //check if default player index -> ignore player index
|
|
controller.hasMultiplayerInfo && //check if the controller is bound to a player index
|
|
playerIndex != controller.multiplayerInfo.playerIndex //check player index
|
|
)
|
|
return;
|
|
|
|
tempNodesSet.Clear();
|
|
tempPortsSet.Clear();
|
|
|
|
tempPortsSet.Add(previousActivePort);
|
|
|
|
if (history.All(item => item.activeNode.passthrough))
|
|
return; //cannot go back as there is no non-passthrough node in history
|
|
|
|
GraphHistory peek = history.Peek();
|
|
|
|
//multiple entries of the same node in history and passthrough check
|
|
if (peek.activeNode == activeNode | peek.activeNode.passthrough) //we may have 1 or more nodes that are passthrough
|
|
{
|
|
while (history.Count > 0) //iterate through history
|
|
{
|
|
peek = history.Peek();
|
|
if (peek.activeNode == activeNode) //we found the node we are currently clear the pings for the visual need to dig deeper
|
|
{
|
|
tempNodesSet.Clear(); //don't ping nodes
|
|
tempPortsSet.Clear(); //don't ping ports
|
|
history.Pop();
|
|
continue;
|
|
}
|
|
|
|
if (peek.activeNode.passthrough) //check if the node is passthrough
|
|
{ //
|
|
tempNodesSet.Add(peek.activeNode); //save node to be able to ping it (visuals matter)
|
|
tempPortsSet.Add(peek.previousActivePort); //save port to be able to ping it (visuals matter)
|
|
history.Pop(); //pop history and go to the next entry
|
|
continue;
|
|
}
|
|
|
|
break; //found node that is NOT passthrough -> prepare to activate it
|
|
}
|
|
}
|
|
|
|
if (history.Count == 0) return; //cannot go back
|
|
|
|
if (activeNode != null)
|
|
activeNode.OnExit();
|
|
|
|
//ping nodes
|
|
tempNodesSet.Remove(null); //remove null
|
|
foreach (FlowNode flowNode in tempNodesSet)
|
|
flowNode.Ping(FlowDirection.Back);
|
|
|
|
//ping ports
|
|
tempPortsSet.Remove(null); //remove null
|
|
foreach (FlowPort flowPort in tempPortsSet)
|
|
flowPort.Ping(FlowDirection.Back);
|
|
|
|
previousActiveNode = history.Peek().previousActiveNode;
|
|
previousActivePort = history.Peek().previousActivePort;
|
|
activeNode = history.Peek().activeNode;
|
|
history.Pop();
|
|
|
|
activeNode.OnEnter();
|
|
|
|
tempNodesSet.Clear();
|
|
tempPortsSet.Clear();
|
|
|
|
OnBackFlow?.Invoke();
|
|
}
|
|
|
|
/// <summary> Activate the first node with the given node name </summary>
|
|
/// <param name="nodeName"> Node name to search for </param>
|
|
public void SetActiveNodeByNodeName(string nodeName) =>
|
|
SetActiveNode(GetNodeByName(nodeName));
|
|
|
|
/// <summary> Activate the node with the given node id </summary>
|
|
/// <param name="nodeId"> Node id to search for </param>
|
|
public void SetActiveNodeByNodeId(string nodeId) =>
|
|
SetActiveNode(GetNodeById(nodeId));
|
|
|
|
/// <summary>
|
|
/// Restart the graph. This will reset the graph and activate the first node.
|
|
/// <para/> Even if the graph is paused, it will reset and start from the beginning.
|
|
/// </summary>
|
|
public void Restart()
|
|
{
|
|
ResetGraph();
|
|
UpdateNodes();
|
|
StartGlobalNodes();
|
|
SetActiveNode(RootNode);
|
|
graphState = GraphState.Running;
|
|
OnStart?.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start the graph. This will reset the graph and activate the first node.
|
|
/// <para/> If the graph is paused, it will resume from the last active node instead of the first node.
|
|
/// </summary>
|
|
public void Start()
|
|
{
|
|
if (graphState == GraphState.Paused)
|
|
{
|
|
Resume(); //graph is paused -> resume
|
|
return;
|
|
}
|
|
|
|
ResetGraph();
|
|
UpdateNodes();
|
|
StartGlobalNodes();
|
|
SetActiveNode(RootNode);
|
|
graphState = GraphState.Running;
|
|
OnStart?.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resume the graph.
|
|
/// <para/> If the graph state is Idle (stopped), it will start the graph instead.
|
|
/// </summary>
|
|
public void Resume()
|
|
{
|
|
if (graphState == GraphState.Idle)
|
|
{
|
|
Start(); //graph is not paused -> start the graph
|
|
return;
|
|
}
|
|
|
|
graphState = GraphState.Running; //set graph state to running
|
|
StartGlobalNodes(); //start global nodes
|
|
if (activeNode != null) //check if there is an active node
|
|
activeNode.OnUnPaused(); //resume the active node
|
|
OnResume?.Invoke(); //invoke graph resumed event
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pause the graph.
|
|
/// <para/> If the graph state is not Running, it will do nothing.
|
|
/// </summary>
|
|
public void Pause()
|
|
{
|
|
if (graphState != GraphState.Running) //check if the graph is running
|
|
return; //graph is not running -> do nothing
|
|
graphState = GraphState.Paused; //set graph state to paused
|
|
StopGlobalNodes(); //stop global nodes
|
|
if (activeNode != null) //check if there is an active node
|
|
activeNode.OnPaused(); //pause the active node
|
|
OnPause?.Invoke(); //invoke graph paused event
|
|
}
|
|
|
|
/// <summary> Toggle the graph state between running and paused </summary>
|
|
/// <param name="paused"> If true, the graph will be paused, otherwise it will be resumed </param>
|
|
public void SetPaused(bool paused)
|
|
{
|
|
if (paused)
|
|
{
|
|
Pause();
|
|
return;
|
|
}
|
|
|
|
Resume();
|
|
}
|
|
|
|
/// <summary> Stop the graph </summary>
|
|
public void Stop()
|
|
{
|
|
StopGlobalNodes(); //stop global nodes
|
|
if (activeNode != null) //check if there is an active node
|
|
activeNode.OnExit(); //deactivate the active node
|
|
activeNode = null; //set active node to null
|
|
graphState = GraphState.Idle; //set graph state to idle
|
|
OnStop?.Invoke(); //invoke graph stopped event
|
|
}
|
|
|
|
/// <summary> Start all the global nodes inside the graph </summary>
|
|
public void StartGlobalNodes()
|
|
{
|
|
// ReSharper disable once ForCanBeConvertedToForeach
|
|
for (int i = 0; i < nodes.Count; i++) //iterate through nodes
|
|
{
|
|
FlowNode node = nodes[i]; //get node
|
|
if (node.nodeType != NodeType.Global) continue; //check if the node is global
|
|
node.Start(); //start the node
|
|
}
|
|
|
|
if (activeSubGraph != null) //check if there is an active sub graph
|
|
activeSubGraph.StartGlobalNodes(); //start global nodes in the active sub graph
|
|
}
|
|
|
|
/// <summary> Stop all the global nodes inside the graph </summary>
|
|
public virtual void StopGlobalNodes()
|
|
{
|
|
// ReSharper disable once ForCanBeConvertedToForeach
|
|
for (int i = 0; i < nodes.Count; i++) //iterate through nodes
|
|
{
|
|
FlowNode node = nodes[i]; //get node
|
|
if (node.nodeType != NodeType.Global) continue; //check if the node is global
|
|
node.Stop(); //stop the node
|
|
}
|
|
|
|
if (activeSubGraph != null) //check if there is an active sub graph
|
|
activeSubGraph.StopGlobalNodes(); //stop global nodes in the active sub graph
|
|
}
|
|
|
|
/// <summary> FixedUpdate is called every fixed framerate frame, if this flow has been loaded by a controller </summary>
|
|
public void FixedUpdate()
|
|
{
|
|
if (graphState != GraphState.Running) //check if the graph is running
|
|
return; //graph is not running -> return
|
|
|
|
if (activeNode != null && activeNode.runFixedUpdate) //check if the active node is not null and if it should run fixed update
|
|
activeNode.FixedUpdate(); //run fixed update
|
|
|
|
if (activeSubGraph != null) //check if the active sub graph is not null
|
|
activeSubGraph.FixedUpdate(); //run fixed update
|
|
|
|
// ReSharper disable once ForCanBeConvertedToForeach
|
|
for (int i = 0; i < nodes.Count; i++) //iterate through nodes
|
|
{
|
|
FlowNode node = nodes[i]; //get node
|
|
if (node.nodeType != NodeType.Global) continue; //check if node is global
|
|
if (!node.runFixedUpdate) continue; //check if node should run fixed update
|
|
node.FixedUpdate(); //run fixed update
|
|
}
|
|
}
|
|
|
|
/// <summary> LateUpdate is called every frame, after all Update functions have been called and if this flow has been loaded by a controller </summary>
|
|
public void LateUpdate()
|
|
{
|
|
if (graphState != GraphState.Running) //check if the graph is running
|
|
return; //graph is not running -> return
|
|
|
|
if (activeNode != null && activeNode.runLateUpdate) //check if the active node is running
|
|
activeNode.LateUpdate(); //run the active node
|
|
|
|
if (activeSubGraph != null) //check if the active subgraph is running
|
|
activeSubGraph.LateUpdate(); //run the active subgraph
|
|
|
|
// ReSharper disable once ForCanBeConvertedToForeach
|
|
for (int i = 0; i < nodes.Count; i++) //iterate through all nodes
|
|
{
|
|
FlowNode node = nodes[i]; //get the current node
|
|
if (node.nodeType != NodeType.Global) continue; //check if the node is global
|
|
if (node.runLateUpdate == false) continue; //check if the node is running
|
|
if (node.nodeState == NodeState.Idle) continue; //check if the node is idle
|
|
node.LateUpdate(); //run the node
|
|
}
|
|
}
|
|
|
|
/// <summary> Update is called every frame, if this flow has been loaded by a controller </summary>
|
|
public void Update()
|
|
{
|
|
if (graphState != GraphState.Running) //check if the graph is running
|
|
return; //graph is not running -> return
|
|
|
|
if (activeNode != null && activeNode.runUpdate) //check if the active node is running
|
|
activeNode.Update(); //run the active node
|
|
|
|
if (activeSubGraph != null) //check if the active subgraph is running
|
|
activeSubGraph.Update(); //run the active subgraph
|
|
|
|
|
|
// ReSharper disable once ForCanBeConvertedToForeach
|
|
for (int i = 0; i < nodes.Count; i++) //iterate through all nodes
|
|
{
|
|
FlowNode node = nodes[i]; //get the current node
|
|
if (node.nodeType != NodeType.Global) continue; //check if the node is global
|
|
if (node.runUpdate == false) continue; //check if the node is running
|
|
if (node.nodeState == NodeState.Idle) continue; //check if the node is idle
|
|
node.Update(); //run the node
|
|
}
|
|
}
|
|
|
|
/// <summary> Refresh all the references for the graph's nodes </summary>
|
|
public void UpdateNodes()
|
|
{
|
|
Nodes.RemoveNulls();
|
|
// ReSharper disable once ForCanBeConvertedToForeach
|
|
for (int i = 0; i < nodes.Count; i++) //iterate through all nodes
|
|
nodes[i].SetFlowGraph(this); //set the flow graph for the node
|
|
}
|
|
|
|
/// <summary> Create a clone of this graph </summary>
|
|
/// <returns> The cloned graph </returns>
|
|
public FlowGraph Clone()
|
|
{
|
|
FlowGraph flowClone = Instantiate(this);
|
|
flowClone.RootNode = RootNode.Clone().SetFlowGraph(flowClone);
|
|
flowClone.nodes = nodes.ConvertAll(n => n.Clone());
|
|
flowClone.UpdateNodes();
|
|
return flowClone;
|
|
}
|
|
|
|
/// <summary> True if the graph contains the given node </summary>
|
|
/// <param name="node"> Node to search for </param>
|
|
public bool ContainsNode(FlowNode node) =>
|
|
node != null && nodes.Contains(node);
|
|
|
|
/// <summary> True if the graph contains a node with the given id </summary>
|
|
/// <param name="nodeId"> Node id to search for </param>
|
|
public bool ContainsNodeById(string nodeId) =>
|
|
nodes.Any(node => node.nodeId.Equals(nodeId));
|
|
|
|
/// <summary> True if the graph contains a node with the given node name </summary>
|
|
/// <param name="nodeName"> Node name to search for </param>
|
|
public bool ContainsNodeByName(string nodeName) =>
|
|
nodes.Any(node => node.nodeName.Equals(nodeName));
|
|
|
|
/// <summary>
|
|
/// Get the StartNode if this is not a sub graph.
|
|
/// If not found, this method returns null
|
|
/// </summary>
|
|
public StartNode GetStartNode() =>
|
|
(StartNode)nodes.FirstOrDefault(n => n is StartNode);
|
|
|
|
/// <summary>
|
|
/// Get the EnterNode if this is a sub graph.
|
|
/// If not found, this method returns null
|
|
/// </summary>
|
|
public EnterNode GetEnterNode() =>
|
|
(EnterNode)nodes.FirstOrDefault(n => n is EnterNode);
|
|
|
|
/// <summary>
|
|
/// Get the ExitNode if this is a sub graph.
|
|
/// If not found, this method returns null
|
|
/// </summary>
|
|
public ExitNode GetExitNode() =>
|
|
(ExitNode)nodes.FirstOrDefault(n => n is ExitNode);
|
|
|
|
/// <summary>
|
|
/// Get the first node with the given node name.
|
|
/// If one is not found, this method returns null
|
|
/// </summary>
|
|
/// <param name="nodeName"> Node name to search for </param>
|
|
public FlowNode GetNodeByName(string nodeName) =>
|
|
nodes.FirstOrDefault(node => node.nodeName.Equals(nodeName));
|
|
|
|
/// <summary>
|
|
/// Get the node with the given node id.
|
|
/// If one is not found, this method returns null
|
|
/// </summary>
|
|
/// <param name="nodeId"> Node id to search for </param>
|
|
public FlowNode GetNodeById(string nodeId) =>
|
|
nodes.FirstOrDefault(node => node.nodeId.Equals(nodeId));
|
|
|
|
/// <summary> Get all the nodes of the given type </summary>
|
|
/// <typeparam name="T"> Type of node </typeparam>
|
|
public List<T> GetNodeByType<T>() where T : FlowNode =>
|
|
(List<T>)nodes.Where(node => node is T);
|
|
|
|
/// <summary>
|
|
/// Get the port with the given port id.
|
|
/// If one is not found, this method returns null
|
|
/// </summary>
|
|
/// <param name="portId"> Port id to search for </param>
|
|
public FlowPort GetPortById(string portId) =>
|
|
nodes.Select(node => node.GetPortFromId(portId)).FirstOrDefault(port => port != null);
|
|
|
|
#region Graph History and Cats
|
|
|
|
private Stack<GraphHistory> history { get; set; }
|
|
private HashSet<FlowNode> tempNodesSet { get; set; }
|
|
private HashSet<FlowPort> tempPortsSet { get; set; }
|
|
|
|
// |\__/,| (`\
|
|
// _.|o o |_ ) )
|
|
//-(((---(((--------
|
|
/// <summary>
|
|
/// Clear graph history and remove the possibility of being able to go back to previously active nodes
|
|
/// </summary>
|
|
public void ClearHistory()
|
|
{
|
|
history ??= new Stack<GraphHistory>();
|
|
tempNodesSet ??= new HashSet<FlowNode>();
|
|
tempPortsSet ??= new HashSet<FlowPort>();
|
|
|
|
history.Clear();
|
|
}
|
|
|
|
// /)
|
|
// /\___/\ ((
|
|
// \`@_@'/ ))
|
|
// {_:Y:.}_//
|
|
//-------{_}^-'{_}---------
|
|
private struct GraphHistory
|
|
{
|
|
public FlowNode previousActiveNode { get; set; }
|
|
public FlowPort previousActivePort { get; set; }
|
|
public FlowNode activeNode { get; set; }
|
|
|
|
public GraphHistory(FlowNode previousActiveNode, FlowPort previousActivePort, FlowNode activeNode)
|
|
{
|
|
this.previousActiveNode = previousActiveNode;
|
|
this.previousActivePort = previousActivePort;
|
|
this.activeNode = activeNode;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|