using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using UnityEngine; using UnityEditor.Graphing; using UnityEditor.Graphing.Util; using UnityEditor.Rendering; using Edge = UnityEditor.Graphing.Edge; namespace UnityEditor.ShaderGraph { [Serializable] [FormerName("UnityEditor.ShaderGraph.MaterialGraph")] [FormerName("UnityEditor.ShaderGraph.SubGraph")] [FormerName("UnityEditor.ShaderGraph.AbstractMaterialGraph")] sealed class GraphData : ISerializationCallbackReceiver { public GraphObject owner { get; set; } #region Input data [NonSerialized] List m_Properties = new List(); public IEnumerable properties { get { return m_Properties; } } [SerializeField] List m_SerializedProperties = new List(); [NonSerialized] List m_Keywords = new List(); public IEnumerable keywords { get { return m_Keywords; } } [SerializeField] List m_SerializedKeywords = new List(); [NonSerialized] List m_AddedInputs = new List(); public IEnumerable addedInputs { get { return m_AddedInputs; } } [NonSerialized] List m_RemovedInputs = new List(); public IEnumerable removedInputs { get { return m_RemovedInputs; } } [NonSerialized] List m_MovedInputs = new List(); public IEnumerable movedInputs { get { return m_MovedInputs; } } public string assetGuid { get; set; } #endregion #region Node data [NonSerialized] Stack m_FreeNodeTempIds = new Stack(); [NonSerialized] List m_Nodes = new List(); [NonSerialized] Dictionary m_NodeDictionary = new Dictionary(); public IEnumerable GetNodes() { return m_Nodes.Where(x => x != null).OfType(); } [SerializeField] List m_SerializableNodes = new List(); [NonSerialized] List m_AddedNodes = new List(); public IEnumerable addedNodes { get { return m_AddedNodes; } } [NonSerialized] List m_RemovedNodes = new List(); public IEnumerable removedNodes { get { return m_RemovedNodes; } } [NonSerialized] List m_PastedNodes = new List(); public IEnumerable pastedNodes { get { return m_PastedNodes; } } #endregion #region Group Data [SerializeField] List m_Groups = new List(); public IEnumerable groups { get { return m_Groups; } } [NonSerialized] List m_AddedGroups = new List(); public IEnumerable addedGroups { get { return m_AddedGroups; } } [NonSerialized] List m_RemovedGroups = new List(); public IEnumerable removedGroups { get { return m_RemovedGroups; } } [NonSerialized] List m_PastedGroups = new List(); public IEnumerable pastedGroups { get { return m_PastedGroups; } } [NonSerialized] List m_ParentGroupChanges = new List(); public IEnumerable parentGroupChanges { get { return m_ParentGroupChanges; } } [NonSerialized] GroupData m_MostRecentlyCreatedGroup; public GroupData mostRecentlyCreatedGroup => m_MostRecentlyCreatedGroup; [NonSerialized] Dictionary> m_GroupItems = new Dictionary>(); public IEnumerable GetItemsInGroup(GroupData groupData) { if (m_GroupItems.TryGetValue(groupData.guid, out var nodes)) { return nodes; } return Enumerable.Empty(); } #endregion #region StickyNote Data [SerializeField] List m_StickyNotes = new List(); public IEnumerable stickyNotes => m_StickyNotes; [NonSerialized] List m_AddedStickyNotes = new List(); public List addedStickyNotes => m_AddedStickyNotes; [NonSerialized] List m_RemovedNotes = new List(); public IEnumerable removedNotes => m_RemovedNotes; [NonSerialized] List m_PastedStickyNotes = new List(); public IEnumerable pastedStickyNotes => m_PastedStickyNotes; #endregion #region Edge data [NonSerialized] List m_Edges = new List(); public IEnumerable edges { get { return m_Edges; } } [SerializeField] List m_SerializableEdges = new List(); [NonSerialized] Dictionary> m_NodeEdges = new Dictionary>(); [NonSerialized] List m_AddedEdges = new List(); public IEnumerable addedEdges { get { return m_AddedEdges; } } [NonSerialized] List m_RemovedEdges = new List(); public IEnumerable removedEdges { get { return m_RemovedEdges; } } #endregion [SerializeField] InspectorPreviewData m_PreviewData = new InspectorPreviewData(); public InspectorPreviewData previewData { get { return m_PreviewData; } set { m_PreviewData = value; } } [SerializeField] string m_Path; public string path { get { return m_Path; } set { if (m_Path == value) return; m_Path = value; if(owner != null) owner.RegisterCompleteObjectUndo("Change Path"); } } public MessageManager messageManager { get; set; } public bool isSubGraph { get; set; } [SerializeField] private ConcretePrecision m_ConcretePrecision = ConcretePrecision.Float; public ConcretePrecision concretePrecision { get => m_ConcretePrecision; set => m_ConcretePrecision = value; } [NonSerialized] Guid m_ActiveOutputNodeGuid; public Guid activeOutputNodeGuid { get { return m_ActiveOutputNodeGuid; } set { if (value != m_ActiveOutputNodeGuid) { m_ActiveOutputNodeGuid = value; m_OutputNode = null; didActiveOutputNodeChange = true; } } } [SerializeField] string m_ActiveOutputNodeGuidSerialized; [NonSerialized] private AbstractMaterialNode m_OutputNode; public AbstractMaterialNode outputNode { get { // find existing node if (m_OutputNode == null) { if (isSubGraph) { m_OutputNode = GetNodes().FirstOrDefault(); } else { m_OutputNode = GetNodeFromGuid(m_ActiveOutputNodeGuid); } } return m_OutputNode; } } public bool didActiveOutputNodeChange { get; set; } public GraphData() { m_GroupItems[Guid.Empty] = new List(); } public void ClearChanges() { m_AddedNodes.Clear(); m_RemovedNodes.Clear(); m_PastedNodes.Clear(); m_ParentGroupChanges.Clear(); m_AddedGroups.Clear(); m_RemovedGroups.Clear(); m_PastedGroups.Clear(); m_AddedEdges.Clear(); m_RemovedEdges.Clear(); m_AddedInputs.Clear(); m_RemovedInputs.Clear(); m_MovedInputs.Clear(); m_AddedStickyNotes.Clear(); m_RemovedNotes.Clear(); m_PastedStickyNotes.Clear(); m_MostRecentlyCreatedGroup = null; didActiveOutputNodeChange = false; } public void AddNode(AbstractMaterialNode node) { if (node is AbstractMaterialNode materialNode) { if (isSubGraph && !materialNode.allowedInSubGraph) { Debug.LogWarningFormat("Attempting to add {0} to Sub Graph. This is not allowed.", materialNode.GetType()); return; } AddNodeNoValidate(materialNode); // If adding a Sub Graph node whose asset contains Keywords // Need to restest Keywords against the variant limit if(node is SubGraphNode subGraphNode && subGraphNode.asset.keywords.Count > 0) { OnKeywordChangedNoValidate(); } ValidateGraph(); } else { Debug.LogWarningFormat("Trying to add node {0} to Material graph, but it is not a {1}", node, typeof(AbstractMaterialNode)); } } public void CreateGroup(GroupData groupData) { if (AddGroup(groupData)) { m_MostRecentlyCreatedGroup = groupData; } } bool AddGroup(GroupData groupData) { if (m_Groups.Contains(groupData)) return false; m_Groups.Add(groupData); m_AddedGroups.Add(groupData); m_GroupItems.Add(groupData.guid, new List()); return true; } public void RemoveGroup(GroupData groupData) { RemoveGroupNoValidate(groupData); ValidateGraph(); } void RemoveGroupNoValidate(GroupData group) { if (!m_Groups.Contains(group)) throw new InvalidOperationException("Cannot remove a group that doesn't exist."); m_Groups.Remove(group); m_RemovedGroups.Add(group); if (m_GroupItems.TryGetValue(group.guid, out var items)) { foreach (IGroupItem groupItem in items.ToList()) { SetGroup(groupItem, null); } m_GroupItems.Remove(group.guid); } } public void AddStickyNote(StickyNoteData stickyNote) { if (m_StickyNotes.Contains(stickyNote)) { throw new InvalidOperationException("Sticky note has already been added to the graph."); } if (!m_GroupItems.ContainsKey(stickyNote.groupGuid)) { throw new InvalidOperationException("Trying to add sticky note with group that doesn't exist."); } m_StickyNotes.Add(stickyNote); m_AddedStickyNotes.Add(stickyNote); m_GroupItems[stickyNote.groupGuid].Add(stickyNote); } void RemoveNoteNoValidate(StickyNoteData stickyNote) { if (!m_StickyNotes.Contains(stickyNote)) { throw new InvalidOperationException("Cannot remove a note that doesn't exist."); } m_StickyNotes.Remove(stickyNote); m_RemovedNotes.Add(stickyNote); if (m_GroupItems.TryGetValue(stickyNote.groupGuid, out var groupItems)) { groupItems.Remove(stickyNote); } } public void SetGroup(IGroupItem node, GroupData group) { var groupChange = new ParentGroupChange() { groupItem = node, oldGroupGuid = node.groupGuid, // Checking if the groupdata is null. If it is, then it means node has been removed out of a group. // If the group data is null, then maybe the old group id should be removed newGroupGuid = group?.guid ?? Guid.Empty }; node.groupGuid = groupChange.newGroupGuid; var oldGroupNodes = m_GroupItems[groupChange.oldGroupGuid]; oldGroupNodes.Remove(node); m_GroupItems[groupChange.newGroupGuid].Add(node); m_ParentGroupChanges.Add(groupChange); } void AddNodeNoValidate(AbstractMaterialNode node) { if (node.groupGuid != Guid.Empty && !m_GroupItems.ContainsKey(node.groupGuid)) { throw new InvalidOperationException("Cannot add a node whose group doesn't exist."); } node.owner = this; if (m_FreeNodeTempIds.Any()) { var id = m_FreeNodeTempIds.Pop(); id.IncrementVersion(); node.tempId = id; m_Nodes[id.index] = node; } else { var id = new Identifier(m_Nodes.Count); node.tempId = id; m_Nodes.Add(node); } m_NodeDictionary.Add(node.guid, node); m_AddedNodes.Add(node); m_GroupItems[node.groupGuid].Add(node); } public void RemoveNode(AbstractMaterialNode node) { if (!node.canDeleteNode) { throw new InvalidOperationException($"Node {node.name} ({node.guid}) cannot be deleted."); } RemoveNodeNoValidate(node); ValidateGraph(); } void RemoveNodeNoValidate(AbstractMaterialNode node) { if (!m_NodeDictionary.ContainsKey(node.guid)) { throw new InvalidOperationException("Cannot remove a node that doesn't exist."); } m_Nodes[node.tempId.index] = null; m_FreeNodeTempIds.Push(node.tempId); m_NodeDictionary.Remove(node.guid); messageManager?.RemoveNode(node.tempId); m_RemovedNodes.Add(node); if (m_GroupItems.TryGetValue(node.groupGuid, out var groupItems)) { groupItems.Remove(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); } 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 == null || toSlot == null) return null; 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 IEdge Connect(SlotReference fromSlotRef, SlotReference toSlotRef) { var newEdge = ConnectNoValidate(fromSlotRef, toSlotRef); ValidateGraph(); return newEdge; } public void RemoveEdge(IEdge e) { RemoveEdgeNoValidate(e); ValidateGraph(); } public void RemoveElements(AbstractMaterialNode[] nodes, IEdge[] edges, GroupData[] groups, StickyNoteData[] notes) { foreach (var node in nodes) { if (!node.canDeleteNode) { throw new InvalidOperationException($"Node {node.name} ({node.guid}) cannot be deleted."); } } foreach (var edge in edges.ToArray()) { RemoveEdgeNoValidate(edge); } foreach (var serializableNode in nodes) { RemoveNodeNoValidate(serializableNode); } foreach (var noteData in notes) { RemoveNoteNoValidate(noteData); } foreach (var groupData in groups) { RemoveGroupNoValidate(groupData); } ValidateGraph(); } 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 AbstractMaterialNode GetNodeFromGuid(Guid guid) { AbstractMaterialNode node; m_NodeDictionary.TryGetValue(guid, out node); return node; } public AbstractMaterialNode GetNodeFromTempId(Identifier tempId) { if (tempId.index > m_Nodes.Count) throw new ArgumentException("Trying to retrieve a node using an identifier that does not exist."); var node = m_Nodes[tempId.index]; if (node == null) throw new Exception("Trying to retrieve a node using an identifier that does not exist."); if (node.tempId.version != tempId.version) throw new Exception("Trying to retrieve a node that was removed from the graph."); return node; } public bool ContainsNodeGuid(Guid guid) { return m_NodeDictionary.ContainsKey(guid); } public T GetNodeFromGuid(Guid guid) where T : AbstractMaterialNode { 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) { 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 IEnumerable GetEdges(SlotReference s) { var edges = new List(); GetEdges(s, edges); return edges; } public void CollectShaderProperties(PropertyCollector collector, GenerationMode generationMode) { foreach (var prop in properties) { if(prop is GradientShaderProperty gradientProp && generationMode == GenerationMode.Preview) { GradientUtil.GetGradientPropertiesForPreview(collector, gradientProp.referenceName, gradientProp.value); continue; } collector.AddShaderProperty(prop); } } public void CollectShaderKeywords(KeywordCollector collector, GenerationMode generationMode) { foreach (var keyword in keywords) { collector.AddShaderKeyword(keyword); } // Alwways calculate permutations when collecting collector.CalculateKeywordPermutations(); } public void AddGraphInput(ShaderInput input) { if (input == null) return; switch(input) { case AbstractShaderProperty property: if (m_Properties.Contains(property)) return; m_Properties.Add(property); break; case ShaderKeyword keyword: if (m_Keywords.Contains(keyword)) return; m_Keywords.Add(keyword); break; default: throw new ArgumentOutOfRangeException(); } m_AddedInputs.Add(input); } public void SanitizeGraphInputName(ShaderInput input) { input.displayName = input.displayName.Trim(); switch(input) { case AbstractShaderProperty property: input.displayName = GraphUtil.SanitizeName(properties.Where(p => p.guid != input.guid).Select(p => p.displayName), "{0} ({1})", input.displayName); break; case ShaderKeyword keyword: input.displayName = GraphUtil.SanitizeName(keywords.Where(p => p.guid != input.guid).Select(p => p.displayName), "{0} ({1})", input.displayName); break; default: throw new ArgumentOutOfRangeException(); } } public void SanitizeGraphInputReferenceName(ShaderInput input, string newName) { if (string.IsNullOrEmpty(newName)) return; string name = newName.Trim(); if (string.IsNullOrEmpty(name)) return; name = Regex.Replace(name, @"(?:[^A-Za-z_0-9])|(?:\s)", "_"); switch(input) { case AbstractShaderProperty property: property.overrideReferenceName = GraphUtil.SanitizeName(properties.Where(p => p.guid != property.guid).Select(p => p.referenceName), "{0}_{1}", name); break; case ShaderKeyword keyword: keyword.overrideReferenceName = GraphUtil.SanitizeName(keywords.Where(p => p.guid != input.guid).Select(p => p.referenceName), "{0}_{1}", name).ToUpper(); break; default: throw new ArgumentOutOfRangeException(); } } public void RemoveGraphInput(ShaderInput input) { switch(input) { case AbstractShaderProperty property: var propetyNodes = GetNodes().Where(x => x.propertyGuid == input.guid).ToList(); foreach (var propNode in propetyNodes) ReplacePropertyNodeWithConcreteNodeNoValidate(propNode); break; } RemoveGraphInputNoValidate(input.guid); ValidateGraph(); } public void MoveProperty(AbstractShaderProperty property, int newIndex) { if (newIndex > m_Properties.Count || newIndex < 0) throw new ArgumentException("New index is not within properties list."); var currentIndex = m_Properties.IndexOf(property); if (currentIndex == -1) throw new ArgumentException("Property is not in graph."); if (newIndex == currentIndex) return; m_Properties.RemoveAt(currentIndex); if (newIndex > currentIndex) newIndex--; var isLast = newIndex == m_Properties.Count; if (isLast) m_Properties.Add(property); else m_Properties.Insert(newIndex, property); if (!m_MovedInputs.Contains(property)) m_MovedInputs.Add(property); } public void MoveKeyword(ShaderKeyword keyword, int newIndex) { if (newIndex > m_Keywords.Count || newIndex < 0) throw new ArgumentException("New index is not within keywords list."); var currentIndex = m_Keywords.IndexOf(keyword); if (currentIndex == -1) throw new ArgumentException("Keyword is not in graph."); if (newIndex == currentIndex) return; m_Keywords.RemoveAt(currentIndex); if (newIndex > currentIndex) newIndex--; var isLast = newIndex == m_Keywords.Count; if (isLast) m_Keywords.Add(keyword); else m_Keywords.Insert(newIndex, keyword); if (!m_MovedInputs.Contains(keyword)) m_MovedInputs.Add(keyword); } public int GetGraphInputIndex(ShaderInput input) { switch(input) { case AbstractShaderProperty property: return m_Properties.IndexOf(property); case ShaderKeyword keyword: return m_Keywords.IndexOf(keyword); default: throw new ArgumentOutOfRangeException(); } } void RemoveGraphInputNoValidate(Guid guid) { if (m_Properties.RemoveAll(x => x.guid == guid) > 0 || m_Keywords.RemoveAll(x => x.guid == guid) > 0) { m_RemovedInputs.Add(guid); m_AddedInputs.RemoveAll(x => x.guid == guid); m_MovedInputs.RemoveAll(x => x.guid == guid); } } static List s_TempEdges = new List(); public void ReplacePropertyNodeWithConcreteNode(PropertyNode propertyNode) { ReplacePropertyNodeWithConcreteNodeNoValidate(propertyNode); ValidateGraph(); } void ReplacePropertyNodeWithConcreteNodeNoValidate(PropertyNode propertyNode) { var property = properties.FirstOrDefault(x => x.guid == propertyNode.propertyGuid); if (property == null) return; var node = property.ToConcreteNode() as AbstractMaterialNode; if (node == null) return; var slot = propertyNode.FindOutputSlot(PropertyNode.OutputSlotId); var newSlot = node.GetOutputSlots().FirstOrDefault(s => s.valueType == slot.valueType); if (newSlot == null) return; node.drawState = propertyNode.drawState; node.groupGuid = propertyNode.groupGuid; AddNodeNoValidate(node); foreach (var edge in this.GetEdges(slot.slotReference)) ConnectNoValidate(newSlot.slotReference, edge.inputSlot); RemoveNodeNoValidate(propertyNode); } public void OnKeywordChanged() { OnKeywordChangedNoValidate(); ValidateGraph(); } public void OnKeywordChangedNoValidate() { var allNodes = GetNodes(); foreach(AbstractMaterialNode node in allNodes) { node.Dirty(ModificationScope.Topological); node.ValidateNode(); } } public void ValidateGraph() { var propertyNodes = GetNodes().Where(n => !m_Properties.Any(p => p.guid == n.propertyGuid)).ToArray(); foreach (var pNode in propertyNodes) ReplacePropertyNodeWithConcreteNodeNoValidate(pNode); messageManager?.ClearAllFromProvider(this); //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); MaterialSlot outputSlot = null; MaterialSlot inputSlot = null; if (outputNode != null && inputNode != null) { outputSlot = outputNode.FindOutputSlot(edge.outputSlot.slotId); inputSlot = inputNode.FindInputSlot(edge.inputSlot.slotId); } if (outputNode == null || inputNode == null || outputSlot == null || inputSlot == null) { //orphaned edge RemoveEdgeNoValidate(edge); } } var temporaryMarks = IndexSetPool.Get(); var permanentMarks = IndexSetPool.Get(); var slots = ListPool.Get(); // Make sure we process a node's children before the node itself. var stack = StackPool.Get(); foreach (var node in GetNodes()) { stack.Push(node); } while (stack.Count > 0) { var node = stack.Pop(); if (permanentMarks.Contains(node.tempId.index)) { continue; } if (temporaryMarks.Contains(node.tempId.index)) { node.ValidateNode(); permanentMarks.Add(node.tempId.index); } else { temporaryMarks.Add(node.tempId.index); stack.Push(node); node.GetInputSlots(slots); foreach (var inputSlot in slots) { var nodeEdges = GetEdges(inputSlot.slotReference); foreach (var edge in nodeEdges) { var fromSocketRef = edge.outputSlot; var childNode = GetNodeFromGuid(fromSocketRef.nodeGuid); if (childNode != null) { stack.Push(childNode); } } } slots.Clear(); } } StackPool.Release(stack); ListPool.Release(slots); IndexSetPool.Release(temporaryMarks); IndexSetPool.Release(permanentMarks); 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); } } foreach (var groupChange in m_ParentGroupChanges.ToList()) { if (groupChange.groupItem is AbstractMaterialNode node && !ContainsNodeGuid(node.guid)) { m_ParentGroupChanges.Remove(groupChange); } if (groupChange.groupItem is StickyNoteData stickyNote && !m_StickyNotes.Contains(stickyNote)) { m_ParentGroupChanges.Remove(groupChange); } } } public void AddValidationError(Identifier id, string errorMessage, ShaderCompilerMessageSeverity severity = ShaderCompilerMessageSeverity.Error) { messageManager?.AddOrAppendError(this, id, new ShaderMessage(errorMessage, severity)); } public void ClearErrorsForNode(AbstractMaterialNode node) { messageManager?.ClearNodesFromProvider(this, node.ToEnumerable()); } public void ReplaceWith(GraphData other) { if (other == null) throw new ArgumentException("Can only replace with another AbstractMaterialGraph", "other"); using (var removedInputsPooledObject = ListPool.GetDisposable()) { var removedInputGuids = removedInputsPooledObject.value; foreach (var property in m_Properties) removedInputGuids.Add(property.guid); foreach (var keyword in m_Keywords) removedInputGuids.Add(keyword.guid); foreach (var inputGuid in removedInputGuids) RemoveGraphInputNoValidate(inputGuid); } foreach (var otherProperty in other.properties) { if (!properties.Any(p => p.guid == otherProperty.guid)) AddGraphInput(otherProperty); } foreach (var otherKeyword in other.keywords) { if (!keywords.Any(p => p.guid == otherKeyword.guid)) AddGraphInput(otherKeyword); } 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 removedGroupsPooledObject = ListPool.GetDisposable()) { var removedGroupDatas = removedGroupsPooledObject.value; removedGroupDatas.AddRange(m_Groups); foreach (var groupData in removedGroupDatas) { RemoveGroupNoValidate(groupData); } } using (var removedNotesPooledObject = ListPool.GetDisposable()) { var removedNoteDatas = removedNotesPooledObject.value; removedNoteDatas.AddRange(m_StickyNotes); foreach (var groupData in removedNoteDatas) { RemoveNoteNoValidate(groupData); } } 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.Where(n => n != null).Select(n => n.guid)); foreach (var nodeGuid in removedNodeGuids) RemoveNodeNoValidate(m_NodeDictionary[nodeGuid]); } ValidateGraph(); foreach (GroupData groupData in other.groups) AddGroup(groupData); foreach (var stickyNote in other.stickyNotes) { AddStickyNote(stickyNote); } foreach (var node in other.GetNodes()) AddNodeNoValidate(node); foreach (var edge in other.edges) ConnectNoValidate(edge.outputSlot, edge.inputSlot); ValidateGraph(); } internal void PasteGraph(CopyPasteGraph graphToPaste, List remappedNodes, List remappedEdges) { var groupGuidMap = new Dictionary(); foreach (var group in graphToPaste.groups) { var position = group.position; position.x += 30; position.y += 30; GroupData newGroup = new GroupData(group.title, position); var oldGuid = group.guid; var newGuid = newGroup.guid; groupGuidMap[oldGuid] = newGuid; AddGroup(newGroup); m_PastedGroups.Add(newGroup); } foreach (var stickyNote in graphToPaste.stickyNotes) { var position = stickyNote.position; position.x += 30; position.y += 30; StickyNoteData pastedStickyNote = new StickyNoteData(stickyNote.title, stickyNote.content, position); if (groupGuidMap.ContainsKey(stickyNote.groupGuid)) { pastedStickyNote.groupGuid = groupGuidMap[stickyNote.groupGuid]; } AddStickyNote(pastedStickyNote); m_PastedStickyNotes.Add(pastedStickyNote); } var nodeGuidMap = new Dictionary(); foreach (var node in graphToPaste.GetNodes()) { AbstractMaterialNode pastedNode = node; var oldGuid = node.guid; var newGuid = node.RewriteGuid(); nodeGuidMap[oldGuid] = newGuid; // Check if the property nodes need to be made into a concrete node. if (node is PropertyNode propertyNode) { // If the property is not in the current graph, do check if the // property can be made into a concrete node. if (!m_Properties.Select(x => x.guid).Contains(propertyNode.propertyGuid)) { // If the property is in the serialized paste graph, make the property node into a property node. var pastedGraphMetaProperties = graphToPaste.metaProperties.Where(x => x.guid == propertyNode.propertyGuid); if (pastedGraphMetaProperties.Any()) { pastedNode = pastedGraphMetaProperties.FirstOrDefault().ToConcreteNode(); pastedNode.drawState = node.drawState; nodeGuidMap[oldGuid] = pastedNode.guid; } } } AbstractMaterialNode abstractMaterialNode = (AbstractMaterialNode)node; // Check if the node is inside a group if (groupGuidMap.ContainsKey(abstractMaterialNode.groupGuid)) { var absNode = pastedNode as AbstractMaterialNode; absNode.groupGuid = groupGuidMap[abstractMaterialNode.groupGuid]; pastedNode = absNode; } var drawState = node.drawState; var position = drawState.position; position.x += 30; position.y += 30; drawState.position = position; node.drawState = drawState; remappedNodes.Add(pastedNode); AddNode(pastedNode); // add the node to the pasted node list m_PastedNodes.Add(pastedNode); // Check if the keyword nodes need to have their keywords copied. if (node is KeywordNode keywordNode) { // If the keyword is not in the current graph and is in the serialized paste graph copy it. if (!keywords.Select(x => x.guid).Contains(keywordNode.keywordGuid)) { var pastedGraphMetaKeywords = graphToPaste.metaKeywords.Where(x => x.guid == keywordNode.keywordGuid); if (pastedGraphMetaKeywords.Any()) { var keyword = pastedGraphMetaKeywords.FirstOrDefault(x => x.guid == keywordNode.keywordGuid); SanitizeGraphInputName(keyword); SanitizeGraphInputReferenceName(keyword, keyword.overrideReferenceName); AddGraphInput(keyword); } } // Always update Keyword nodes to handle any collisions resolved on the Keyword keywordNode.UpdateNode(); } } // only connect edges within pasted elements, discard // external edges. foreach (var edge in graphToPaste.edges) { var outputSlot = edge.outputSlot; var inputSlot = edge.inputSlot; Guid remappedOutputNodeGuid; Guid remappedInputNodeGuid; if (nodeGuidMap.TryGetValue(outputSlot.nodeGuid, out remappedOutputNodeGuid) && nodeGuidMap.TryGetValue(inputSlot.nodeGuid, out remappedInputNodeGuid)) { var outputSlotRef = new SlotReference(remappedOutputNodeGuid, outputSlot.slotId); var inputSlotRef = new SlotReference(remappedInputNodeGuid, inputSlot.slotId); remappedEdges.Add(Connect(outputSlotRef, inputSlotRef)); } } ValidateGraph(); } public void OnBeforeSerialize() { m_SerializableNodes = SerializationHelper.Serialize(GetNodes()); m_SerializableEdges = SerializationHelper.Serialize(m_Edges); m_SerializedProperties = SerializationHelper.Serialize(m_Properties); m_SerializedKeywords = SerializationHelper.Serialize(m_Keywords); m_ActiveOutputNodeGuidSerialized = m_ActiveOutputNodeGuid == Guid.Empty ? null : m_ActiveOutputNodeGuid.ToString(); } public void OnAfterDeserialize() { // have to deserialize 'globals' before nodes m_Properties = SerializationHelper.Deserialize(m_SerializedProperties, GraphUtil.GetLegacyTypeRemapping()); m_Keywords = SerializationHelper.Deserialize(m_SerializedKeywords, GraphUtil.GetLegacyTypeRemapping()); var nodes = SerializationHelper.Deserialize(m_SerializableNodes, GraphUtil.GetLegacyTypeRemapping()); m_Nodes = new List(nodes.Count); m_NodeDictionary = new Dictionary(nodes.Count); foreach (var group in m_Groups) { m_GroupItems.Add(group.guid, new List()); } foreach (var node in nodes) { node.owner = this; node.UpdateNodeAfterDeserialization(); node.tempId = new Identifier(m_Nodes.Count); m_Nodes.Add(node); m_NodeDictionary.Add(node.guid, node); m_GroupItems[node.groupGuid].Add(node); } foreach (var stickyNote in m_StickyNotes) { m_GroupItems[stickyNote.groupGuid].Add(stickyNote); } m_SerializableNodes = null; m_Edges = SerializationHelper.Deserialize(m_SerializableEdges, GraphUtil.GetLegacyTypeRemapping()); m_SerializableEdges = null; foreach (var edge in m_Edges) AddEdgeToNodeEdges(edge); m_OutputNode = null; if (!isSubGraph) { if (string.IsNullOrEmpty(m_ActiveOutputNodeGuidSerialized)) { var node = (AbstractMaterialNode)GetNodes().FirstOrDefault(); if (node != null) { m_ActiveOutputNodeGuid = node.guid; } } else { m_ActiveOutputNodeGuid = new Guid(m_ActiveOutputNodeGuidSerialized); } } } public void OnEnable() { foreach (var node in GetNodes().OfType()) { node.OnEnable(); } ShaderGraphPreferences.onVariantLimitChanged += OnKeywordChanged; } public void OnDisable() { ShaderGraphPreferences.onVariantLimitChanged -= OnKeywordChanged; } } [Serializable] class InspectorPreviewData { public SerializableMesh serializedMesh = new SerializableMesh(); [NonSerialized] public Quaternion rotation = Quaternion.identity; [NonSerialized] public float scale = 1f; } }