using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; using UnityEditor.VFX; using UnityEngine.VFX; using UnityEngine.Profiling; namespace UnityEditor.VFX { class VFXObject : ScriptableObject { //Explicitly disable the Reset option on all VFXObject //Internal Reset() behavior leads to a dandling state in graph object [MenuItem("CONTEXT/VFXObject/Reset", false)] public static void DummyReset() { } [MenuItem("CONTEXT/VFXObject/Reset", true)] static bool ValidateDummyReset() { return false; } public Action onModified; void OnValidate() { Modified(false); } public void Modified(bool uiChange) { if (onModified != null) onModified(this, uiChange); } } [Serializable] abstract class VFXModel : VFXObject { public enum InvalidationCause { kStructureChanged, // Model structure (hierarchy) has changed kParamChanged, // Some parameter values have changed kSettingChanged, // A setting value has changed kSpaceChanged, // Space has been changed kConnectionChanged, // Connection have changed kExpressionInvalidated, // No direct change to the model but a change in connection was propagated from the parents kExpressionGraphChanged,// Expression graph must be recomputed kUIChanged, // UI stuff has changed kUIChangedTransient, // UI stuff has been changed be does not require serialization kEnableChanged, // Node has been enabled/disabled } public new virtual string name { get { return string.Empty; } } public virtual string libraryName { get { return name; } } public delegate void InvalidateEvent(VFXModel model, InvalidationCause cause); public event InvalidateEvent onInvalidateDelegate; protected VFXModel() { m_UICollapsed = true; } public virtual void OnEnable() { if (m_Children == null) m_Children = new List(); else { int nbRemoved = m_Children.RemoveAll(c => c == null);// Remove bad references if any if (nbRemoved > 0) Debug.LogWarning(String.Format("Remove {0} child(ren) that couldnt be deserialized from {1} of type {2}", nbRemoved, name, GetType())); } } public virtual void Sanitize(int version) {} public virtual void CheckGraphBeforeImport() {} public virtual void OnUnknownChange() { } public virtual void GetSourceDependentAssets(HashSet dependencies) { foreach (var child in children) child.GetSourceDependentAssets(dependencies); } public virtual void GetImportDependentAssets(HashSet dependencies) { //var monoScript = MonoScript.FromScriptableObject(this); //dependencies.Add(AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(monoScript))); foreach (var child in children) child.GetImportDependentAssets(dependencies); } public virtual void CollectDependencies(HashSet objs, bool ownedOnly = true) { foreach (var child in children) { objs.Add(child); child.CollectDependencies(objs, ownedOnly); } } protected virtual void OnInvalidate(VFXModel model, InvalidationCause cause) { if (onInvalidateDelegate != null) { Profiler.BeginSample("VFXEditor.OnInvalidateDelegate"); try { onInvalidateDelegate(model, cause); } finally { Profiler.EndSample(); } } } public void RefreshErrors(VFXGraph graph) { if (graph != null) { graph.errorManager.ClearAllErrors(this, VFXErrorOrigin.Invalidate); using (var reporter = new VFXInvalidateErrorReporter(graph.errorManager, this)) { try { GenerateErrors(reporter); } catch (Exception e) { Debug.LogException(e); } } } } protected virtual void OnAdded() {} protected virtual void OnRemoved() {} public virtual bool AcceptChild(VFXModel model, int index = -1) { return false; } public void AddChild(VFXModel model, int index = -1, bool notify = true) { int realIndex = index == -1 ? m_Children.Count : index; if (model.m_Parent != this || realIndex != GetIndex(model)) { if (!AcceptChild(model, index)) throw new ArgumentException("Cannot attach " + model + " to " + this); model.Detach(notify && model.m_Parent != this); // Dont notify if the owner is already this to avoid double invalidation realIndex = index == -1 ? m_Children.Count : index; // Recompute as the child may have been removed m_Children.Insert(realIndex, model); model.m_Parent = this; model.OnAdded(); if (notify) Invalidate(InvalidationCause.kStructureChanged); } } public void RemoveChild(VFXModel model, bool notify = true) { if (model.m_Parent != this) return; model.OnRemoved(); m_Children.Remove(model); model.m_Parent = null; if (notify) Invalidate(InvalidationCause.kStructureChanged); } public void RemoveAllChildren(bool notify = true) { while (m_Children.Count > 0) RemoveChild(m_Children[m_Children.Count - 1], notify); } public VFXModel GetParent() { return m_Parent; } public T GetFirstOfType() where T : VFXModel { if (this is T) return this as T; var parent = GetParent(); if (parent == null) return null; return parent.GetFirstOfType(); } public void Attach(VFXModel parent, bool notify = true) { parent.AddChild(this, -1, notify); } public void Detach(bool notify = true) { if (m_Parent == null) return; m_Parent.RemoveChild(this, notify); } public IEnumerable children { get { return m_Children; } } public VFXModel this[int index] { get { return m_Children[index]; } } public Vector2 position { get { return m_UIPosition; } set { if (m_UIPosition != value) { m_UIPosition = value; Invalidate(InvalidationCause.kUIChanged); } } } public bool collapsed { get { return m_UICollapsed; } set { if (m_UICollapsed != value) { m_UICollapsed = value; Invalidate(InvalidationCause.kUIChanged); } } } public bool superCollapsed { get { return m_UISuperCollapsed; } set { if (m_UISuperCollapsed != value) { m_UISuperCollapsed = value; Invalidate(InvalidationCause.kUIChanged); } } } public int GetNbChildren() { return m_Children.Count; } public int GetIndex(VFXModel child) { return m_Children.IndexOf(child); } public object GetSettingValue(string name) { var setting = GetSetting(name); if (setting.field == null) { throw new ArgumentException(string.Format("Unable to find field {0} in {1}", name, GetType().ToString())); } return setting.value; } public void SetSettingValue(string name, object value) { SetSettingValue(name, value, true); } public void SetSettingValues(IEnumerable> nameValues) { bool hasChanged = false; foreach (var kvp in nameValues) { if (SetSettingValueAndReturnIfChanged(kvp.Key, kvp.Value)) hasChanged = true; } if (hasChanged) Invalidate(InvalidationCause.kSettingChanged); } protected void SetSettingValue(string name, object value, bool notify) { bool hasChanged = SetSettingValueAndReturnIfChanged(name, value); if (hasChanged && notify) Invalidate(InvalidationCause.kSettingChanged); } private bool SetSettingValueAndReturnIfChanged(string name, object value) { var setting = GetSetting(name); if (setting.field == null) { throw new ArgumentException(string.Format("Unable to find field {0} in {1}", name, GetType().ToString())); } var currentValue = setting.value; if (currentValue != value) { setting.field.SetValue(setting.instance, value); OnSettingModified(setting); if (setting.instance != this) setting.instance.OnSettingModified(setting); return true; } return false; } // Override this method to update other settings based on a setting modification // Use OnIvalidate with KSettingChanged and not this method to handle other side effects public virtual void OnSettingModified(VFXSetting setting) {} public virtual IEnumerable GetFilteredOutEnumerators(string name) { return null; } public virtual VFXSetting GetSetting(string name) { return new VFXSetting(GetType().GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), this); } public void Invalidate(InvalidationCause cause) { if (cause != InvalidationCause.kExpressionGraphChanged && cause != InvalidationCause.kExpressionInvalidated) Modified(cause == InvalidationCause.kUIChanged || cause == InvalidationCause.kUIChangedTransient); string sampleName = GetType().Name + "-" + name + "-" + cause; Profiler.BeginSample("VFXEditor.Invalidate" + sampleName); try { Invalidate(this, cause); } finally { Profiler.EndSample(); } } [SerializeField] List m_UIIgnoredErrors = new List(); public void IgnoreError(string error) { if (m_UIIgnoredErrors == null) m_UIIgnoredErrors = new List(); if (!m_UIIgnoredErrors.Contains(error)) m_UIIgnoredErrors.Add(error); } public bool IsErrorIgnored(string error) { return m_UIIgnoredErrors != null && m_UIIgnoredErrors.Contains(error); } public void ClearIgnoredErrors() { m_UIIgnoredErrors = null; RefreshErrors(GetGraph()); } public bool HasIgnoredErrors() { return m_UIIgnoredErrors != null && m_UIIgnoredErrors.Count > 0; } protected virtual void GenerateErrors(VFXInvalidateErrorReporter manager) { } protected internal virtual void Invalidate(VFXModel model, InvalidationCause cause) { OnInvalidate(model, cause); if (m_Parent != null) m_Parent.Invalidate(model, cause); } public virtual IEnumerable GetSettings(bool listHidden, VFXSettingAttribute.VisibleFlags flags = VFXSettingAttribute.VisibleFlags.All) { return GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(f => { var attrArray = f.GetCustomAttributes(typeof(VFXSettingAttribute), true); if (attrArray.Length == 1) { var attr = attrArray[0] as VFXSettingAttribute; if (listHidden) return true; return (attr.visibleFlags & flags) != 0 && !filteredOutSettings.Contains(f.Name); } return false; }).Select(field => new VFXSetting(field, this)); } static public VFXExpression ConvertSpace(VFXExpression input, VFXSlot targetSlot, VFXCoordinateSpace space) { if (targetSlot.spaceable) { if (targetSlot.space != space) { var spaceType = targetSlot.GetSpaceTransformationType(); input = ConvertSpace(input, spaceType, space); } } return input; } static protected VFXExpression ConvertSpace(VFXExpression input, SpaceableType spaceType, VFXCoordinateSpace space) { VFXExpression matrix = null; if (space == VFXCoordinateSpace.Local) { matrix = VFXBuiltInExpression.WorldToLocal; } else if (space == VFXCoordinateSpace.World) { matrix = VFXBuiltInExpression.LocalToWorld; } else { throw new InvalidOperationException("Cannot Convert to unknown space"); } if (spaceType == SpaceableType.Position) { input = new VFXExpressionTransformPosition(matrix, input); } else if (spaceType == SpaceableType.Direction) { input = new VFXExpressionTransformDirection(matrix, input); } else if (spaceType == SpaceableType.Matrix) { input = new VFXExpressionTransformMatrix(matrix, input); } else if (spaceType == SpaceableType.Vector) { input = new VFXExpressionTransformVector(matrix, input); } else { //Not a transformable subSlot } return input; } protected virtual IEnumerable filteredOutSettings { get { return Enumerable.Empty(); } } public VisualEffectResource GetResource() { var graph = GetGraph(); if (graph != null) return graph.visualEffectResource; return null; } public VFXGraph GetGraph() { var graph = this as VFXGraph; if (graph != null) return graph; var parent = GetParent(); if (parent != null) return parent.GetGraph(); return null; } public static void UnlinkModel(VFXModel model, bool notify = true) { if (model is IVFXSlotContainer) { var slotContainer = (IVFXSlotContainer)model; VFXSlot slotToClean = null; do { slotToClean = slotContainer.inputSlots.Concat(slotContainer.outputSlots).FirstOrDefault(o => o.HasLink(true)); if (slotToClean) slotToClean.UnlinkAll(true, notify); } while (slotToClean != null); } } public static void RemoveModel(VFXModel model, bool notify = true) { VFXGraph graph = model.GetGraph(); if (graph != null) graph.UIInfos.Sanitize(graph); // Remove reference from groupInfos UnlinkModel(model); model.Detach(notify); } public static void ReplaceModel(VFXModel dst, VFXModel src, bool notify = true) { // UI dst.m_UIPosition = src.m_UIPosition; dst.m_UICollapsed = src.m_UICollapsed; dst.m_UISuperCollapsed = src.m_UISuperCollapsed; if (notify) dst.Invalidate(InvalidationCause.kUIChanged); VFXGraph graph = src.GetGraph(); if (graph != null && graph.UIInfos != null && graph.UIInfos.groupInfos != null) { // Update group nodes foreach (var groupInfo in graph.UIInfos.groupInfos) if (groupInfo.contents != null) for (int i = 0; i < groupInfo.contents.Length; ++i) if (groupInfo.contents[i].model == src) groupInfo.contents[i].model = dst; } if (dst is VFXBlock && src is VFXBlock) { ((VFXBlock)dst).enabled = ((VFXBlock)src).enabled; } // Unlink everything UnlinkModel(src); // Replace model var parent = src.GetParent(); int index = parent.GetIndex(src); src.Detach(notify); if (parent) parent.AddChild(dst, index, notify); } [SerializeField] protected VFXModel m_Parent; [SerializeField] protected List m_Children; [SerializeField] protected Vector2 m_UIPosition; [SerializeField] protected bool m_UICollapsed; [SerializeField] protected bool m_UISuperCollapsed; } abstract class VFXModel : VFXModel where ParentType : VFXModel where ChildrenType : VFXModel { public override bool AcceptChild(VFXModel model, int index = -1) { return index >= -1 && index <= m_Children.Count && model is ChildrenType; } public new ParentType GetParent() { return (ParentType)m_Parent; } public new int GetNbChildren() { return m_Children.Count; } public new ChildrenType this[int index] { get { return m_Children[index] as ChildrenType; } } public new IEnumerable children { get { return m_Children.Cast(); } } } }