You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

863 lines
26 KiB

using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.VFX;
using UnityEditor.Experimental.GraphView;
using UnityEditor.Graphing.Util;
using UnityEngine.Profiling;
using UnityObject = UnityEngine.Object;
namespace UnityEditor.VFX.UI
{
interface IVFXAnchorController
{
void Connect(VFXEdgeController edgeController);
void Disconnect(VFXEdgeController edgeController);
Direction direction { get; }
}
abstract class VFXDataAnchorController : VFXController<VFXSlot>, IVFXAnchorController, IPropertyRMProvider, IGizmoable, IGizmoError
{
private VFXNodeController m_SourceNode;
private int m_expressionHashCode;
public VFXNodeController sourceNode
{
get
{
return m_SourceNode;
}
}
public VFXSpace space
{
get
{
return model.space;
}
set
{
model.space = value;
}
}
public bool spaceableAndMasterOfSpace
{
get
{
return model.spaceable && model.IsMasterSlot();
}
}
public bool IsSpaceInherited()
{
return model.IsSpaceInherited();
}
public override string name
{
get
{
return base.name;
}
}
VFXSlot m_MasterSlot;
public Type portType { get; set; }
IEnumerable<int> IPropertyRMProvider.filteredOutEnumerators { get { return null; } }
public Type storageType
{
get
{
if (typeof(Texture).IsAssignableFrom(portType))
{
return typeof(Texture);
}
return portType;
}
}
public VFXDataAnchorController(VFXSlot model, VFXNodeController sourceNode, bool hidden) : base(sourceNode.viewController, model)
{
m_SourceNode = sourceNode;
m_Hidden = hidden;
m_Expanded = expandedSelf;
if (model != null)
{
isSubgraphActivation = sourceNode.model is VFXSubgraphBlock && model.name == VFXBlock.activationSlotName;
portType = model.property.type;
if (model.GetMasterSlot() != null && model.GetMasterSlot() != model)
{
m_MasterSlot = model.GetMasterSlot();
viewController.RegisterNotification(m_MasterSlot, MasterSlotChanged);
}
ModelChanged(model);
}
}
void MasterSlotChanged()
{
if (m_MasterSlot == null)
return;
ModelChanged(m_MasterSlot);
}
bool m_Expanded;
protected override void ModelChanged(UnityEngine.Object obj)
{
Profiler.BeginSample("VFXDataAnchorController.ModelChanged");
if (expandedSelf != m_Expanded)
{
m_Expanded = expandedSelf;
UpdateHiddenRecursive(m_Hidden, true);
}
Profiler.BeginSample("VFXDataAnchorController.ModelChanged:UpdateInfos");
UpdateInfos();
Profiler.EndSample();
// This method is called every time a value change in the expression which is way to often
// Currently we only want to refresh the gizmo when the expression change (especially when space or "can evaluate" change)
// That's why we cache the expression hash code
if (m_GizmoContext != null)
{
HashSet<VFXExpression> expressions = new HashSet<VFXExpression>();
model.GetExpressions(expressions);
var currentExpressionHashCode = UIUtilities.GetHashCode(expressions);
if (currentExpressionHashCode != m_expressionHashCode)
{
RefreshGizmo();
m_expressionHashCode = currentExpressionHashCode;
}
}
sourceNode.DataEdgesMightHaveChanged();
Profiler.BeginSample("VFXDataAnchorController.NotifyChange");
NotifyChange(AnyThing);
Profiler.EndSample();
Profiler.EndSample();
}
public override void OnDisable()
{
if (!object.ReferenceEquals(m_MasterSlot, null))
{
viewController.UnRegisterNotification(m_MasterSlot, MasterSlotChanged);
m_MasterSlot = null;
}
base.OnDisable();
}
// Used to hide activation slot and forbid linking
public bool isSubgraphActivation { get; }
public virtual bool HasLink()
{
return model.HasLink();
}
public class CanLinkCache
{
internal HashSet<IVFXSlotContainer> localChildrenOperator = new HashSet<IVFXSlotContainer>();
internal HashSet<IVFXSlotContainer> localParentOperator = new HashSet<IVFXSlotContainer>();
}
public bool CanLinkToNode(VFXNodeController nodeController, CanLinkCache cache)
{
if (isSubgraphActivation)
return false;
if (nodeController == sourceNode)
return false;
if (cache == null)
cache = new CanLinkCache();
cache.localChildrenOperator.Clear();
cache.localParentOperator.Clear();
bool result;
if (direction != Direction.Input)
{
VFXViewController.CollectAncestorOperator(sourceNode.slotContainer, cache.localParentOperator);
result = !cache.localParentOperator.Contains(nodeController.slotContainer);
}
else
{
VFXViewController.CollectDescendantOperator(sourceNode.slotContainer, cache.localChildrenOperator);
result = !cache.localChildrenOperator.Contains(nodeController.slotContainer);
}
return result;
}
public virtual bool CanLink(VFXDataAnchorController controller, CanLinkCache cache = null)
{
if (isSubgraphActivation)
return false;
if (controller.model != null)
{
if (model.CanLink(controller.model) && controller.model.CanLink(model))
{
if (!CanLinkToNode(controller.sourceNode, cache))
return false;
return true;
}
return sourceNode.CouldLink(this, controller, cache);
}
return controller.CanLink(this, cache);
}
public virtual VFXParameter.NodeLinkedSlot CreateLinkTo(VFXDataAnchorController output, bool revertTypeConstraint = false)
{
var slotOutput = output != null ? output.model : null;
var slotInput = model;
sourceNode.WillCreateLink(ref slotInput, ref slotOutput, revertTypeConstraint);
if (slotInput != null && slotOutput != null && slotInput.Link(slotOutput))
{
return new VFXParameter.NodeLinkedSlot() { inputSlot = slotInput, outputSlot = slotOutput };
}
return new VFXParameter.NodeLinkedSlot();
}
public class Change
{
public const int hidden = 1;
}
private void UpdateHiddenRecursive(bool parentCollapsed, bool firstLevel)
{
bool changed = m_Hidden != parentCollapsed;
if (changed || firstLevel)
{
m_Hidden = parentCollapsed;
var ports = (direction == Direction.Input) ? m_SourceNode.inputPorts : m_SourceNode.outputPorts;
var children = model.children;
if (model.spaceable && model.children.Count() == 1)
{
children = children.First().children;
}
foreach (var element in children.Select(t => ports.First(u => u.model == t)))
{
element.UpdateHiddenRecursive(m_Hidden || !expandedSelf, false);
}
if (changed && !firstLevel) //Do not notify on first level as it will be done by the called
NotifyChange((int)Change.hidden);
}
}
VFXPropertyAttributes m_Attributes;
public virtual void UpdateInfos()
{
portType = model.property.type;
m_Attributes = model.property.attributes;
}
public bool indeterminate
{
get
{
return !m_SourceNode.viewController.CanGetEvaluatedContent(model);
}
}
public virtual object value
{
get
{
if (portType != null)
{
if (!editable)
{
VFXViewController nodeController = m_SourceNode.viewController;
try
{
Profiler.BeginSample("GetEvaluatedContent");
var evaluatedValue = nodeController.GetEvaluatedContent(model);
Profiler.EndSample();
if (evaluatedValue != null)
{
if (typeof(UnityObject).IsAssignableFrom(storageType))
{
int instanceID = (int)evaluatedValue;
return VFXConverter.ConvertTo(EditorUtility.InstanceIDToObject(instanceID), storageType);
}
else
return VFXConverter.ConvertTo(evaluatedValue, storageType);
}
}
catch (System.Exception e)
{
Debug.LogError("Trying to get the value from expressions threw." + e.Message + " In anchor : " + name + " from node :" + sourceNode.title);
}
}
return VFXConverter.ConvertTo(model.value, storageType);
}
else
{
return null;
}
}
set { SetPropertyValue(VFXConverter.ConvertTo(value, storageType)); }
}
List<VFXDataEdgeController> m_Connections = new List<VFXDataEdgeController>();
public virtual void Connect(VFXEdgeController edgeController)
{
m_Connections.Add(edgeController as VFXDataEdgeController);
RefreshGizmo();
}
public virtual void Disconnect(VFXEdgeController edgeController)
{
m_Connections.Remove(edgeController as VFXDataEdgeController);
RefreshGizmo();
}
public bool connected
{
get { return m_Connections.Count > 0; }
}
public IEnumerable<VFXDataEdgeController> connections { get { return m_Connections; } }
public abstract Direction direction { get; }
public Orientation orientation { get { return Orientation.Horizontal; } }
public string path
{
get { return model.path; }
}
public object[] customAttributes
{
get
{
return new object[] { };
}
}
public VFXPropertyAttributes attributes
{
get { return m_Attributes; }
}
public virtual int depth
{
get
{
int depth = model.depth;
if (depth > 0)
{
if (SlotShouldSkipFirstLevel(model.GetMasterSlot()))
{
--depth;
}
}
return depth;
}
}
public virtual bool expandable
{
get { return VFXContextController.IsTypeExpandable(portType); }
}
bool IPropertyRMProvider.expandableIfShowsEverything { get { return true; } }
public virtual string iconName
{
get { return portType.Name; }
}
private bool m_Hidden;
public bool expandedInHierachy
{
get
{
return !m_Hidden || connected;
}
}
public virtual bool expandedSelf
{
get
{
return !model.collapsed;
}
}
bool IPropertyRMProvider.expanded
{
get { return expandedSelf; }
}
public bool m_Editable = true;
public void UpdateEditable()
{
m_Editable = true;
if (direction == Direction.Output)
return;
VFXSlot slot = model;
if (!slot || slot.HasLink(true))
{
m_Editable = false;
return;
}
while (slot != null)
{
if (slot.HasLink())
{
m_Editable = false;
return;
}
slot = slot.GetParent();
}
}
public virtual bool editable
{
get
{
return m_Editable;
}
}
private void SetPropertyValue(object value)
{
if (model.value != value)
{
Undo.RecordObject(model.GetMasterSlot(), "VFX port value (" + model.GetMasterSlot().value?.ToString() ?? "null" + ")"); // The slot value is stored on the master slot, not necessarily my own slot
model.value = value;
}
}
public static bool SlotShouldSkipFirstLevel(VFXSlot slot)
{
return slot is VFXSlotEncapsulated;
}
public virtual void ExpandPath()
{
if (model == null || !model.collapsed)
return;
Undo.RecordObject(model, "Expand port");
model.collapsed = false;
if (SlotShouldSkipFirstLevel(model))
{
model.children.First().collapsed = model.collapsed;
}
}
public virtual void RetractPath()
{
if (model == null || model.collapsed)
return;
Undo.RecordObject(model, "Collapse port");
model.collapsed = true;
if (SlotShouldSkipFirstLevel(model))
{
model.children.First().collapsed = model.collapsed;
}
}
void RefreshGizmo()
{
if (m_GizmoContext != null) m_GizmoContext.Unprepare();
if (model == null || model.IsMasterSlot()) return;
var parentController = sourceNode.inputPorts.FirstOrDefault(t => t.model == model.GetParent());
if (parentController != null)
{
parentController.RefreshGizmo();
}
else if (model.GetParent()) // Try with grand parent for Vector3 spacable types
{
parentController = sourceNode.inputPorts.FirstOrDefault(t => t.model == model.GetParent().GetParent());
if (parentController != null)
{
parentController.RefreshGizmo();
}
}
}
public Bounds GetGizmoBounds(VisualEffect component)
{
if (m_GizmoContext != null)
{
return VFXGizmoUtility.GetGizmoBounds(m_GizmoContext, component);
}
return new Bounds();
}
public GizmoError GetGizmoError(VisualEffect component)
{
if (!VFXGizmoUtility.HasGizmo(portType))
return GizmoError.None;
CreateGizmoContextIfNeeded();
return VFXGizmoUtility.CollectGizmoError(m_GizmoContext, component);
}
VFXDataAnchorGizmoContext m_GizmoContext;
public void DrawGizmo(VisualEffect component)
{
if (VFXGizmoUtility.HasGizmo(portType))
{
CreateGizmoContextIfNeeded();
VFXGizmoUtility.Draw(m_GizmoContext, component);
}
}
void CreateGizmoContextIfNeeded()
{
if (m_GizmoContext == null)
{
m_GizmoContext = new VFXDataAnchorGizmoContext(this);
}
}
void IPropertyRMProvider.StartLiveModification()
{
sourceNode.viewController.errorRefresh = false;
}
void IPropertyRMProvider.EndLiveModification()
{
sourceNode.viewController.errorRefresh = true;
}
}
class VFXUpcommingDataAnchorController : VFXDataAnchorController
{
public VFXUpcommingDataAnchorController(VFXNodeController sourceNode, bool hidden) : base(null, sourceNode, hidden)
{
}
public override void OnDisable()
{
base.OnDisable();
}
public override Direction direction
{
get
{
return Direction.Input;
}
}
public override bool editable
{
get { return true; }
}
public override bool expandedSelf
{
get
{
return false;
}
}
public override bool expandable
{
get { return false; }
}
public override bool HasLink()
{
return false;
}
public override void UpdateInfos()
{
}
public override object value
{
get
{
return null;
}
set
{
}
}
public override int depth
{
get
{
return 0;
}
}
public override string name
{
get
{
return "";
}
}
public override bool CanLink(VFXDataAnchorController controller, CanLinkCache cache = null)
{
if (isSubgraphActivation)
return false;
var op = (sourceNode as VFXCascadedOperatorController);
if (op == null)
return false;
if (controller is VFXUpcommingDataAnchorController)
return false;
if (!CanLinkToNode(controller.sourceNode, cache))
return false;
return op.model.GetBestAffinityType(controller.model.property.type) != null;
}
public new VFXCascadedOperatorController sourceNode
{
get { return base.sourceNode as VFXCascadedOperatorController; }
}
public override VFXParameter.NodeLinkedSlot CreateLinkTo(VFXDataAnchorController output, bool revertTypeConstraint = false)
{
var slotOutput = output != null ? output.model : null;
VFXOperatorNumericCascadedUnified op = sourceNode.model;
op.AddOperand(op.GetBestAffinityType(output.model.property.type));
var slotInput = op.GetInputSlot(op.GetNbInputSlots() - 1);
if (slotInput != null && slotOutput != null && slotInput.Link(slotOutput))
{
return new VFXParameter.NodeLinkedSlot() { inputSlot = slotInput, outputSlot = slotOutput };
}
return new VFXParameter.NodeLinkedSlot();
}
}
class VFXDataAnchorGizmoContext : VFXGizmoUtility.Context
{
// Provider
internal VFXDataAnchorGizmoContext(VFXDataAnchorController controller)
{
m_Controller = controller;
}
VFXDataAnchorController m_Controller;
public override Type portType
{
get { return m_Controller.portType; }
}
List<object> stack = new List<object>();
public override object value
{
get
{
// If the vfxwindow is hidden then Update will not be called, which in turn will not recompile the expression graph. so try recompiling it now
m_Controller.viewController.RecompileExpressionGraphIfNeeded();
stack.Clear();
foreach (var action in m_ValueBuilder)
{
action(stack);
}
return stack.First();
}
}
public override VFXSpace space
{
get
{
return m_Controller.space;
}
}
List<Action<List<object>>> m_ValueBuilder = new List<Action<List<object>>>();
protected override void InternalPrepare()
{
var type = m_Controller.portType;
if (!type.IsValueType)
{
Debug.LogError("No support for class types in Gizmos");
return;
}
m_ValueBuilder.Clear();
m_ValueBuilder.Add(o => o.Add(m_Controller.value));
if (!m_Controller.viewController.CanGetEvaluatedContent(m_Controller.model))
{
if (m_Controller.model.HasLink(false))
{
if (VFXTypeUtility.GetComponentCount(m_Controller.model) != 0)
{
m_Error |= GizmoError.HasLinkIndeterminate;
return;
}
}
BuildValue(m_Controller.model);
}
}
void BuildValue(VFXSlot slot)
{
foreach (var field in slot.property.type.GetFields())
{
VFXSlot subSlot = slot.children.FirstOrDefault<VFXSlot>(t => t.name == field.Name);
if (subSlot != null)
{
object result = null;
if (subSlot.HasLink(true) && m_Controller.viewController.CanGetEvaluatedContent(subSlot) && (result = m_Controller.viewController.GetEvaluatedContent(subSlot)) != null)
{
m_ValueBuilder.Add(o => o.Add(m_Controller.viewController.GetEvaluatedContent(subSlot)));
}
else if (subSlot.HasLink(false) && VFXTypeUtility.GetComponentCount(subSlot) != 0) // replace by is VFXType
{
m_Error |= GizmoError.HasLinkIndeterminate;
return;
}
else
{
m_ValueBuilder.Add(o => o.Add(subSlot.value));
BuildValue(subSlot);
if (m_Error != GizmoError.None)
return;
}
m_ValueBuilder.Add(o =>
{
var newValue = o[o.Count - 1];
if (newValue != null)
{
var target = o[o.Count - 2];
if (field.FieldType != newValue.GetType())
{
if (!VFXConverter.TryConvertTo(newValue, field.FieldType, out var convertedValue))
throw new InvalidOperationException($"VFXDataAnchorGizmo is failing to convert from {newValue.GetType()} to {field.FieldType}");
newValue = convertedValue;
}
field.SetValue(target, newValue);
}
});
m_ValueBuilder.Add(o => o.RemoveAt(o.Count - 1));
}
}
}
public override VFXGizmo.IProperty<T> RegisterProperty<T>(string member)
{
object result;
if (m_PropertyCache.TryGetValue(member, out result))
{
if (result is VFXGizmo.IProperty<T>)
return result as VFXGizmo.IProperty<T>;
else
return VFXGizmoUtility.NullProperty<T>.defaultProperty;
}
var controller = GetMemberController(member);
if (controller != null && controller.portType == typeof(T))
{
bool readOnly = false;
var slot = controller.model;
if (slot.HasLink(true))
readOnly = true;
else
{
slot = slot.GetParent();
while (slot != null)
{
if (slot.HasLink(false))
{
readOnly = true;
break;
}
slot = slot.GetParent();
}
}
return new VFXGizmoUtility.Property<T>(controller, !readOnly);
}
return VFXGizmoUtility.NullProperty<T>.defaultProperty;
}
VFXDataAnchorController GetMemberController(string memberPath)
{
if (string.IsNullOrEmpty(memberPath))
{
return m_Controller;
}
return GetSubMemberController(memberPath, m_Controller.model);
}
VFXDataAnchorController GetSubMemberController(string memberPath, VFXSlot slot)
{
int index = memberPath.IndexOf(separator);
if (index == -1)
{
VFXSlot subSlot = slot.children.FirstOrDefault(t => t.name == memberPath);
if (subSlot != null)
{
var subController = m_Controller.sourceNode.inputPorts.FirstOrDefault(t => t.model == subSlot);
return subController;
}
return null;
}
else
{
string memberName = memberPath.Substring(0, index);
VFXSlot subSlot = slot.children.FirstOrDefault(t => t.name == memberName);
if (subSlot != null)
{
return GetSubMemberController(memberPath.Substring(index + 1), subSlot);
}
return null;
}
}
}
}