using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using UnityEngine; using UnityEngine.VFX; using System.Reflection; namespace UnityEditor.VFX { [Serializable] class VFXSlot : VFXModel { public enum Direction { kInput, kOutput, } public Direction direction { get { return m_Direction; } } public VFXProperty property { get { return m_Property; } } public override string name { get { return m_Property.name; } } private FieldInfo m_FieldInfoCache; public object value { get { try { object slotValue = null; if (IsMasterSlot()) { slotValue = GetMasterData().m_Value.Get(); } else { object parentValue = GetParent().value; if (m_FieldInfoCache == null) { Type type = GetParent().property.type; m_FieldInfoCache = type.GetField(name); } slotValue = m_FieldInfoCache.GetValue(parentValue); } if (slotValue == null && !typeof(UnityEngine.Object).IsAssignableFrom(property.type)) { Debug.Log("null value in slot of type" + property.type.UserFriendlyName()); } return slotValue; } catch (Exception e) { Debug.LogErrorFormat("Exception while getting value for slot {0} of type {1}: {2}\n{3}", name, GetType(), e, e.StackTrace); // TODO Initialize to default value (try to call static default static method defaultValue from type) return null; } } set { try { if (IsMasterSlot()) SetValueInternal(value, true); else { object parentValue = GetParent().value; if (m_FieldInfoCache == null) { Type type = GetParent().property.type; m_FieldInfoCache = type.GetField(name); } m_FieldInfoCache.SetValue(parentValue, value); GetParent().value = parentValue; } } catch (Exception e) { Debug.LogErrorFormat("Exception while setting value for slot {0} of type {1}: {2}\n{3}", name, GetType(), e, e.StackTrace); } } } public bool spaceable { get { var masterSlot = GetMasterSlot(); return VFXLibrary.IsSpaceableSlotType(masterSlot.property.type); } } public VFXCoordinateSpace space { get { if (spaceable) return GetMasterData().m_Space; return (VFXCoordinateSpace)int.MaxValue; } set { if (spaceable) { if (space != value) { GetMasterData().m_Space = value; Invalidate(InvalidationCause.kSpaceChanged); } } else { Debug.LogError("Trying to modify space on a not spaceable slot : " + property.type); } } } public bool IsSpaceInherited() { if (!spaceable) { Debug.LogError("Unexpected call of IsSpaceInherited : " + ToString()); return true; } if (!IsMasterSlot()) return GetMasterSlot().IsSpaceInherited(); if (!HasLink() || direction == Direction.kOutput) return false; var linkedSlot = m_LinkedSlots.First(); return linkedSlot.spaceable && linkedSlot.space != (VFXCoordinateSpace)int.MaxValue; } public SpaceableType GetSpaceTransformationType() { if (!spaceable) Debug.LogError("Unexpected call of GetSpaceTransformationType : " + ToString()); var masterSlot = GetMasterSlot(); if (masterSlot.m_SlotsSpaceable == null) { //Should only happened after a domain reload : OnEnable is called but not Sanitize (only if VFXType doesn't change) masterSlot.InitializeSpaceableCachedSlot(); } var findSlot = masterSlot.m_SlotsSpaceable.Where(o => o.slot == this).FirstOrDefault(); return findSlot.slot == null ? SpaceableType.None : findSlot.type; //Explicitly return none (not really needed because is default of spaceableType) } private void SetValueInternal(object value, bool notify) { if (!IsMasterSlot()) // Must be a master node throw new InvalidOperationException(); GetMasterData().m_Value.Set(value); UpdateDefaultExpressionValue(); if (notify && owner != null) Invalidate(InvalidationCause.kParamChanged); } public string path { get { if (GetParent() != null) return string.Format("{0}.{1}", GetParent().path, name); else return name; } } public int depth { get { if (GetParent() == null) { return 0; } else { return GetParent().depth + 1; } } } public string fullName { get { string name = property.name; if (GetParent() != null) name = GetParent().fullName + "_" + name; return name; } } public VFXExpression GetExpression() { if (!m_ExpressionTreeUpToDate) RecomputeExpressionTree(); return m_OutExpression; } public VFXExpression GetInExpression() { if (!m_ExpressionTreeUpToDate) RecomputeExpressionTree(); return m_InExpression; } public void SetExpression(VFXExpression expr) { if (!expr.Equals(m_LinkedInExpression)) { PropagateToTree(s => { s.m_LinkedInExpression = null; s.m_LinkedInSlot = null; }); m_LinkedInExpression = expr; InvalidateExpressionTree(); } } // Get relevant expressions in the slot hierarchy public void GetExpressions(HashSet expressions) { var exp = GetExpression(); if (exp != null) expressions.Add(exp); else foreach (var child in children) child.GetExpressions(expressions); } // Get relevant slot for UI & exposed expressions public IEnumerable GetVFXValueTypeSlots() { if (valueType != VFXValueType.None) yield return this; else foreach (var child in children) { var slots = child.GetVFXValueTypeSlots(); foreach (var slot in slots) yield return slot; } } // Get relevant slots public IEnumerable GetExpressionSlots() { var exp = GetExpression(); if (exp != null) yield return this; else foreach (var child in children) { var exps = child.GetExpressionSlots(); foreach (var e in exps) yield return e; } } public VFXExpression DefaultExpr { get { if (!m_DefaultExpressionInitialized) { InitDefaultExpression(); } return m_DefaultExpression; } } public IEnumerable LinkedSlots { get { return m_LinkedSlots.AsReadOnly(); } } public VFXSlot refSlot { get { if (direction == Direction.kOutput || !HasLink()) return this; return m_LinkedSlots[0]; } } public IVFXSlotContainer owner { get { return GetMasterData().m_Owner as IVFXSlotContainer; } } public bool IsMasterSlot() { return m_MasterSlot == this; } public VFXSlot GetMasterSlot() { return m_MasterSlot; } private MasterData GetMasterData() { return GetMasterSlot().m_MasterData; } // Never call this directly ! Called only by VFXSlotContainerModel public void SetOwner(VFXModel owner) { if (IsMasterSlot()) m_MasterData.m_Owner = owner; else throw new InvalidOperationException(); } public static VFXSlot Create(VFXPropertyWithValue property, Direction direction) { return Create(property.property, direction, property.value); } private void InitializeSpaceableCachedSlot() { if (!IsMasterSlot()) throw new InvalidOperationException(); m_SlotsSpaceable = ComputeCacheSpaceable(this); if (m_SlotsSpaceable.Any()) { if (!spaceable) Debug.LogError("Unexpected slot spaceable while slot isn't mark as spaceable : " + property.type); if (m_MasterData.m_Space == (VFXCoordinateSpace)int.MaxValue) { m_MasterData.m_Space = VFXCoordinateSpace.Local; } } else { if (spaceable) Debug.LogError("Expected slot spaceable while slot is mark as spaceable : " + property.type); m_MasterData.m_Space = (VFXCoordinateSpace)int.MaxValue; } } // Create and return a slot hierarchy from a property info public static VFXSlot Create(VFXProperty property, Direction direction, object value = null) { var slot = CreateSub(property, direction); // First create slot tree var masterData = new MasterData() { m_Owner = null, m_Value = new VFXSerializableObject(property.type, value), }; slot.PropagateToChildren(s => s.SetMasterSlotAndData(slot, null)); slot.m_MasterData = masterData; slot.UpdateDefaultExpressionValue(); slot.InitializeSpaceableCachedSlot(); return slot; } private static VFXSlot CreateSub(VFXProperty property, Direction direction) { var desc = VFXLibrary.GetSlot(property.type); if (desc != null) { var slot = desc.CreateInstance(); slot.m_Direction = direction; slot.m_Property = property; foreach (var subInfo in property.SubProperties()) { var subSlot = CreateSub(subInfo, direction); if (subSlot != null) { subSlot.Attach(slot, false); } } return slot; } throw new InvalidOperationException(string.Format("Unable to create slot for property {0} of type {1}", property.name, property.type)); } public VFXValueType valueType { get { return VFXExpression.GetVFXValueTypeFromType(property.type); } } public static void CopyLinksAndValue(VFXSlot dst, VFXSlot src, bool notify) { CopyValue(dst, src, notify); CopyLinks(dst, src, notify); CopySpace(dst, src, notify); } public static void CopySpace(VFXSlot dst, VFXSlot src, bool notify) { if (dst.IsMasterSlot() && dst.spaceable && src.spaceable) { if (dst.space != src.space) { dst.GetMasterData().m_Space = src.space; if (notify) dst.Invalidate(InvalidationCause.kSpaceChanged); } } } public static void CopyValue(VFXSlot dst, VFXSlot src, bool notify) { // Transfer value only if dst can hold it (master slot) if (dst.IsMasterSlot()) { if (src.property.type == dst.property.type) { dst.SetValueInternal(src.value, notify); } else { object newValue; if (VFXConverter.TryConvertTo(src.value, dst.property.type, out newValue)) { dst.SetValueInternal(newValue, notify); } } } } public static void CopyLinks(VFXSlot dst, VFXSlot src, bool notify) { var links = src.LinkedSlots.ToArray(); int index = 0; while (index < links.Count()) { var link = links[index]; if (dst.CanLink(link)) { dst.Link(link, notify); // TODO Remove the callbacks after VFXParameter refactor if (dst.owner != null) dst.owner.OnCopyLinksMySlot(src, dst, link); if (link.owner != null) link.owner.OnCopyLinksOtherSlot(link, src, dst); } ++index; } var copySubLinks = dst.CanConvertFrom(src.property.type); //Automatically skip encapsulation //TODO : Should rework this to avoid these several "is VFXSlotEncapsulated" special cases (see also "SlotShouldSkipFirstLevel" at controller level) if (src is VFXSlotEncapsulated) { src = src.children.First(); } if (dst is VFXSlotEncapsulated) { dst = dst.children.First(); } if (copySubLinks && src.GetNbChildren() == dst.GetNbChildren()) { int nbSubSlots = src.GetNbChildren(); for (int i = 0; i < nbSubSlots; ++i) CopyLinks(dst[i], src[i], notify); } } public override void OnUnknownChange() { base.OnUnknownChange(); m_ExpressionTreeUpToDate = false; m_DefaultExpressionInitialized = false; m_SlotsSpaceable = null; if (IsMasterSlot()) InitializeSpaceableCachedSlot(); } public override void OnEnable() { base.OnEnable(); if (m_LinkedSlots == null) m_LinkedSlots = new List(); int nbRemoved = m_LinkedSlots.RemoveAll(c => c == null);// Remove bad references if any if (nbRemoved > 0) Debug.LogWarningFormat("Remove {0} linked slot(s) that couldnt be deserialized from {1} of type {2}", nbRemoved, name, GetType()); m_ExpressionTreeUpToDate = false; m_DefaultExpressionInitialized = false; m_SlotsSpaceable = null; if (!IsMasterSlot()) m_MasterData = null; // Non master slot will always have a null master data } public override void Sanitize(int version) { if (!IsMasterSlot()) return; //Give only the responsibility to the master slot to sanitize children (sanitizing deeper children first) //PropagateToChildrenReverse(c => c.InternalSanitize(version), false); //Not possible because collection could be modified while sanitizing var slotToSanitize = new List(); PropagateToChildrenReverse(c => slotToSanitize.Add(c), false); foreach (var slot in slotToSanitize) slot.InternalSanitize(version); var masterSlot = this; masterSlot = masterSlot.InternalSanitize(version); masterSlot.InitializeSpaceableCachedSlot(); } private VFXSlot InternalSanitize(int version) { // Remove invalid links (without owners) if (owner == null) UnlinkAll(); foreach (var link in LinkedSlots.ToArray()) if (link.owner == null || ((VFXModel)(link.owner)).GetGraph() != ((VFXModel)owner).GetGraph()) Unlink(link); // Here we check if hierarchy of type match with slot hierarchy var subProperties = property.SubProperties().ToList(); bool hierarchySane = subProperties.Count == GetNbChildren(); if (hierarchySane) for (int i = 0; i < GetNbChildren(); ++i) if (subProperties[i].type != this[i].property.type) { hierarchySane = false; break; } else { // Just ensure potential renaming of property is taken into account this[i].m_Property = subProperties[i]; } if (!hierarchySane) { Debug.LogWarningFormat("Slot {0} holding {1} didnt match the type layout. It is recreated and all links are lost.", property.name, property.type); // Try to retrieve the value object previousValue = null; try { previousValue = this.value; } catch (Exception e) { Debug.LogWarningFormat("Exception while trying to retrieve value: {0}: {1}", e, e.StackTrace); } // Recreate the slot var newSlot = Create(property, direction, previousValue); if (IsMasterSlot()) { var owner = this.owner; if (owner != null) { int index = owner.GetSlotIndex(this); owner.RemoveSlot(this); owner.AddSlot(newSlot, index); } } else { var parent = GetParent(); var index = parent.GetIndex(this); parent.RemoveChild(this, false); parent.AddChild(newSlot, index); } CopyLinks(newSlot, this, true); CopySpace(newSlot, this, true); UnlinkAll(true); return newSlot; } return this; } private void SetDefaultExpressionValue() { var val = value; if (m_DefaultExpression is VFXValue) ((VFXValue)m_DefaultExpression).SetContent(val); } private void InitDefaultExpression() { if (GetNbChildren() == 0) { m_DefaultExpression = DefaultExpression(VFXValue.Mode.FoldableVariable); } else { // Depth first foreach (var child in children) child.InitDefaultExpression(); m_DefaultExpression = ExpressionFromChildren(children.Select(c => c.m_DefaultExpression).ToArray()); } m_DefaultExpressionInitialized = true; } private static SlotWithSpaceTransformation[] ComputeCacheSpaceable(VFXSlot masterSlot) { if (!masterSlot.IsMasterSlot()) throw new InvalidOperationException("Trying to call init spaceable on a child slot"); var spaceableCollection = new List(); masterSlot.PropagateToChildren(s => { if (!s.property.IsExpandable()) return; var spaceAttributeOnType = s.property.type.GetCustomAttributes(typeof(VFXSpaceAttribute), true).FirstOrDefault(); if (spaceAttributeOnType != null) { spaceableCollection.Add(new SlotWithSpaceTransformation { slot = s, type = (spaceAttributeOnType as VFXSpaceAttribute).type }); } var fields = s.property.type.GetFields(BindingFlags.Public | BindingFlags.Instance).ToArray(); if (fields.Length != s.children.Count()) throw new InvalidOperationException(string.Format("Unexpected slot count for : " + s.property.type + " ({0} vs. {1})", fields.Length == 0 ? "(empty)" : fields.Select(o => o.Name).Aggregate((a, b) => a + ", " + b), s.children.Count() == 0 ? "(empty)" : s.children.Select(o => o.name).Aggregate((a, b) => a + ", " + b))); for (int fieldIndex = 0; fieldIndex < fields.Length; ++fieldIndex) { var spaceAttribute = fields[fieldIndex].GetCustomAttributes(typeof(VFXSpaceAttribute), true).FirstOrDefault(); if (spaceAttribute != null) { var slot = s.children.ElementAt(fieldIndex); if (slot.GetParent() is VFXSlotEncapsulated) { slot = slot.GetParent(); } spaceableCollection.Add(new SlotWithSpaceTransformation { slot = slot, type = (spaceAttribute as VFXSpaceAttribute).type }); } } }); if (spaceableCollection.GroupBy(s => s.slot).Any(g => g.Count() > 1)) { Debug.LogErrorFormat("Unexpected space collection computed for {0}", masterSlot.property.type); } return spaceableCollection.ToArray(); } public void UpdateDefaultExpressionValue() { if (!m_DefaultExpressionInitialized) InitDefaultExpression(); GetMasterSlot().PropagateToChildren(s => s.SetDefaultExpressionValue()); } void InvalidateChildren(VFXModel model, InvalidationCause cause) { foreach (var child in children) { child.OnInvalidate(model, cause); child.InvalidateChildren(model, cause); } } protected internal override void Invalidate(VFXModel model, InvalidationCause cause) { base.Invalidate(model, cause); var owner = this.owner; if (owner != null) owner.Invalidate(this, cause); } public void UpdateAttributes(VFXPropertyAttributes attributes, bool notify) { if (notify) { if (!m_Property.attributes.IsEqual(attributes)) { m_Property.attributes = attributes; Invalidate(InvalidationCause.kUIChangedTransient); // TODO This will trigger a setDirty while it shouldn't as property attributes are not serialized } } else // fast path without comparison m_Property.attributes = attributes; } protected override void OnAdded() { base.OnAdded(); var parent = GetParent(); PropagateToChildren(s => s.SetMasterSlotAndData(parent.m_MasterSlot, null)); } protected override void OnRemoved() { base.OnRemoved(); var masterData = new MasterData() { m_Owner = null, m_Value = new VFXSerializableObject(property.type, value), m_Space = (VFXCoordinateSpace)int.MaxValue, }; PropagateToChildren(s => s.SetMasterSlotAndData(this, null)); m_MasterData = masterData; } public void CleanupLinkedSlots() { m_LinkedSlots = m_LinkedSlots.Where(t => t != null).ToList(); } public int GetNbLinks() { return m_LinkedSlots.Count; } public bool HasLink(bool rescursive = false) { if (GetNbLinks() != 0) { return true; } if (rescursive) { foreach (var child in children) { if (child.HasLink(rescursive)) { return true; } } } return false; } public bool CanLink(VFXSlot other) { return direction != other.direction && ((direction == Direction.kInput && CanConvertFrom(other.property.type)) || (other.CanConvertFrom(property.type))); } public bool Link(VFXSlot other, bool notify = true) { if (other == null) return false; if (!CanLink(other) || !other.CanLink(this)) // can link return false; if (direction == Direction.kOutput) InnerLink(this, other, notify); else InnerLink(other, this, notify); if (notify) { Invalidate(InvalidationCause.kConnectionChanged); other.Invalidate(InvalidationCause.kConnectionChanged); } return true; } public void Unlink(VFXSlot other, bool notify = true) { if (m_LinkedSlots.Contains(other)) { if (direction == Direction.kOutput) InnerUnlink(this, other); else InnerUnlink(other, this); if (notify) { Invalidate(InvalidationCause.kConnectionChanged); other.Invalidate(InvalidationCause.kConnectionChanged); } } } protected void PropagateToParent(Action func) { var parent = GetParent(); if (parent != null) { func(parent); parent.PropagateToParent(func); } } protected void PropagateToChildren(Action func, bool includeThis = true) { if (includeThis) func(this); foreach (var child in children) child.PropagateToChildren(func, true); } protected void PropagateToChildrenReverse(Action func, bool includeThis = true) { foreach (var child in children) child.PropagateToChildrenReverse(func, true); if (includeThis) func(this); } protected void PropagateToTree(Action func) { PropagateToParent(func); PropagateToChildren(func); } private static void UpdateLinkedInExpression(VFXSlot destSlot, VFXSlot refSlot) { var expression = refSlot.GetExpression(); if (expression != null) { destSlot.m_LinkedInExpression = expression; destSlot.m_LinkedInSlot = refSlot; } else if (destSlot.GetType() == refSlot.GetType()) { for (int i = 0; i < destSlot.GetNbChildren(); ++i) { UpdateLinkedInExpression(destSlot.children.ElementAt(i), refSlot.children.ElementAt(i)); } } } public IEnumerable AllChildrenWithLink(bool includeSelf = true) { if (includeSelf && HasLink()) yield return this; foreach (var child in children) { var results = child.AllChildrenWithLink(true); foreach (var result in results) { yield return result; } } } private void RecomputeExpressionTree() { // Start from the top most parent var masterSlot = GetMasterSlot(); // When deserializing, default expression wont be initialized if (!m_DefaultExpressionInitialized) masterSlot.UpdateDefaultExpressionValue(); // Mark all slots in tree as not up to date masterSlot.PropagateToChildren(s => { s.m_ExpressionTreeUpToDate = false; }); if (direction == Direction.kInput) // For input slots, linked expression are directly taken from linked slots { masterSlot.PropagateToChildren(s => { s.m_LinkedInExpression = null; s.m_LinkedInSlot = null; }); var linkedChildren = masterSlot.AllChildrenWithLink(); foreach (var slot in linkedChildren) { UpdateLinkedInExpression(slot, slot.refSlot);// this will trigger recomputation of linked expressions if needed } } else { if (owner != null) { owner.UpdateOutputExpressions(); // Update outputs can trigger an invalidate, it can be reentrant. Just check if we're up to date after that and early out if (m_ExpressionTreeUpToDate) return; } else masterSlot.PropagateToChildren(s => { s.m_LinkedInExpression = null; s.m_LinkedInSlot = null; }); } List startSlots = new List(); masterSlot.PropagateToChildren(s => { if (s.m_LinkedInExpression != null) startSlots.Add(s); // Initialize in expression to linked (will be overwritten later on for some slots) s.m_InExpression = s.m_DefaultExpression; }); // First pass set in expression and propagate to children foreach (var startSlot in startSlots) { var inExpressionPatched = ApplySpaceConversion(startSlot.m_LinkedInExpression, startSlot, startSlot.m_LinkedInSlot); startSlot.m_InExpression = startSlot.ConvertExpression(inExpressionPatched, startSlot.m_LinkedInSlot); // TODO Handle structural modification startSlot.PropagateToChildren(s => { var exp = s.ExpressionToChildren(s.m_InExpression); for (int i = 0; i < s.GetNbChildren(); ++i) s[i].m_InExpression = exp != null ? exp[i] : s.refSlot[i].GetExpression(); // Not sure about that }); } // Then propagate to parent foreach (var startSlot in startSlots) startSlot.PropagateToParent(s => s.m_InExpression = s.ExpressionFromChildren(s.children.Select(c => c.m_InExpression).ToArray())); var toInvalidate = new HashSet(); masterSlot.SetOutExpression(masterSlot.m_InExpression, toInvalidate, masterSlot.owner != null ? masterSlot.owner.GetOutputSpaceFromSlot(this) : (VFXCoordinateSpace)int.MaxValue); masterSlot.PropagateToChildren(s => { var exp = s.ExpressionToChildren(s.m_OutExpression); for (int i = 0; i < s.GetNbChildren(); ++i) s[i].SetOutExpression(exp != null ? exp[i] : s[i].m_InExpression, toInvalidate, masterSlot.owner != null ? masterSlot.owner.GetOutputSpaceFromSlot(s) : (VFXCoordinateSpace)int.MaxValue); }); foreach (var slot in toInvalidate) slot.InvalidateExpressionTree(); } private static VFXExpression ApplySpaceConversion(VFXExpression exp, VFXSlot destSlot, VFXSlot sourceSlot) { if (sourceSlot != null && destSlot.spaceable && sourceSlot.spaceable && destSlot.space != sourceSlot.space) { var destSpaceableType = destSlot.GetSpaceTransformationType(); var sourceSpaceableType = sourceSlot.GetSpaceTransformationType(); //Check if this slot is concerned by spaceable transform (e.g. radius of a sphere) //+ is same kind of space (but should not happen since SlotDirection isn't compatible to SlotPosition) if (destSpaceableType != SpaceableType.None && sourceSpaceableType != SpaceableType.None) { if (destSpaceableType != sourceSpaceableType) { Debug.LogError("Unexpected spaceable type connection slot together, should be forbidden"); } else { exp = ConvertSpace(exp, destSpaceableType, destSlot.space); } } } return exp; } public void SetOutExpression(VFXExpression exp, HashSet toInvalidate, VFXCoordinateSpace convertToSpace = (VFXCoordinateSpace)int.MaxValue) { exp = m_Property.attributes.ApplyToExpressionGraph(exp); if (convertToSpace != (VFXCoordinateSpace)int.MaxValue) { exp = ConvertSpace(exp, this, convertToSpace); } if (m_OutExpression != exp) { m_OutExpression = exp; if (direction == Direction.kInput) { if (owner != null) toInvalidate.UnionWith(owner.outputSlots); } else toInvalidate.UnionWith(LinkedSlots); } m_ExpressionTreeUpToDate = true; } private string GetOwnerType() { if (owner != null) return owner.GetType().Name; else return "No Owner"; } public void InvalidateExpressionTree() { var masterSlot = GetMasterSlot(); masterSlot.PropagateToChildren(s => { if (s.m_ExpressionTreeUpToDate) { s.m_ExpressionTreeUpToDate = false; if (s.direction == Direction.kOutput) foreach (var linkedSlot in s.LinkedSlots.ToArray()) // To array as this can be reentrant... linkedSlot.InvalidateExpressionTree(); } }); if (masterSlot.direction == Direction.kInput) { if (owner != null) { foreach (var slot in owner.outputSlots.ToArray()) slot.InvalidateExpressionTree(); } } if (owner != null && direction == Direction.kInput) owner.Invalidate(InvalidationCause.kExpressionInvalidated); } public void UnlinkAll(bool recursive = false, bool notify = true) { if (recursive) { PropagateToChildren(o => o.UnlinkAll(false, notify)); } else { var currentSlots = new List(m_LinkedSlots); foreach (var slot in currentSlots) Unlink(slot, notify); } } protected override sealed void OnInvalidate(VFXModel model, InvalidationCause cause) { if (cause == InvalidationCause.kConnectionChanged || cause == InvalidationCause.kSpaceChanged) { if (IsMasterSlot() && direction == Direction.kInput && spaceable && HasLink()) { var linkedSlot = m_LinkedSlots.First(); if (linkedSlot.spaceable && linkedSlot.space != (VFXCoordinateSpace)int.MaxValue) { space = linkedSlot.space; } } } //Propagate space change to children if (cause == InvalidationCause.kSpaceChanged) { if (IsMasterSlot()) { InvalidateExpressionTree(); } if (direction == Direction.kOutput) { PropagateToChildren(s => { foreach (var slot in s.m_LinkedSlots) slot.Invalidate(cause); }); } } base.OnInvalidate(model, cause); } private static void InnerLink(VFXSlot output, VFXSlot input, bool notify) { input.UnlinkAll(false, notify); // First disconnect any other linked slot input.PropagateToTree(s => s.UnlinkAll(false, notify)); // Unlink other links in tree input.m_LinkedSlots.Add(output); output.m_LinkedSlots.Add(input); input.InvalidateExpressionTree(); } private static void InnerUnlink(VFXSlot output, VFXSlot input) { output.m_LinkedSlots.Remove(input); if (input.m_LinkedSlots.Remove(output)) input.InvalidateExpressionTree(); } protected virtual bool CanConvertFrom(Type type) { return type == null || property.type == type; } protected virtual VFXExpression ConvertExpression(VFXExpression expression, VFXSlot sourceSlot) { return expression; } protected virtual VFXExpression[] ExpressionToChildren(VFXExpression exp) { return null; } protected virtual VFXExpression ExpressionFromChildren(VFXExpression[] exp) { return null; } public virtual VFXValue DefaultExpression(VFXValue.Mode mode) { return null; } // Expression cache private VFXExpression m_DefaultExpression; // The default expression private VFXExpression m_LinkedInExpression; // The current linked expression to the slot private VFXSlot m_LinkedInSlot; // The origin of linked slot from linked expression (always null for output slot, and null if m_LinkedInExpression is null) private VFXExpression m_InExpression; // correctly converted expression private VFXExpression m_OutExpression; // output expression that can be fetched [NonSerialized] // This must not survive domain reload ! private bool m_ExpressionTreeUpToDate = false; [NonSerialized] private bool m_DefaultExpressionInitialized = false; [NonSerialized] private SlotWithSpaceTransformation[] m_SlotsSpaceable; private struct SlotWithSpaceTransformation { public VFXSlot slot; public SpaceableType type; } [Serializable] private class MasterData { public MasterData() { m_Space = (VFXCoordinateSpace)int.MaxValue; } public VFXModel m_Owner; public VFXSerializableObject m_Value; public VFXCoordinateSpace m_Space; //can be undefined } private void SetMasterSlotAndData(VFXSlot masterSlot, MasterData masterData) { m_MasterSlot = masterSlot; m_MasterData = masterData; m_FieldInfoCache = null; // Invalidate cache as it's based on hierarchy } [SerializeField] private VFXSlot m_MasterSlot; [SerializeField] private MasterData m_MasterData; // always null for none master slots [SerializeField] private VFXProperty m_Property; [SerializeField] private Direction m_Direction; [SerializeField] private List m_LinkedSlots; } }