using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace UnityEditor.Graphing { [Serializable] public class SerializableGraph : IGraph, ISerializationCallbackReceiver { [NonSerialized] List m_Edges = new List(); [NonSerialized] Dictionary> m_NodeEdges = new Dictionary>(); [NonSerialized] Dictionary m_Nodes = new Dictionary(); [SerializeField] List m_SerializableNodes = new List(); [SerializeField] List m_SerializableEdges = new List(); [NonSerialized] List m_AddedNodes = new List(); [NonSerialized] List m_RemovedNodes = new List(); [NonSerialized] List m_AddedEdges = new List(); [NonSerialized] List m_RemovedEdges = new List(); public IEnumerable addedNodes { get { return m_AddedNodes; } } public IEnumerable removedNodes { get { return m_RemovedNodes; } } public IEnumerable addedEdges { get { return m_AddedEdges; } } public IEnumerable removedEdges { get { return m_RemovedEdges; } } public IGraphObject owner { get; set; } public virtual void ClearChanges() { m_AddedNodes.Clear(); m_RemovedNodes.Clear(); m_AddedEdges.Clear(); m_RemovedEdges.Clear(); } public IEnumerable GetNodes() where T : INode { return m_Nodes.Values.OfType(); } public IEnumerable edges { get { return m_Edges; } } public virtual void AddNode(INode node) { AddNodeNoValidate(node); ValidateGraph(); } protected void AddNodeNoValidate(INode node) { m_Nodes.Add(node.guid, node); node.owner = this; m_AddedNodes.Add(node); } public virtual void RemoveNode(INode node) { if (!node.canDeleteNode) return; m_Nodes.Remove(node.guid); m_RemovedNodes.Add(node); ValidateGraph(); } protected void RemoveNodeNoValidate(INode node) { if (!node.canDeleteNode) return; m_Nodes.Remove(node.guid); m_RemovedNodes.Add(node); } void AddEdgeToNodeEdges(IEdge edge) { List inputEdges; if (!m_NodeEdges.TryGetValue(edge.inputSlot.nodeGuid, out inputEdges)) m_NodeEdges[edge.inputSlot.nodeGuid] = inputEdges = new List(); inputEdges.Add(edge); List outputEdges; if (!m_NodeEdges.TryGetValue(edge.outputSlot.nodeGuid, out outputEdges)) m_NodeEdges[edge.outputSlot.nodeGuid] = outputEdges = new List(); outputEdges.Add(edge); } public virtual Dictionary GetLegacyTypeRemapping() { return new Dictionary(); } static List s_TempEdges = new List(); protected IEdge ConnectNoValidate(SlotReference fromSlotRef, SlotReference toSlotRef) { var fromNode = GetNodeFromGuid(fromSlotRef.nodeGuid); var toNode = GetNodeFromGuid(toSlotRef.nodeGuid); if (fromNode == null || toNode == null) return null; // if fromNode is already connected to toNode // do now allow a connection as toNode will then // have an edge to fromNode creating a cycle. // if this is parsed it will lead to an infinite loop. var dependentNodes = new List(); NodeUtils.CollectNodesNodeFeedsInto(dependentNodes, toNode); if (dependentNodes.Contains(fromNode)) return null; var fromSlot = fromNode.FindSlot(fromSlotRef.slotId); var toSlot = toNode.FindSlot(toSlotRef.slotId); if (fromSlot.isOutputSlot == toSlot.isOutputSlot) return null; var outputSlot = fromSlot.isOutputSlot ? fromSlotRef : toSlotRef; var inputSlot = fromSlot.isInputSlot ? fromSlotRef : toSlotRef; s_TempEdges.Clear(); GetEdges(inputSlot, s_TempEdges); // remove any inputs that exits before adding foreach (var edge in s_TempEdges) { RemoveEdgeNoValidate(edge); } var newEdge = new Edge(outputSlot, inputSlot); m_Edges.Add(newEdge); m_AddedEdges.Add(newEdge); AddEdgeToNodeEdges(newEdge); //Debug.LogFormat("Connected edge: {0} -> {1} ({2} -> {3})\n{4}", newEdge.outputSlot.nodeGuid, newEdge.inputSlot.nodeGuid, fromNode.name, toNode.name, Environment.StackTrace); return newEdge; } public virtual IEdge Connect(SlotReference fromSlotRef, SlotReference toSlotRef) { var newEdge = ConnectNoValidate(fromSlotRef, toSlotRef); ValidateGraph(); return newEdge; } public virtual void RemoveEdge(IEdge e) { RemoveEdgeNoValidate(e); ValidateGraph(); } public void RemoveElements(IEnumerable nodes, IEnumerable edges) { foreach (var edge in edges.ToArray()) RemoveEdgeNoValidate(edge); foreach (var serializableNode in nodes.ToArray()) RemoveNodeNoValidate(serializableNode); ValidateGraph(); } protected void RemoveEdgeNoValidate(IEdge e) { e = m_Edges.FirstOrDefault(x => x.Equals(e)); if (e == null) throw new ArgumentException("Trying to remove an edge that does not exist.", "e"); m_Edges.Remove(e); List inputNodeEdges; if (m_NodeEdges.TryGetValue(e.inputSlot.nodeGuid, out inputNodeEdges)) inputNodeEdges.Remove(e); List outputNodeEdges; if (m_NodeEdges.TryGetValue(e.outputSlot.nodeGuid, out outputNodeEdges)) outputNodeEdges.Remove(e); m_RemovedEdges.Add(e); } public INode GetNodeFromGuid(Guid guid) { INode node; m_Nodes.TryGetValue(guid, out node); return node; } public bool ContainsNodeGuid(Guid guid) { return m_Nodes.ContainsKey(guid); } public T GetNodeFromGuid(Guid guid) where T : INode { var node = GetNodeFromGuid(guid); if (node is T) return (T)node; return default(T); } public void GetEdges(SlotReference s, List foundEdges) { var node = GetNodeFromGuid(s.nodeGuid); if (node == null) { Debug.LogWarning("Node does not exist"); return; } ISlot slot = node.FindSlot(s.slotId); List candidateEdges; if (!m_NodeEdges.TryGetValue(s.nodeGuid, out candidateEdges)) return; foreach (var edge in candidateEdges) { var cs = slot.isInputSlot ? edge.inputSlot : edge.outputSlot; if (cs.nodeGuid == s.nodeGuid && cs.slotId == s.slotId) foundEdges.Add(edge); } } public virtual void OnBeforeSerialize() { m_SerializableNodes = SerializationHelper.Serialize(m_Nodes.Values); m_SerializableEdges = SerializationHelper.Serialize(m_Edges); } public virtual void OnAfterDeserialize() { var nodes = SerializationHelper.Deserialize(m_SerializableNodes, GetLegacyTypeRemapping()); m_Nodes = new Dictionary(nodes.Count); foreach (var node in nodes) { node.owner = this; node.UpdateNodeAfterDeserialization(); m_Nodes.Add(node.guid, node); } m_SerializableNodes = null; m_Edges = SerializationHelper.Deserialize(m_SerializableEdges, null); m_SerializableEdges = null; foreach (var edge in m_Edges) AddEdgeToNodeEdges(edge); } public virtual void ValidateGraph() { //First validate edges, remove any //orphans. This can happen if a user //manually modifies serialized data //of if they delete a node in the inspector //debug view. foreach (var edge in edges.ToArray()) { var outputNode = GetNodeFromGuid(edge.outputSlot.nodeGuid); var inputNode = GetNodeFromGuid(edge.inputSlot.nodeGuid); if (outputNode == null || inputNode == null || outputNode.FindOutputSlot(edge.outputSlot.slotId) == null || inputNode.FindInputSlot(edge.inputSlot.slotId) == null) { //orphaned edge RemoveEdgeNoValidate(edge); } } foreach (var node in GetNodes()) node.ValidateNode(); foreach (var edge in m_AddedEdges.ToList()) { if (!ContainsNodeGuid(edge.outputSlot.nodeGuid) || !ContainsNodeGuid(edge.inputSlot.nodeGuid)) { Debug.LogWarningFormat("Added edge is invalid: {0} -> {1}\n{2}", edge.outputSlot.nodeGuid, edge.inputSlot.nodeGuid, Environment.StackTrace); m_AddedEdges.Remove(edge); } } } public virtual void ReplaceWith(IGraph other) { other.ValidateGraph(); ValidateGraph(); // Current tactic is to remove all nodes and edges and then re-add them, such that depending systems // will re-initialize with new references. using (var pooledList = ListPool.GetDisposable()) { var removedNodeEdges = pooledList.value; removedNodeEdges.AddRange(m_Edges); foreach (var edge in removedNodeEdges) RemoveEdgeNoValidate(edge); } using (var removedNodesPooledObject = ListPool.GetDisposable()) { var removedNodeGuids = removedNodesPooledObject.value; removedNodeGuids.AddRange(m_Nodes.Keys); foreach (var nodeGuid in removedNodeGuids) RemoveNodeNoValidate(m_Nodes[nodeGuid]); } ValidateGraph(); foreach (var node in other.GetNodes()) AddNodeNoValidate(node); foreach (var edge in other.edges) ConnectNoValidate(edge.outputSlot, edge.inputSlot); ValidateGraph(); } public void OnEnable() { foreach (var node in GetNodes().OfType()) { node.OnEnable(); } } } }