using System; using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEngine.VFX; using UnityEditor.VFX; namespace UnityEditor.VFX { [ExcludeFromPreset] class VFXSubgraphContext : VFXContext { [VFXSetting, SerializeField] protected VisualEffectAsset m_Subgraph; [NonSerialized] VFXModel[] m_SubChildren; VFXGraph m_UsedSubgraph; public VisualEffectAsset subgraph { get { return m_Subgraph; } } public static void CallOnGraphChanged(VFXGraph graph) { if (OnGraphChanged != null) OnGraphChanged(graph); } public IEnumerable subChildren { get { return m_SubChildren; } } static Action OnGraphChanged; public VFXSubgraphContext() : base(VFXContextType.Subgraph, VFXDataType.SpawnEvent, VFXDataType.None) { } public override void GetImportDependentAssets(HashSet dependencies) { base.GetImportDependentAssets(dependencies); if (!object.ReferenceEquals(m_Subgraph, null)) dependencies.Add(m_Subgraph.GetInstanceID()); } void GraphParameterChanged(VFXGraph graph) { VisualEffectAsset asset = graph != null && graph.GetResource() != null ? graph.GetResource().asset : null; if (m_Subgraph == asset && GetParent() != null) RecreateCopy(); } public const int s_MaxInputFlow = 5; protected override int inputFlowCount { get { return m_InputFlowNames.Count > s_MaxInputFlow ? s_MaxInputFlow : m_InputFlowNames.Count; } } public sealed override string name { get { return m_Subgraph != null ? m_Subgraph.name : "Subgraph"; } } void RefreshSubgraphObject() { if (m_Subgraph == null && !object.ReferenceEquals(m_Subgraph, null)) { string assetPath = AssetDatabase.GetAssetPath(m_Subgraph.GetInstanceID()); var newSubgraph = AssetDatabase.LoadAssetAtPath(assetPath); if (newSubgraph != null) { m_Subgraph = newSubgraph; } } } protected override IEnumerable inputProperties { get { RefreshSubgraphObject(); if (m_SubChildren == null && m_Subgraph != null) // if the subasset exists but the subchildren has not been recreated yet, return the existing slots { foreach (var slot in inputSlots) yield return new VFXPropertyWithValue(slot.property); } if (m_Subgraph != null) { foreach (var param in GetSortedInputParameters()) yield return VFXSubgraphUtility.GetPropertyFromInputParameter(param); } } } IEnumerable GetSortedInputParameters() { var resource = m_Subgraph.GetResource(); if (resource != null) { var graph = resource.GetOrCreateGraph(); if (graph != null) { var UIInfos = graph.UIInfos; var categoriesOrder = UIInfos.categories; if (categoriesOrder == null) categoriesOrder = new List(); return GetParameters(t => InputPredicate(t)).OrderBy(t => categoriesOrder.FindIndex(u => u.name == t.category)).ThenBy(t => t.order); } else { Debug.LogError("Can't find subgraph graph"); } } else { Debug.LogError("Cant't find subgraph resource"); } return Enumerable.Empty(); } public override VFXExpressionMapper GetExpressionMapper(VFXDeviceTarget target) { return null; } public override bool CanBeCompiled() { return subgraph != null; } static bool InputPredicate(VFXParameter param) { return param.exposed && !param.isOutput; } static bool OutputPredicate(VFXParameter param) { return param.isOutput; } IEnumerable GetParameters(Func predicate) { if (m_SubChildren == null) return Enumerable.Empty(); return m_SubChildren.OfType().Where(t => predicate(t)).OrderBy(t => t.order); } private new void OnEnable() { base.OnEnable(); OnGraphChanged += GraphParameterChanged; } public override void Sanitize(int version) { base.Sanitize(version); RecreateCopy(); } void SubChildrenOnInvalidate(VFXModel model, InvalidationCause cause) { Invalidate(this, cause); } private void OnDisable() { DetachFromOriginal(); OnGraphChanged -= GraphParameterChanged; } public void RecreateCopy() { DetachFromOriginal(); RefreshSubgraphObject(); if (m_Subgraph == null) { m_SubChildren = null; m_UsedSubgraph = null; return; } var resource = m_Subgraph.GetResource(); if (resource == null) { m_SubChildren = null; m_UsedSubgraph = null; return; } var graph = resource.GetOrCreateGraph(); HashSet dependencies = new HashSet(); graph.CollectDependencies(dependencies); dependencies.RemoveWhere(o => o == null); //script is missing should be removed from the list before copy. var duplicated = VFXMemorySerializer.DuplicateObjects(dependencies.ToArray()); m_SubChildren = duplicated.OfType().Where(t => t is VFXContext || t is VFXOperator || t is VFXParameter).ToArray(); m_UsedSubgraph = graph; foreach (var child in duplicated.Zip(dependencies, (a, b) => new { copy = a, original = b })) { child.copy.hideFlags = HideFlags.HideAndDontSave; if (child.copy is VFXSlot) { var original = child.original as VFXSlot; var copy = child.copy as VFXSlot; if (original.direction == VFXSlot.Direction.kInput || original.owner is VFXParameter) { m_OriginalToCopy[original] = copy; original.onInvalidateDelegate += OnOriginalSlotModified; } } else if (child.copy is VFXSubgraphBlock subgraphBlock) { subgraphBlock.RecreateCopy(); } } List newInputFlowNames = new List(); foreach (var basicEvent in m_SubChildren.OfType()) { if (!newInputFlowNames.Contains(basicEvent.eventName)) newInputFlowNames.Add(basicEvent.eventName); } bool hasStart = false; bool hasStop = false; foreach (var initialize in m_SubChildren.OfType()) { if (!hasStart && initialize.inputFlowSlot[0].link.Count() == 0) { hasStart = true; } if (!hasStop && initialize.inputFlowSlot[1].link.Count() == 0) { hasStop = true; } } int directEventCount = newInputFlowNames.Count; foreach (var subContext in m_SubChildren.OfType()) { for (int i = 0; i < subContext.inputFlowCount; ++i) { string name = subContext.GetInputFlowName(i); switch (name) { case VisualEffectAsset.PlayEventName: hasStart = true; break; case VisualEffectAsset.StopEventName: hasStop = true; break; default: m_InputFlowNames.Add(name); break; } } } newInputFlowNames.Sort(0, directEventCount, Comparer.Default); newInputFlowNames.Sort(directEventCount, newInputFlowNames.Count - directEventCount, Comparer.Default); if (hasStop) newInputFlowNames.Insert(0, VisualEffectAsset.StopEventName); if (hasStart) newInputFlowNames.Insert(0, VisualEffectAsset.PlayEventName); // Don't notify while doing this else asset is considered dirty after each call at RecreateCopy if (m_InputFlowNames == null || !newInputFlowNames.SequenceEqual(m_InputFlowNames) || inputFlowSlot.Length != inputFlowCount) { var oldLinks = new Dictionary>(); for (int i = 0; i < inputFlowSlot.Count() && i < m_InputFlowNames.Count; ++i) { oldLinks[GetInputFlowName(i)] = inputFlowSlot[i].link.ToList(); } m_InputFlowNames = newInputFlowNames; DetachAllInputFlowSlots(false); for (int i = 0; i < inputFlowSlot.Count(); ++i) { List ctxSlot; if (oldLinks.TryGetValue(GetInputFlowName(i), out ctxSlot)) foreach (var link in ctxSlot) InnerLink(link.context, this, link.slotIndex, i, false); } } SyncSlots(VFXSlot.Direction.kInput, true); } public VFXContext GetEventContext(string eventName) { return m_SubChildren.OfType().Where(t => t.eventName == eventName).FirstOrDefault(); } public string GetInputFlowName(int index) { return m_InputFlowNames[index]; } public int GetInputFlowIndex(string name) { return m_InputFlowNames.IndexOf(name); } [SerializeField] List m_InputFlowNames = new List(); private void DetachFromOriginal() { if (m_SubChildren != null) { HashSet deps = new HashSet(); foreach (var child in m_SubChildren) { if (child != null) { child.onInvalidateDelegate -= SubChildrenOnInvalidate; child.CollectDependencies(deps); ScriptableObject.DestroyImmediate(child, true); } } foreach (var obj in deps) { ScriptableObject.DestroyImmediate(obj, true); } foreach (var kv in m_OriginalToCopy) { kv.Key.onInvalidateDelegate -= OnOriginalSlotModified; } m_OriginalToCopy.Clear(); } m_SubChildren = null; } public void OnOriginalSlotModified(VFXModel original, InvalidationCause cause) { if (cause == InvalidationCause.kParamChanged) { m_OriginalToCopy[original as VFXSlot].value = (original as VFXSlot).value; Invalidate(InvalidationCause.kParamChanged); } } Dictionary m_OriginalToCopy = new Dictionary(); public void PatchInputExpressions() { if (m_SubChildren == null) return; var inputExpressions = new List(); foreach (var subSlot in inputSlots.SelectMany(t => t.GetExpressionSlots())) inputExpressions.Add(subSlot.GetExpression()); VFXSubgraphUtility.TransferExpressionToParameters(inputExpressions, GetSortedInputParameters()); } protected override void OnAdded() { base.OnAdded(); if (m_Subgraph != null) { var graph = GetGraph(); if (graph != null) { var otherGraph = m_Subgraph.GetResource().GetOrCreateGraph(); if (otherGraph == graph || otherGraph.subgraphDependencies.Contains(graph.GetResource().visualEffectObject)) m_Subgraph = null; // prevent cyclic dependencies. } } } protected override void OnInvalidate(VFXModel model, InvalidationCause cause) { if (cause == InvalidationCause.kSettingChanged || cause == InvalidationCause.kExpressionInvalidated) { if (cause == InvalidationCause.kSettingChanged) { if (m_Subgraph != null) { var graph = GetGraph(); if (graph != null) // that case it will be checked in OnAdded { var otherGraph = m_Subgraph.GetResource().GetOrCreateGraph(); if (otherGraph == graph || otherGraph.subgraphDependencies.Contains(graph.GetResource().visualEffectObject)) m_Subgraph = null; // prevent cyclic dependencies. } } if (m_Subgraph != null || object.ReferenceEquals(m_Subgraph, null) || m_UsedSubgraph == null || (m_Subgraph != null && m_UsedSubgraph != m_Subgraph.GetResource().GetOrCreateGraph())) // do not recreate subchildren if the subgraph is not available but is not null RecreateCopy(); } base.OnInvalidate(model, cause); PatchInputExpressions(); } else base.OnInvalidate(model, cause); } public override void CheckGraphBeforeImport() { base.CheckGraphBeforeImport(); // If the graph is reimported it can be because one of its depedency such as the subgraphs, has been changed. if (!VFXGraph.explicitCompile) ResyncSlots(true); } public override void CollectDependencies(HashSet objs, bool ownedOnly = true) { base.CollectDependencies(objs, ownedOnly); if (ownedOnly) return; if (m_Subgraph != null && m_SubChildren == null) RecreateCopy(); if (m_SubChildren != null) { foreach (var child in m_SubChildren) { if (!(child is VFXParameter)) { objs.Add(child); if (child is VFXModel) (child as VFXModel).CollectDependencies(objs, false); } } } } } }