using System; using System.Linq; using System.Collections.Generic; using UnityEditor.VFX.Block; using UnityEngine; namespace UnityEditor.VFX { static class VFXSubgraphUtility { public static bool IsSubgraphModel(VFXModel model) => model is VFXSubgraphBlock or VFXSubgraphContext or VFXSubgraphOperator; public static int TransferExpressionToParameters(IList inputExpression, IEnumerable parameters, List backedUpExpressions = null) { int cptSlot = 0; foreach (var param in parameters) { VFXSlot outputSlot = param.outputSlots[0]; param.subgraphMode = true; if (inputExpression.Count <= cptSlot) continue; foreach (var slot in outputSlot.GetExpressionSlots()) { if (backedUpExpressions != null) backedUpExpressions.Add(slot.GetExpression()); slot.SetExpression(inputExpression[cptSlot]); cptSlot += 1; } } return cptSlot; } public static VFXPropertyWithValue GetPropertyFromInputParameter(VFXParameter param) { List attributes = new List(); if (!string.IsNullOrEmpty(param.tooltip)) attributes.Add(new TooltipAttribute(param.tooltip)); if (param.valueFilter == VFXValueFilter.Range) attributes.Add(new RangeAttribute((float)VFXConverter.ConvertTo(param.min, typeof(float)), (float)VFXConverter.ConvertTo(param.max, typeof(float)))); else if (param.valueFilter == VFXValueFilter.Enum) attributes.Add(new EnumAttribute(param.enumValues.ToArray())); return new VFXPropertyWithValue(new VFXProperty(param.type, param.exposedName, attributes.ToArray()), param.value); } public static bool InputPredicate(VFXParameter param) { return param.exposed && !param.isOutput; } public static bool OutputPredicate(VFXParameter param) { return param.isOutput; } public static IEnumerable GetParameters(IEnumerable models, Func predicate) { return models.OfType().Where(predicate).OrderBy(t => t.order); } } [VFXHelpURL("Subgraph")] [VFXInfo(name = "Empty Subgraph Operator")] class VFXSubgraphOperator : VFXOperator, IVFXAttributeUsage { bool m_IsMissing; [VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), SerializeField] protected VisualEffectSubgraphOperator m_Subgraph; public VFXSubgraphOperator() { // This allow to detect when a resource is deleted or restored EditorApplication.projectChanged += OnProjectOrHierarchyChanged; } public void OnDestroy() { EditorApplication.projectChanged -= OnProjectOrHierarchyChanged; } public VisualEffectSubgraphOperator subgraph { get { 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; } m_IsMissing = m_Subgraph == null; } return m_Subgraph; } } [NonSerialized] VFXModel[] m_SubChildren; public void RecreateCopy() { ClearCopy(); if (subgraph == null) { m_SubChildren = null; return; } var graph = m_Subgraph.GetResource().GetOrCreateGraph(); HashSet dependencies = new HashSet(); foreach (var child in graph.children.Where(t => t is VFXOperator || t is VFXParameter)) { dependencies.Add(child); child.CollectDependencies(dependencies); } var copy = VFXMemorySerializer.DuplicateObjects(dependencies.ToArray()); m_SubChildren = copy.OfType().Where(t => t is VFXOperator || t is VFXParameter).ToArray(); foreach (var child in copy) { child.hideFlags = HideFlags.HideAndDontSave; } var usedSubgraph = m_Subgraph.GetResource().GetOrCreateGraph(); usedSubgraph.SyncCustomAttributes(); if (GetGraph() is { } mainGraph) { mainGraph.SyncCustomAttributes(); } ResyncCustomAttributes(); } private void ClearCopy() { if (m_SubChildren != null) { foreach (var child in m_SubChildren) { if (child != null) { ScriptableObject.DestroyImmediate(child, true); } } m_SubChildren = null; } } public sealed override string name => m_Subgraph != null ? ObjectNames.NicifyVariableName(m_Subgraph.name) : "Empty Subgraph Operator"; protected override IEnumerable inputProperties { get { if (m_SubChildren == null) RecreateCopy(); return GetParameters(VFXSubgraphUtility.InputPredicate) .OrderBy(x => x.order) .Select(VFXSubgraphUtility.GetPropertyFromInputParameter); } } protected override IEnumerable outputProperties { get { foreach (var param in GetParameters(VFXSubgraphUtility.OutputPredicate).OrderBy(t => t.order)) { if (!string.IsNullOrEmpty(param.tooltip)) yield return new VFXPropertyWithValue(new VFXProperty(param.type, param.exposedName, new TooltipAttribute(param.tooltip))); else yield return new VFXPropertyWithValue(new VFXProperty(param.type, param.exposedName)); } } } public override void GetImportDependentAssets(HashSet dependencies) { base.GetImportDependentAssets(dependencies); if (!object.ReferenceEquals(m_Subgraph, null)) { dependencies.Add(m_Subgraph.GetInstanceID()); } } protected internal override void Invalidate(VFXModel model, InvalidationCause cause) { if (cause == InvalidationCause.kSettingChanged) { var graph = GetGraph(); if (graph != null && m_Subgraph != null && m_Subgraph.GetResource() is {} resource) { var otherGraph = resource.GetOrCreateGraph(); if (otherGraph == graph || otherGraph.subgraphDependencies.Contains(graph.GetResource().visualEffectObject)) m_Subgraph = null; // prevent cyclic dependencies. if (graph.GetResource().isSubgraph) // BuildSubgraphDependencies is called for vfx by recompilation, but in subgraph we must call it explicitly graph.BuildSubgraphDependencies(); RecreateCopy(); } } base.Invalidate(model, cause); } IEnumerable GetParameters(Func predicate) { return m_Subgraph != null ? VFXSubgraphUtility.GetParameters(m_SubChildren, predicate) : Enumerable.Empty(); } public override void CollectDependencies(HashSet objs, bool ownedOnly = true) { base.CollectDependencies(objs, ownedOnly); if (ownedOnly || m_Subgraph == null) return; m_Subgraph.GetResource().GetOrCreateGraph().CollectDependencies(objs, false); } public override void CheckGraphBeforeImport() { base.CheckGraphBeforeImport(); // If the graph is reimported it can be because one of its dependency such as the subgraphs, has been changed. if (!VFXGraph.explicitCompile) { MarkOutputExpressionsAsOutOfDate(); ResyncSlots(true); ResyncCustomAttributes(); } } protected override void OnAdded() { base.OnAdded(); ResyncCustomAttributes(); } protected override VFXExpression[] BuildExpression(VFXExpression[] inputExpression) { if (subgraph == null) return Array.Empty(); if (m_SubChildren == null) RecreateCopy(); // Change all the inputExpressions of the parameters. var parameters = GetParameters(VFXSubgraphUtility.InputPredicate).OrderBy(t => t.order); var backedUpExpressions = new List(); VFXSubgraphUtility.TransferExpressionToParameters(inputExpression, parameters, backedUpExpressions); List outputExpressions = new List(); foreach (var param in GetParameters(VFXSubgraphUtility.OutputPredicate)) { outputExpressions.AddRange(param.inputSlots[0].GetExpressionSlots().Select(t => t.GetExpression())); } return outputExpressions.ToArray(); } void OnProjectOrHierarchyChanged() { var wasMissing = m_IsMissing; var temp = subgraph; if (wasMissing != m_IsMissing) { Invalidate(InvalidationCause.kExpressionGraphChanged); } } private void ResyncCustomAttributes() { var graph = GetGraph(); if (graph == null || m_Subgraph == null) { return; } var usedSubgraph = m_Subgraph.GetResource().GetOrCreateGraph(); foreach (var customAttribute in usedSubgraph.customAttributes) { if (!graph.attributesManager.Exist(customAttribute.attributeName)) { graph.TryAddCustomAttribute(customAttribute.attributeName, CustomAttributeUtility.GetValueType(customAttribute.type), customAttribute.description, true, out _); graph.Invalidate(InvalidationCause.kExpressionGraphChanged); } else { graph.TryUpdateCustomAttribute(customAttribute.attributeName, customAttribute.type, customAttribute.description, true); } } graph.SetCustomAttributeDirty(); } public IEnumerable usedAttributes { get { if (m_Subgraph != null) { var usedSubgraph = m_Subgraph.GetResource().GetOrCreateGraph(); foreach (var customAttribute in usedSubgraph.customAttributes) { if (usedSubgraph.attributesManager.TryFind(customAttribute.attributeName, out var attribute)) { yield return attribute; } } } } } public void Rename(string oldName, string newName) { throw new NotSupportedException("The subgraph operator can use attributes, but cannot rename them"); } } }