1187 lines
41 KiB
C#
1187 lines
41 KiB
C#
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<VFXSlot, VFXSlot>
|
|
{
|
|
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<VFXExpression> 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<VFXSlot> 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<VFXSlot> 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<VFXSlot> 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<VFXSlot>();
|
|
|
|
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<VFXSlot>();
|
|
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<SlotWithSpaceTransformation>();
|
|
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<VFXSlot> func)
|
|
{
|
|
var parent = GetParent();
|
|
if (parent != null)
|
|
{
|
|
func(parent);
|
|
parent.PropagateToParent(func);
|
|
}
|
|
}
|
|
|
|
protected void PropagateToChildren(Action<VFXSlot> func, bool includeThis = true)
|
|
{
|
|
if (includeThis)
|
|
func(this);
|
|
|
|
foreach (var child in children)
|
|
child.PropagateToChildren(func, true);
|
|
}
|
|
|
|
protected void PropagateToChildrenReverse(Action<VFXSlot> func, bool includeThis = true)
|
|
{
|
|
foreach (var child in children)
|
|
child.PropagateToChildrenReverse(func, true);
|
|
|
|
if (includeThis)
|
|
func(this);
|
|
}
|
|
|
|
protected void PropagateToTree(Action<VFXSlot> 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<VFXSlot> 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<VFXSlot> startSlots = new List<VFXSlot>();
|
|
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<VFXSlot>();
|
|
|
|
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<VFXSlot> 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<VFXSlot>(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<VFXSlot> m_LinkedSlots;
|
|
}
|
|
}
|