2072 lines
74 KiB
C#
2072 lines
74 KiB
C#
#define NOTIFICATION_VALIDATION
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Collections.ObjectModel;
|
||
using System.IO;
|
||
using System.Linq;
|
||
|
||
using UnityEditor.Experimental.GraphView;
|
||
using UnityEngine;
|
||
using UnityEngine.Profiling;
|
||
|
||
using UnityObject = UnityEngine.Object;
|
||
|
||
namespace UnityEditor.VFX.UI
|
||
{
|
||
internal partial class VFXViewController : Controller<VisualEffectResource>
|
||
{
|
||
private int m_UseCount;
|
||
public int useCount
|
||
{
|
||
get { return m_UseCount; }
|
||
set
|
||
{
|
||
m_UseCount = value;
|
||
if (m_UseCount == 0)
|
||
{
|
||
RemoveController(this);
|
||
}
|
||
}
|
||
}
|
||
|
||
public enum Priorities
|
||
{
|
||
Graph,
|
||
Node,
|
||
Slot,
|
||
Default,
|
||
GroupNode,
|
||
Count
|
||
}
|
||
|
||
string m_Name;
|
||
|
||
public string name
|
||
{
|
||
get { return m_Name; }
|
||
}
|
||
|
||
string ComputeName()
|
||
{
|
||
if (model == null)
|
||
return "";
|
||
string assetPath = AssetDatabase.GetAssetPath(model);
|
||
if (!string.IsNullOrEmpty(assetPath))
|
||
{
|
||
return Path.GetFileNameWithoutExtension(assetPath);
|
||
}
|
||
else
|
||
{
|
||
return model.name;
|
||
}
|
||
}
|
||
|
||
static Dictionary<ScriptableObject, bool>[] NewPrioritizedHashSet()
|
||
{
|
||
Dictionary<ScriptableObject, bool>[] result = new Dictionary<ScriptableObject, bool>[(int)Priorities.Count];
|
||
|
||
for (int i = 0; i < (int)Priorities.Count; ++i)
|
||
{
|
||
result[i] = new Dictionary<ScriptableObject, bool>();
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
Priorities GetPriority(VFXObject obj)
|
||
{
|
||
if (obj is IVFXSlotContainer)
|
||
{
|
||
return Priorities.Node;
|
||
}
|
||
if (obj is VFXSlot)
|
||
{
|
||
return Priorities.Slot;
|
||
}
|
||
if (obj is VFXUI)
|
||
{
|
||
return Priorities.GroupNode;
|
||
}
|
||
if (obj is VFXGraph)
|
||
{
|
||
return Priorities.Graph;
|
||
}
|
||
return Priorities.Default;
|
||
}
|
||
|
||
Dictionary<ScriptableObject, bool>[] modifiedModels = NewPrioritizedHashSet();
|
||
Dictionary<ScriptableObject, bool>[] otherModifiedModels = NewPrioritizedHashSet();
|
||
|
||
private void OnObjectModified(VFXObject obj, bool uiChange)
|
||
{
|
||
// uiChange == false is stronger : if we have a uiChange and there was a nonUIChange before we keep the non uichange.
|
||
if (!uiChange)
|
||
{
|
||
modifiedModels[(int)GetPriority(obj)][obj] = false;
|
||
}
|
||
else
|
||
{
|
||
if (!modifiedModels[(int)GetPriority(obj)].ContainsKey(obj))
|
||
modifiedModels[(int)GetPriority(obj)][obj] = true;
|
||
}
|
||
}
|
||
|
||
Dictionary<ScriptableObject, List<Action>> m_Notified = new Dictionary<ScriptableObject, List<Action>>();
|
||
|
||
|
||
public void RegisterNotification(VFXObject target, Action action)
|
||
{
|
||
if (target == null)
|
||
return;
|
||
|
||
target.onModified += OnObjectModified;
|
||
|
||
List<Action> notifieds;
|
||
if (m_Notified.TryGetValue(target, out notifieds))
|
||
{
|
||
#if NOTIFICATION_VALIDATION
|
||
if (notifieds.Contains(action))
|
||
Debug.LogError("Adding the same notification twice on:" + target.name);
|
||
#endif
|
||
notifieds.Add(action);
|
||
}
|
||
else
|
||
{
|
||
notifieds = new List<Action>();
|
||
notifieds.Add(action);
|
||
|
||
m_Notified.Add(target, notifieds);
|
||
}
|
||
}
|
||
|
||
public void UnRegisterNotification(VFXObject target, Action action)
|
||
{
|
||
if (object.ReferenceEquals(target, null))
|
||
return;
|
||
|
||
target.onModified -= OnObjectModified;
|
||
List<Action> notifieds;
|
||
if (m_Notified.TryGetValue(target, out notifieds))
|
||
{
|
||
#if NOTIFICATION_VALIDATION
|
||
if (!notifieds.Contains(action))
|
||
Debug.LogError("Removing a non existent notification" + target.name);
|
||
#endif
|
||
notifieds.Remove(action);
|
||
|
||
if (m_CurrentlyNotified == target)
|
||
{
|
||
m_CurrentActions.Remove(action);
|
||
}
|
||
}
|
||
}
|
||
|
||
bool m_InNotify = false;
|
||
|
||
ScriptableObject m_CurrentlyNotified; //this and the next list are used when in case a notification removes a following modification
|
||
List<Action> m_CurrentActions = new List<Action>();
|
||
|
||
|
||
public bool errorRefresh { get; set; } = true;
|
||
|
||
public void NotifyUpdate()
|
||
{
|
||
m_InNotify = true;
|
||
Profiler.BeginSample("VFXViewController.NotifyUpdate");
|
||
if (model == null || m_Graph == null || m_Graph != model.graph)
|
||
{
|
||
// In this case the asset has been destroyed or reimported after having changed outside.
|
||
// Lets rebuild everything and clear the undo stack.
|
||
Clear();
|
||
if (model != null && model.graph != null)
|
||
InitializeUndoStack();
|
||
ModelChanged(model);
|
||
}
|
||
|
||
var tmp = modifiedModels;
|
||
modifiedModels = otherModifiedModels;
|
||
otherModifiedModels = tmp;
|
||
|
||
|
||
int cpt = 0;
|
||
foreach (var objs in otherModifiedModels)
|
||
{
|
||
foreach (var kv in objs)
|
||
{
|
||
var obj = kv.Key;
|
||
List<Action> notifieds;
|
||
Profiler.BeginSample("VFXViewController.Notify:" + obj.GetType().Name);
|
||
if (m_Notified.TryGetValue(obj, out notifieds))
|
||
{
|
||
m_CurrentlyNotified = obj;
|
||
m_CurrentActions.Clear();
|
||
m_CurrentActions.AddRange(notifieds);
|
||
m_CurrentActions.Reverse();
|
||
while (m_CurrentActions.Count > 0)
|
||
{
|
||
var action = m_CurrentActions[m_CurrentActions.Count - 1];
|
||
try
|
||
{
|
||
action();
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogException(e);
|
||
}
|
||
cpt++;
|
||
m_CurrentActions.RemoveAt(m_CurrentActions.Count - 1);
|
||
}
|
||
}
|
||
Profiler.EndSample();
|
||
if (!kv.Value && obj is VFXModel vfxModel && errorRefresh) // we refresh errors only if it wasn't a ui change
|
||
{
|
||
vfxModel.RefreshErrors();
|
||
}
|
||
}
|
||
m_CurrentlyNotified = null;
|
||
|
||
objs.Clear();
|
||
}
|
||
/*
|
||
if (cpt > 0)
|
||
Debug.LogWarningFormat("{0} notification sent this frame", cpt);*/
|
||
Profiler.EndSample();
|
||
|
||
m_InNotify = false;
|
||
|
||
string newName = ComputeName();
|
||
if (newName != m_Name)
|
||
{
|
||
m_Name = newName;
|
||
|
||
if (model != null && model.name != m_Name)
|
||
{
|
||
bool prevDirty = EditorUtility.IsDirty(model);
|
||
model.name = m_Name;
|
||
if (!prevDirty)
|
||
EditorUtility.ClearDirty(model);
|
||
}
|
||
if (graph != null && (graph as UnityObject).name != m_Name)
|
||
{
|
||
bool prevDirty = EditorUtility.IsDirty(graph);
|
||
(graph as UnityObject).name = m_Name;
|
||
if (!prevDirty)
|
||
EditorUtility.ClearDirty(graph);
|
||
}
|
||
|
||
NotifyChange(Change.assetName);
|
||
}
|
||
|
||
if (m_DataEdgesMightHaveChangedAsked)
|
||
{
|
||
m_DataEdgesMightHaveChangedAsked = false;
|
||
DataEdgesMightHaveChanged();
|
||
}
|
||
}
|
||
|
||
public VFXGraph graph { get { return model != null ? model.graph as VFXGraph : null; } }
|
||
|
||
readonly List<VFXFlowAnchorController> m_FlowAnchorController = new List<VFXFlowAnchorController>();
|
||
|
||
// Model / Controller synchronization
|
||
readonly Dictionary<VFXModel, List<VFXNodeController>> m_SyncedModels = new Dictionary<VFXModel, List<VFXNodeController>>();
|
||
|
||
readonly List<VFXDataEdgeController> m_DataEdges = new List<VFXDataEdgeController>();
|
||
readonly List<VFXFlowEdgeController> m_FlowEdges = new List<VFXFlowEdgeController>();
|
||
|
||
public override IEnumerable<Controller> allChildren
|
||
{
|
||
get
|
||
{
|
||
return m_SyncedModels.Values.SelectMany(t => t)
|
||
.Cast<Controller>()
|
||
.Concat(m_DataEdges)
|
||
.Concat(m_FlowEdges)
|
||
.Concat(m_ParameterControllers.Values)
|
||
.Concat(m_GroupNodeControllers)
|
||
.Concat(m_StickyNoteControllers);
|
||
}
|
||
}
|
||
|
||
public void LightApplyChanges()
|
||
{
|
||
ModelChanged(model);
|
||
GraphChanged();
|
||
}
|
||
|
||
public override void ApplyChanges()
|
||
{
|
||
ModelChanged(model);
|
||
GraphChanged();
|
||
foreach (var controller in allChildren)
|
||
{
|
||
controller.ApplyChanges();
|
||
}
|
||
}
|
||
|
||
void GraphLost()
|
||
{
|
||
Clear();
|
||
if (!object.ReferenceEquals(m_Graph, null))
|
||
{
|
||
RemoveInvalidateDelegate(m_Graph, InvalidateExpressionGraph);
|
||
RemoveInvalidateDelegate(m_Graph, IncremenentGraphUndoRedoState);
|
||
RemoveInvalidateDelegate(m_Graph, ReinitIfNeeded);
|
||
|
||
UnRegisterNotification(m_Graph, GraphChanged);
|
||
|
||
m_Graph = null;
|
||
}
|
||
if (!object.ReferenceEquals(m_UI, null))
|
||
{
|
||
UnRegisterNotification(m_UI, UIChanged);
|
||
m_UI = null;
|
||
}
|
||
}
|
||
|
||
public override void OnDisable()
|
||
{
|
||
Profiler.BeginSample("VFXViewController.OnDisable");
|
||
GraphLost();
|
||
ReleaseUndoStack();
|
||
Undo.undoRedoPerformed -= SynchronizeUndoRedoState;
|
||
Undo.willFlushUndoRecord -= WillFlushUndoRecord;
|
||
|
||
base.OnDisable();
|
||
Profiler.EndSample();
|
||
}
|
||
|
||
public IEnumerable<VFXNodeController> AllSlotContainerControllers
|
||
{
|
||
get
|
||
{
|
||
var operatorControllers = m_SyncedModels.Values.SelectMany(t => t).OfType<VFXNodeController>();
|
||
var blockControllers = (contexts.SelectMany(t => t.blockControllers)).Cast<VFXNodeController>();
|
||
|
||
return operatorControllers.Concat(blockControllers);
|
||
}
|
||
}
|
||
|
||
private bool RecreateNodeEdges()
|
||
{
|
||
var unusedEdges = new HashSet<VFXDataEdgeController>(m_DataEdges);
|
||
var nodeToUpdate = new HashSet<VFXNodeController>();
|
||
|
||
foreach (var operatorControllers in m_SyncedModels.Values)
|
||
{
|
||
foreach (var nodeController in operatorControllers)
|
||
{
|
||
foreach (var input in nodeController.inputPorts)
|
||
{
|
||
if (RecreateInputSlotEdge(ref unusedEdges, nodeController, input))
|
||
{
|
||
nodeToUpdate.Add(nodeController);
|
||
}
|
||
}
|
||
if (nodeController is VFXContextController contextController)
|
||
{
|
||
foreach (var block in contextController.blockControllers)
|
||
{
|
||
foreach (var input in block.inputPorts)
|
||
{
|
||
if (RecreateInputSlotEdge(ref unusedEdges, block, input))
|
||
{
|
||
nodeToUpdate.Add(block);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach (var edge in unusedEdges)
|
||
{
|
||
nodeToUpdate.Add(edge.input.sourceNode);
|
||
edge.OnDisable();
|
||
|
||
m_DataEdges.Remove(edge);
|
||
}
|
||
|
||
foreach (var node in nodeToUpdate)
|
||
{
|
||
node.UpdateAllEditable();
|
||
// Mark model as changed since it's connections have changed
|
||
OnObjectModified(node.model, false);
|
||
}
|
||
|
||
return nodeToUpdate.Any();
|
||
}
|
||
|
||
bool m_DataEdgesMightHaveChangedAsked;
|
||
|
||
public void DataEdgesMightHaveChanged()
|
||
{
|
||
if (m_Syncing) return;
|
||
|
||
if (m_InNotify)
|
||
{
|
||
m_DataEdgesMightHaveChangedAsked = true;
|
||
return;
|
||
}
|
||
|
||
Profiler.BeginSample("VFXViewController.DataEdgesMightHaveChanged");
|
||
bool change = RecreateNodeEdges();
|
||
|
||
if (change || m_ForceDataEdgeNotification)
|
||
{
|
||
m_ForceDataEdgeNotification = false;
|
||
NotifyChange(Change.dataEdge);
|
||
}
|
||
Profiler.EndSample();
|
||
}
|
||
|
||
public bool RecreateInputSlotEdge(ref HashSet<VFXDataEdgeController> unusedEdges, VFXNodeController slotContainer, VFXDataAnchorController input)
|
||
{
|
||
VFXSlot inputSlot = input.model;
|
||
if (inputSlot == null)
|
||
return false;
|
||
|
||
bool changed = false;
|
||
if (input.HasLink())
|
||
{
|
||
VFXNodeController operatorControllerFrom = null;
|
||
|
||
IVFXSlotContainer targetSlotContainer = inputSlot.refSlot.owner;
|
||
if (targetSlotContainer == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
switch (targetSlotContainer)
|
||
{
|
||
case VFXParameter vfxParameter:
|
||
if (m_ParameterControllers.TryGetValue(vfxParameter, out var controller))
|
||
{
|
||
operatorControllerFrom = controller.GetParameterForLink(inputSlot);
|
||
}
|
||
break;
|
||
case VFXBlock vfxBlock:
|
||
var context = vfxBlock.GetParent();
|
||
if (m_SyncedModels.TryGetValue(context, out var contextControllers) && contextControllers.Any())
|
||
{
|
||
operatorControllerFrom = ((VFXContextController)contextControllers[0]).blockControllers.FirstOrDefault(t => t.model == vfxBlock);
|
||
}
|
||
break;
|
||
case VFXModel vfxModel:
|
||
if (m_SyncedModels.TryGetValue(vfxModel, out var nodeControllers) && nodeControllers.Count > 0)
|
||
{
|
||
operatorControllerFrom = nodeControllers[0];
|
||
}
|
||
break;
|
||
default:
|
||
throw new Exception($"`targetSlotContainer` has unexpected type {targetSlotContainer.GetType().Name}");
|
||
}
|
||
|
||
if (operatorControllerFrom != null && slotContainer != null)
|
||
{
|
||
var anchorFrom = operatorControllerFrom.outputPorts.FirstOrDefault(o => o.model == inputSlot.refSlot);
|
||
|
||
var edgController = m_DataEdges.FirstOrDefault(t => t.input == input && t.output == anchorFrom);
|
||
|
||
if (edgController != null)
|
||
{
|
||
unusedEdges.Remove(edgController);
|
||
}
|
||
else if (anchorFrom != null)
|
||
{
|
||
edgController = new VFXDataEdgeController(input, anchorFrom);
|
||
m_DataEdges.Add(edgController);
|
||
changed = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach (VFXSlot subSlot in inputSlot.children)
|
||
{
|
||
VFXDataAnchorController subAnchor = slotContainer.inputPorts.FirstOrDefault(t => t.model == subSlot);
|
||
// Can be null for example for hidden values from Vector3Spaceables
|
||
changed |= subAnchor != null && RecreateInputSlotEdge(ref unusedEdges, slotContainer, subAnchor);
|
||
}
|
||
|
||
return changed;
|
||
}
|
||
|
||
public IEnumerable<VFXContextController> contexts
|
||
{
|
||
get { return m_SyncedModels.Values.SelectMany(t => t).OfType<VFXContextController>(); }
|
||
}
|
||
public IEnumerable<VFXNodeController> nodes
|
||
{
|
||
get { return m_SyncedModels.Values.SelectMany(t => t); }
|
||
}
|
||
|
||
public void FlowEdgesMightHaveChanged()
|
||
{
|
||
if (m_Syncing) return;
|
||
|
||
bool change = RecreateFlowEdges();
|
||
if (change)
|
||
{
|
||
UpdateSystems(); // System will change based on flowEdges
|
||
NotifyChange(Change.flowEdge);
|
||
}
|
||
}
|
||
|
||
public class Change
|
||
{
|
||
public const int flowEdge = 1;
|
||
public const int dataEdge = 2;
|
||
|
||
public const int groupNode = 3;
|
||
|
||
public const int assetName = 4;
|
||
|
||
public const int ui = 5;
|
||
|
||
public const int destroy = 666;
|
||
}
|
||
|
||
bool RecreateFlowEdges()
|
||
{
|
||
bool changed = false;
|
||
HashSet<VFXFlowEdgeController> unusedEdges = new HashSet<VFXFlowEdgeController>();
|
||
foreach (var e in m_FlowEdges)
|
||
{
|
||
unusedEdges.Add(e);
|
||
}
|
||
|
||
var contextControllers = contexts;
|
||
foreach (var outController in contextControllers.ToArray())
|
||
{
|
||
var output = outController.model;
|
||
for (int slotIndex = 0; slotIndex < output.inputFlowSlot.Length; ++slotIndex)
|
||
{
|
||
var inputFlowSlot = output.inputFlowSlot[slotIndex];
|
||
foreach (var link in inputFlowSlot.link)
|
||
{
|
||
var inController = contexts.FirstOrDefault(x => x.model == link.context);
|
||
if (inController == null)
|
||
break;
|
||
|
||
var outputAnchor = inController.flowOutputAnchors.Where(o => o.slotIndex == link.slotIndex).FirstOrDefault();
|
||
var inputAnchor = outController.flowInputAnchors.Where(o => o.slotIndex == slotIndex).FirstOrDefault();
|
||
|
||
var edgeController = m_FlowEdges.FirstOrDefault(t => t.input == inputAnchor && t.output == outputAnchor);
|
||
if (edgeController != null)
|
||
unusedEdges.Remove(edgeController);
|
||
else
|
||
{
|
||
edgeController = new VFXFlowEdgeController(inputAnchor, outputAnchor);
|
||
m_FlowEdges.Add(edgeController);
|
||
changed = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach (var edge in unusedEdges)
|
||
{
|
||
edge.OnDisable();
|
||
m_FlowEdges.Remove(edge);
|
||
changed = true;
|
||
}
|
||
|
||
return changed;
|
||
}
|
||
|
||
public ReadOnlyCollection<VFXDataEdgeController> dataEdges
|
||
{
|
||
get { return m_DataEdges.AsReadOnly(); }
|
||
}
|
||
public ReadOnlyCollection<VFXFlowEdgeController> flowEdges
|
||
{
|
||
get { return m_FlowEdges.AsReadOnly(); }
|
||
}
|
||
|
||
public bool CreateLink(VFXDataAnchorController input, VFXDataAnchorController output, bool revertTypeConstraint = false)
|
||
{
|
||
if (input == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (input.sourceNode.viewController != output.sourceNode.viewController)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!input.CanLink(output))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
VFXParameter.NodeLinkedSlot resulting = input.CreateLinkTo(output, revertTypeConstraint);
|
||
|
||
if (resulting.inputSlot != null && resulting.outputSlot != null)
|
||
{
|
||
VFXParameterNodeController fromController = output.sourceNode as VFXParameterNodeController;
|
||
if (fromController != null)
|
||
{
|
||
foreach (var anyNode in fromController.parentController.nodes)
|
||
{
|
||
if (anyNode.infos.linkedSlots != null)
|
||
anyNode.infos.linkedSlots.RemoveAll(t => t.inputSlot == resulting.inputSlot && t.outputSlot == resulting.outputSlot);
|
||
}
|
||
|
||
if (fromController.infos.linkedSlots == null)
|
||
fromController.infos.linkedSlots = new List<VFXParameter.NodeLinkedSlot>();
|
||
fromController.infos.linkedSlots.Add(resulting);
|
||
}
|
||
|
||
VFXParameterNodeController toController = input.sourceNode as VFXParameterNodeController;
|
||
if (toController != null)
|
||
{
|
||
foreach (var anyNode in toController.parentController.nodes)
|
||
{
|
||
if (anyNode.infos.linkedSlots != null)
|
||
anyNode.infos.linkedSlots.RemoveAll(t => t.inputSlot == resulting.inputSlot && t.outputSlot == resulting.outputSlot);
|
||
}
|
||
|
||
var infos = toController.infos;
|
||
if (infos.linkedSlots == null)
|
||
infos.linkedSlots = new List<VFXParameter.NodeLinkedSlot>();
|
||
infos.linkedSlots.Add(resulting);
|
||
}
|
||
|
||
DataEdgesMightHaveChanged();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public void AddElement(VFXDataEdgeController edge)
|
||
{
|
||
var fromAnchor = edge.output;
|
||
var toAnchor = edge.input;
|
||
|
||
CreateLink(toAnchor, fromAnchor);
|
||
edge.OnDisable();
|
||
}
|
||
|
||
public void AddElement(VFXFlowEdgeController edge)
|
||
{
|
||
var flowEdge = (VFXFlowEdgeController)edge;
|
||
|
||
var outputFlowAnchor = flowEdge.output as VFXFlowAnchorController;
|
||
var inputFlowAnchor = flowEdge.input as VFXFlowAnchorController;
|
||
|
||
var contextOutput = outputFlowAnchor.owner;
|
||
var contextInput = inputFlowAnchor.owner;
|
||
|
||
contextOutput.LinkTo(contextInput, outputFlowAnchor.slotIndex, inputFlowAnchor.slotIndex);
|
||
|
||
edge.OnDisable();
|
||
}
|
||
|
||
public void Remove(IEnumerable<Controller> removedControllers, bool explicitDelete = false)
|
||
{
|
||
removedControllers = removedControllers.Except(removedControllers.OfType<VFXContextController>().Where(t => t.model is VFXBlockSubgraphContext)); //refuse to delete VFXBlockSubgraphContext
|
||
|
||
var removedContexts = new HashSet<VFXContextController>(removedControllers.OfType<VFXContextController>());
|
||
|
||
//remove all blocks that are in a removed context.
|
||
var removed = removedControllers.Where(t => !(t is VFXBlockController) || !removedContexts.Contains((t as VFXBlockController).contextController)).Distinct().ToArray();
|
||
|
||
foreach (var controller in removed)
|
||
{
|
||
RemoveElement(controller, explicitDelete);
|
||
}
|
||
}
|
||
|
||
bool m_ForceDataEdgeNotification;
|
||
|
||
public void RemoveElement(Controller element, bool explicitDelete = false)
|
||
{
|
||
VFXModel removedModel = null;
|
||
if (element is VFXContextController contextController)
|
||
{
|
||
VFXContext context = contextController.model;
|
||
removedModel = context;
|
||
contextController.NodeGoingToBeRemoved();
|
||
|
||
// Remove connections from context
|
||
foreach (var slot in context.inputSlots.Concat(context.outputSlots))
|
||
slot.UnlinkAll(true, true);
|
||
|
||
// Remove connections from blocks
|
||
foreach (VFXBlockController blockPres in (element as VFXContextController).blockControllers)
|
||
{
|
||
blockPres.slotContainer.activationSlot?.UnlinkAll(true, true);
|
||
foreach (var slot in blockPres.slotContainer.outputSlots.Concat(blockPres.slotContainer.inputSlots))
|
||
{
|
||
slot.UnlinkAll(true, true);
|
||
}
|
||
}
|
||
|
||
// remove flow connections from context
|
||
// TODO update data types
|
||
context.UnlinkAll();
|
||
// Detach from graph
|
||
context.Detach();
|
||
|
||
RemoveFromGroupNodes(element as VFXNodeController);
|
||
|
||
UnityObject.DestroyImmediate(context, true);
|
||
}
|
||
else if (element is VFXBlockController block)
|
||
{
|
||
removedModel = block.model;
|
||
block.NodeGoingToBeRemoved();
|
||
block.contextController.RemoveBlock(block.model);
|
||
|
||
UnityObject.DestroyImmediate(block.model, true);
|
||
}
|
||
else if (element is VFXParameterNodeController parameter)
|
||
{
|
||
removedModel = parameter.model;
|
||
parameter.NodeGoingToBeRemoved();
|
||
parameter.parentController.model.RemoveNode(parameter.infos);
|
||
RemoveFromGroupNodes(parameter);
|
||
DataEdgesMightHaveChanged();
|
||
}
|
||
else if (element is VFXNodeController or VFXParameterController)
|
||
{
|
||
IVFXSlotContainer container = null;
|
||
|
||
if (element is VFXNodeController nodeController)
|
||
{
|
||
removedModel = nodeController.model;
|
||
container = removedModel as IVFXSlotContainer;
|
||
nodeController.NodeGoingToBeRemoved();
|
||
RemoveFromGroupNodes(nodeController);
|
||
}
|
||
else
|
||
{
|
||
removedModel = ((VFXParameterController)element).model;
|
||
container = (IVFXSlotContainer)removedModel;
|
||
foreach (var parameterNode in m_SyncedModels[removedModel])
|
||
{
|
||
RemoveFromGroupNodes(parameterNode);
|
||
}
|
||
}
|
||
|
||
VFXSlot slotToClean = container.activationSlot;
|
||
do
|
||
{
|
||
if (slotToClean)
|
||
{
|
||
slotToClean.UnlinkAll(true, true);
|
||
}
|
||
slotToClean = container.inputSlots.Concat(container.outputSlots)
|
||
.FirstOrDefault(o => o.HasLink(true));
|
||
}
|
||
while (slotToClean != null);
|
||
|
||
graph.RemoveChild(container as VFXModel);
|
||
|
||
UnityObject.DestroyImmediate(container as VFXModel, true);
|
||
DataEdgesMightHaveChanged();
|
||
}
|
||
else if (element is VFXFlowEdgeController flowEdge)
|
||
{
|
||
var inputAnchor = flowEdge.input as VFXFlowAnchorController;
|
||
var outputAnchor = flowEdge.output as VFXFlowAnchorController;
|
||
|
||
if (inputAnchor != null && outputAnchor != null)
|
||
{
|
||
var contextInput = inputAnchor.owner as VFXContext;
|
||
var contextOutput = outputAnchor.owner as VFXContext;
|
||
|
||
if (contextInput != null && contextOutput != null)
|
||
contextInput.UnlinkFrom(contextOutput, outputAnchor.slotIndex, inputAnchor.slotIndex);
|
||
}
|
||
}
|
||
else if (element is VFXDataEdgeController)
|
||
{
|
||
var edge = element as VFXDataEdgeController;
|
||
var to = edge.input as VFXDataAnchorController;
|
||
|
||
if (to != null)
|
||
{
|
||
if (explicitDelete)
|
||
{
|
||
to.sourceNode.OnEdgeFromInputGoingToBeRemoved(to);
|
||
edge.output.sourceNode.OnEdgeFromOutputGoingToBeRemoved(edge.output, edge.input);
|
||
}
|
||
var slot = to.model;
|
||
if (slot != null)
|
||
{
|
||
slot.UnlinkAll();
|
||
}
|
||
m_ForceDataEdgeNotification = true;
|
||
}
|
||
}
|
||
else if (element is VFXGroupNodeController)
|
||
{
|
||
RemoveGroupNode(element as VFXGroupNodeController);
|
||
}
|
||
else if (element is VFXStickyNoteController)
|
||
{
|
||
RemoveStickyNote(element as VFXStickyNoteController);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogErrorFormat("Unexpected type : {0}", element.GetType().FullName);
|
||
}
|
||
|
||
if (removedModel is IVFXAttributeUsage attributeUsage && attributeUsage.usedAttributes.Any(x => graph.attributesManager.IsCustom(x.name)))
|
||
{
|
||
graph.SyncCustomAttributes();
|
||
}
|
||
}
|
||
|
||
private int m_LastFrameVFXReinit = 0;
|
||
|
||
private void ReinitIfNeeded(VFXModel model, VFXModel.InvalidationCause cause)
|
||
{
|
||
if (cause == VFXModel.InvalidationCause.kInitValueChanged)
|
||
{
|
||
var window = VFXViewWindow.GetWindow(this.graph, false, false);
|
||
int currentFrame = Time.frameCount;
|
||
|
||
if (window &&
|
||
window.autoReinit &&
|
||
m_LastFrameVFXReinit != currentFrame) // Prevent multi reinit per frame)
|
||
{
|
||
var vfx = window.graphView.attachedComponent;
|
||
|
||
if (vfx)
|
||
{
|
||
vfx.Reinit();
|
||
int targetFPS = VFXViewPreference.authoringPrewarmStepCountPerSeconds;
|
||
if (window.autoReinitPrewarmTime > 0.0f && targetFPS > 0)
|
||
{
|
||
bool alreadyHasPrewarm = vfx.visualEffectAsset.GetResource().preWarmStepCount > 0;
|
||
|
||
if (!alreadyHasPrewarm)
|
||
{
|
||
float stepTime = 1.0f / targetFPS;
|
||
uint stepCount = (uint)(window.autoReinitPrewarmTime / stepTime + 1);
|
||
stepTime = window.autoReinitPrewarmTime / stepCount;
|
||
vfx.Simulate(stepTime, stepCount);
|
||
}
|
||
}
|
||
}
|
||
m_LastFrameVFXReinit = currentFrame;
|
||
}
|
||
}
|
||
}
|
||
|
||
protected override void ModelChanged(UnityObject obj)
|
||
{
|
||
if (model == null)
|
||
{
|
||
NotifyChange(Change.destroy);
|
||
GraphLost();
|
||
|
||
RemoveController(this);
|
||
return;
|
||
}
|
||
|
||
// a standard equals will return true is the m_Graph is a destroyed object with the same instance ID ( like with a source control revert )
|
||
if (!object.ReferenceEquals(m_Graph, model.GetOrCreateGraph()))
|
||
{
|
||
if (!object.ReferenceEquals(m_Graph, null))
|
||
{
|
||
UnRegisterNotification(m_Graph, GraphChanged);
|
||
UnRegisterNotification(m_UI, UIChanged);
|
||
}
|
||
if (m_Graph != null)
|
||
{
|
||
GraphLost();
|
||
}
|
||
else
|
||
{
|
||
Clear();
|
||
}
|
||
m_Graph = model.GetOrCreateGraph();
|
||
m_Graph.SanitizeGraph();
|
||
|
||
if (m_Graph != null)
|
||
{
|
||
RegisterNotification(m_Graph, GraphChanged);
|
||
|
||
AddInvalidateDelegate(m_Graph, InvalidateExpressionGraph);
|
||
AddInvalidateDelegate(m_Graph, IncremenentGraphUndoRedoState);
|
||
AddInvalidateDelegate(m_Graph, ReinitIfNeeded);
|
||
|
||
m_UI = m_Graph.UIInfos;
|
||
|
||
RegisterNotification(m_UI, UIChanged);
|
||
|
||
GraphChanged();
|
||
}
|
||
}
|
||
}
|
||
|
||
public void AddGroupNode(Vector2 pos)
|
||
{
|
||
PrivateAddGroupNode(pos);
|
||
|
||
m_Graph.Invalidate(VFXModel.InvalidationCause.kUIChanged);
|
||
}
|
||
|
||
public void AddStickyNote(Vector2 position, VFXGroupNodeController group)
|
||
{
|
||
var ui = graph.UIInfos;
|
||
|
||
var stickyNoteInfo = new VFXUI.StickyNoteInfo
|
||
{
|
||
title = "Title",
|
||
position = new Rect(position, Vector2.one * 100),
|
||
contents = "type something here",
|
||
theme = StickyNoteTheme.Classic.ToString(),
|
||
textSize = StickyNoteFontSize.Small.ToString()
|
||
};
|
||
|
||
if (ui.stickyNoteInfos != null)
|
||
ui.stickyNoteInfos = ui.stickyNoteInfos.Concat(Enumerable.Repeat(stickyNoteInfo, 1)).ToArray();
|
||
else
|
||
ui.stickyNoteInfos = new VFXUI.StickyNoteInfo[] { stickyNoteInfo };
|
||
|
||
if (group != null)
|
||
{
|
||
LightApplyChanges();
|
||
|
||
group.AddStickyNote(m_StickyNoteControllers[ui.stickyNoteInfos.Length - 1]);
|
||
}
|
||
|
||
m_Graph.Invalidate(VFXModel.InvalidationCause.kUIChanged);
|
||
}
|
||
|
||
void RemoveGroupNode(VFXGroupNodeController groupNode)
|
||
{
|
||
var ui = graph.UIInfos;
|
||
|
||
int index = groupNode.index;
|
||
|
||
ui.groupInfos = ui.groupInfos.Where((t, i) => i != index).ToArray();
|
||
|
||
groupNode.Remove();
|
||
m_GroupNodeControllers.RemoveAt(index);
|
||
|
||
for (int i = index; i < m_GroupNodeControllers.Count; ++i)
|
||
{
|
||
m_GroupNodeControllers[i].index = i;
|
||
}
|
||
m_Graph.Invalidate(VFXModel.InvalidationCause.kUIChanged);
|
||
}
|
||
|
||
void RemoveStickyNote(VFXStickyNoteController stickyNote)
|
||
{
|
||
var ui = graph.UIInfos;
|
||
|
||
int index = stickyNote.index;
|
||
|
||
ui.stickyNoteInfos = ui.stickyNoteInfos.Where((t, i) => i != index).ToArray();
|
||
|
||
stickyNote.Remove();
|
||
m_StickyNoteControllers.RemoveAt(index);
|
||
|
||
for (int i = index; i < m_StickyNoteControllers.Count; ++i)
|
||
{
|
||
m_StickyNoteControllers[i].index = i;
|
||
}
|
||
|
||
//Patch group nodes, removing this sticky note and fixing ids that are bigger than index
|
||
if (ui.groupInfos != null)
|
||
{
|
||
for (int i = 0; i < ui.groupInfos.Length; ++i)
|
||
{
|
||
for (int j = 0; j < ui.groupInfos[i].contents.Length; ++j)
|
||
{
|
||
if (ui.groupInfos[i].contents[j].isStickyNote)
|
||
{
|
||
if (ui.groupInfos[i].contents[j].id == index)
|
||
{
|
||
ui.groupInfos[i].contents = ui.groupInfos[i].contents.Where((t, idx) => idx != j).ToArray();
|
||
j--;
|
||
}
|
||
else if (ui.groupInfos[i].contents[j].id > index)
|
||
{
|
||
--(ui.groupInfos[i].contents[j].id);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
m_Graph.Invalidate(VFXModel.InvalidationCause.kUIChanged);
|
||
}
|
||
|
||
void RemoveFromGroupNodes(VFXNodeController node)
|
||
{
|
||
foreach (var groupNode in m_GroupNodeControllers)
|
||
{
|
||
if (groupNode.ContainsNode(node))
|
||
{
|
||
groupNode.RemoveNode(node);
|
||
}
|
||
}
|
||
}
|
||
|
||
protected void GraphChanged()
|
||
{
|
||
if (m_Graph == null)
|
||
{
|
||
if (model != null)
|
||
{
|
||
ModelChanged(model);
|
||
}
|
||
return;
|
||
}
|
||
|
||
VFXGraphValidation validation = new VFXGraphValidation(m_Graph);
|
||
validation.ValidateGraph();
|
||
|
||
bool groupNodeChanged = false;
|
||
|
||
Profiler.BeginSample("VFXViewController.GraphChanged:SyncControllerFromModel");
|
||
SyncControllerFromModel(ref groupNodeChanged);
|
||
Profiler.EndSample();
|
||
|
||
Profiler.BeginSample("VFXViewController.GraphChanged:NotifyChange(AnyThing)");
|
||
NotifyChange(AnyThing);
|
||
Profiler.EndSample();
|
||
|
||
//if( groupNodeChanged)
|
||
{
|
||
Profiler.BeginSample("VFXViewController.GraphChanged:NotifyChange(Change.groupNode)");
|
||
NotifyChange(Change.groupNode);
|
||
Profiler.EndSample();
|
||
}
|
||
}
|
||
|
||
protected void UIChanged()
|
||
{
|
||
if (m_UI == null) return;
|
||
if (m_Graph == null) return; // OnModelChange or OnDisable will take care of that later
|
||
|
||
bool groupNodeChanged = false;
|
||
RecreateUI(ref groupNodeChanged);
|
||
|
||
NotifyChange(Change.ui);
|
||
}
|
||
|
||
public void NotifyParameterControllerChange()
|
||
{
|
||
DataEdgesMightHaveChanged();
|
||
if (!m_Syncing)
|
||
NotifyChange(AnyThing);
|
||
}
|
||
|
||
public void RegisterFlowAnchorController(VFXFlowAnchorController controller)
|
||
{
|
||
if (!m_FlowAnchorController.Contains(controller))
|
||
m_FlowAnchorController.Add(controller);
|
||
}
|
||
|
||
public void UnregisterFlowAnchorController(VFXFlowAnchorController controller)
|
||
{
|
||
m_FlowAnchorController.Remove(controller);
|
||
}
|
||
|
||
public static void CollectAncestorOperator(IVFXSlotContainer operatorInput, HashSet<IVFXSlotContainer> hashParents)
|
||
{
|
||
IEnumerable<VFXSlot> slots = operatorInput.inputSlots;
|
||
if (operatorInput.activationSlot != null)
|
||
slots = slots.Append(operatorInput.activationSlot);
|
||
|
||
foreach (var slotInput in slots)
|
||
{
|
||
var linkedSlots = slotInput.AllChildrenWithLink();
|
||
foreach (var linkedSlot in linkedSlots)
|
||
{
|
||
RecurseCollectAncestorOperator(linkedSlot.refSlot.owner, hashParents);
|
||
}
|
||
}
|
||
}
|
||
|
||
public static void RecurseCollectAncestorOperator(IVFXSlotContainer operatorInput, HashSet<IVFXSlotContainer> hashParents)
|
||
{
|
||
if (hashParents.Contains(operatorInput))
|
||
return;
|
||
|
||
hashParents.Add(operatorInput);
|
||
|
||
IEnumerable<VFXSlot> slots = operatorInput.inputSlots;
|
||
if (operatorInput.activationSlot != null)
|
||
slots = slots.Append(operatorInput.activationSlot);
|
||
|
||
foreach (var slotInput in slots)
|
||
{
|
||
var linkedSlots = slotInput.AllChildrenWithLink();
|
||
foreach (var linkedSlot in linkedSlots)
|
||
{
|
||
RecurseCollectAncestorOperator(linkedSlot.refSlot.owner, hashParents);
|
||
}
|
||
}
|
||
}
|
||
|
||
public static void CollectDescendantOperator(IVFXSlotContainer operatorInput, HashSet<IVFXSlotContainer> hashChildren)
|
||
{
|
||
foreach (var slotOutput in operatorInput.outputSlots)
|
||
{
|
||
var linkedSlots = slotOutput.AllChildrenWithLink();
|
||
foreach (var linkedSlot in linkedSlots)
|
||
{
|
||
foreach (var link in linkedSlot.LinkedSlots)
|
||
{
|
||
RecurseCollectDescendantOperator(link.owner, hashChildren);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public static void RecurseCollectDescendantOperator(IVFXSlotContainer operatorInput, HashSet<IVFXSlotContainer> hashChildren)
|
||
{
|
||
if (hashChildren.Contains(operatorInput))
|
||
return;
|
||
|
||
hashChildren.Add(operatorInput);
|
||
foreach (var slotOutput in operatorInput.outputSlots)
|
||
{
|
||
var linkedSlots = slotOutput.AllChildrenWithLink();
|
||
foreach (var linkedSlot in linkedSlots)
|
||
{
|
||
foreach (var link in linkedSlot.LinkedSlots)
|
||
{
|
||
RecurseCollectDescendantOperator(link.owner, hashChildren);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public IEnumerable<VFXDataAnchorController> GetCompatiblePorts(VFXDataAnchorController startAnchorController, NodeAdapter nodeAdapter)
|
||
{
|
||
var cacheLinkData = new VFXDataAnchorController.CanLinkCache();
|
||
|
||
var direction = startAnchorController.direction;
|
||
foreach (var slotContainer in AllSlotContainerControllers)
|
||
{
|
||
var sourceSlot = direction == Direction.Input ? slotContainer.outputPorts : slotContainer.inputPorts;
|
||
foreach (var slot in sourceSlot)
|
||
{
|
||
if (startAnchorController.CanLink(slot, cacheLinkData))
|
||
{
|
||
yield return slot;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public List<VFXFlowAnchorController> GetCompatiblePorts(VFXFlowAnchorController startAnchorController, NodeAdapter nodeAdapter)
|
||
{
|
||
var res = new List<VFXFlowAnchorController>();
|
||
|
||
var startFlowAnchorController = (VFXFlowAnchorController)startAnchorController;
|
||
foreach (var anchorController in m_FlowAnchorController)
|
||
{
|
||
VFXContext owner = anchorController.owner;
|
||
if (owner == null ||
|
||
startAnchorController == anchorController ||
|
||
startAnchorController.direction == anchorController.direction ||
|
||
owner == startFlowAnchorController.owner)
|
||
continue;
|
||
|
||
if (startAnchorController.direction == Direction.Input)
|
||
{
|
||
if (VFXFlowAnchorController.CanLink(anchorController, startFlowAnchorController))
|
||
res.Add(anchorController);
|
||
}
|
||
else
|
||
{
|
||
if (VFXFlowAnchorController.CanLink(startFlowAnchorController, anchorController))
|
||
res.Add(anchorController);
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
|
||
public void AddVFXModel(Vector2 pos, VFXModel model)
|
||
{
|
||
model.position = pos;
|
||
this.graph.AddChild(model);
|
||
}
|
||
|
||
public VFXContext AddVFXContext(Vector2 pos, Variant variant)
|
||
{
|
||
VFXContext model = (VFXContext)variant.CreateInstance();
|
||
AddVFXModel(pos, model);
|
||
return model;
|
||
}
|
||
|
||
public VFXOperator AddVFXOperator(Vector2 pos, Variant variant)
|
||
{
|
||
var model = (VFXOperator)variant.CreateInstance();
|
||
AddVFXModel(pos, model);
|
||
return model;
|
||
}
|
||
|
||
public VFXParameter AddVFXParameter(Vector2 pos, Variant variant, bool parent = true)
|
||
{
|
||
var parameter = (VFXParameter)variant.CreateInstance();
|
||
if (parent)
|
||
AddVFXModel(pos, parameter);
|
||
|
||
Type type = parameter.type;
|
||
|
||
parameter.collapsed = true;
|
||
|
||
int order = 0;
|
||
if (m_ParameterControllers.Count > 0)
|
||
{
|
||
order = m_ParameterControllers.Keys.Select(t => t.order).Max() + 1;
|
||
}
|
||
parameter.order = order;
|
||
parameter.SetSettingValue("m_ExposedName", $"New {ObjectNames.NicifyVariableName(type.UserFriendlyName())}");
|
||
|
||
if (!type.IsPrimitive)
|
||
{
|
||
parameter.value = VFXTypeExtension.GetDefaultField(type);
|
||
}
|
||
|
||
return parameter;
|
||
}
|
||
|
||
public VFXNodeController GetNewNodeController(VFXModel model)
|
||
{
|
||
List<VFXNodeController> nodeControllers = null;
|
||
if (m_SyncedModels.TryGetValue(model, out nodeControllers))
|
||
{
|
||
return nodeControllers.FirstOrDefault();
|
||
}
|
||
bool groupNodeChanged = false;
|
||
SyncControllerFromModel(ref groupNodeChanged);
|
||
|
||
m_SyncedModels.TryGetValue(model, out nodeControllers);
|
||
|
||
return nodeControllers.FirstOrDefault();
|
||
}
|
||
|
||
public VFXNodeController AddNode(Vector2 tPos, Variant variant, VFXGroupNodeController groupNode)
|
||
{
|
||
VFXModel newNode = null;
|
||
if (variant.modelType.IsSubclassOf(typeof(VFXOperator)))
|
||
{
|
||
newNode = AddVFXOperator(tPos, variant);
|
||
}
|
||
else if (variant.modelType.IsSubclassOf(typeof(VFXContext)))
|
||
{
|
||
newNode = AddVFXContext(tPos, variant);
|
||
}
|
||
else if (variant.modelType == typeof(VFXParameter))
|
||
{
|
||
newNode = AddVFXParameter(tPos, variant);
|
||
}
|
||
if (newNode != null)
|
||
{
|
||
var groupNodeChanged = false;
|
||
SyncControllerFromModel(ref groupNodeChanged);
|
||
|
||
m_SyncedModels.TryGetValue(newNode, out var nodeControllers);
|
||
|
||
if (newNode is VFXParameter newParameter)
|
||
{
|
||
// Set an exposed name on a new parameter so that uniqueness is ensured
|
||
m_ParameterControllers[newParameter].exposedName = $"New {ObjectNames.NicifyVariableName(newParameter.type.UserFriendlyName())}";
|
||
}
|
||
|
||
NotifyChange(AnyThing);
|
||
|
||
groupNode?.AddNode(nodeControllers.First());
|
||
|
||
return nodeControllers[0];
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
public VFXNodeController AddVFXParameter(Vector2 pos, VFXParameterController parameterController, VFXGroupNodeController groupNode)
|
||
{
|
||
if (parameterController.isOutput && parameterController.hasNodes)
|
||
{
|
||
return parameterController.nodes.First();
|
||
}
|
||
int id = parameterController.model.AddNode(pos);
|
||
|
||
LightApplyChanges();
|
||
|
||
var nodeController = GetRootNodeController(parameterController.model, id);
|
||
|
||
if (groupNode != null)
|
||
{
|
||
if (nodeController != null)
|
||
{
|
||
groupNode.AddNode(nodeController);
|
||
}
|
||
}
|
||
|
||
return nodeController;
|
||
}
|
||
|
||
public void Clear()
|
||
{
|
||
foreach (var element in allChildren)
|
||
{
|
||
element.OnDisable();
|
||
}
|
||
|
||
m_FlowAnchorController.Clear();
|
||
m_SyncedModels.Clear();
|
||
m_ParameterControllers.Clear();
|
||
m_DataEdges.Clear();
|
||
m_FlowEdges.Clear();
|
||
m_GroupNodeControllers.Clear();
|
||
m_StickyNoteControllers.Clear();
|
||
}
|
||
|
||
private Dictionary<VFXModel, List<VFXModel.InvalidateEvent>> m_registeredEvent = new Dictionary<VFXModel, List<VFXModel.InvalidateEvent>>();
|
||
public void AddInvalidateDelegate(VFXModel model, VFXModel.InvalidateEvent evt)
|
||
{
|
||
model.onInvalidateDelegate += evt;
|
||
if (!m_registeredEvent.ContainsKey(model))
|
||
{
|
||
m_registeredEvent.Add(model, new List<VFXModel.InvalidateEvent>());
|
||
}
|
||
m_registeredEvent[model].Add(evt);
|
||
}
|
||
|
||
public void RemoveInvalidateDelegate(VFXModel model, VFXModel.InvalidateEvent evt)
|
||
{
|
||
List<VFXModel.InvalidateEvent> evtList;
|
||
if (model != null && m_registeredEvent.TryGetValue(model, out evtList))
|
||
{
|
||
model.onInvalidateDelegate -= evt;
|
||
evtList.Remove(evt);
|
||
if (evtList.Count == 0)
|
||
{
|
||
m_registeredEvent.Remove(model);
|
||
}
|
||
}
|
||
}
|
||
|
||
static Dictionary<VisualEffectResource, VFXViewController> s_Controllers = new Dictionary<VisualEffectResource, VFXViewController>();
|
||
|
||
public static VFXViewController GetController(VisualEffectResource resource, bool forceUpdate = false)
|
||
{
|
||
//TRANSITION : delete VFXAsset as it should be in Library
|
||
resource.ValidateAsset();
|
||
|
||
VFXViewController controller;
|
||
if (!s_Controllers.TryGetValue(resource, out controller))
|
||
{
|
||
controller = new VFXViewController(resource);
|
||
s_Controllers[resource] = controller;
|
||
}
|
||
else
|
||
{
|
||
if (forceUpdate)
|
||
{
|
||
controller.ForceReload();
|
||
}
|
||
}
|
||
|
||
return controller;
|
||
}
|
||
|
||
static void RemoveController(VFXViewController controller)
|
||
{
|
||
if (s_Controllers.ContainsKey(controller.model))
|
||
{
|
||
controller.OnDisable();
|
||
s_Controllers.Remove(controller.model);
|
||
}
|
||
}
|
||
|
||
internal VFXViewController(VisualEffectResource vfx) : base(vfx)
|
||
{
|
||
ModelChanged(vfx); // This will initialize the graph from the vfx asset.
|
||
|
||
if (m_FlowAnchorController == null)
|
||
m_FlowAnchorController = new List<VFXFlowAnchorController>();
|
||
|
||
Undo.undoRedoPerformed += SynchronizeUndoRedoState;
|
||
Undo.willFlushUndoRecord += WillFlushUndoRecord;
|
||
|
||
string fileName = System.IO.Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(vfx));
|
||
vfx.name = fileName;
|
||
|
||
if (m_Graph != null)
|
||
m_Graph.BuildParameterInfo();
|
||
|
||
|
||
InitializeUndoStack();
|
||
GraphChanged();
|
||
|
||
Sanitize();
|
||
}
|
||
|
||
void Sanitize()
|
||
{
|
||
VFXParameter[] parameters = m_ParameterControllers.Keys.OrderBy(t => t.order).ToArray();
|
||
if (parameters.Length > 0)
|
||
{
|
||
var existingNames = new HashSet<string>();
|
||
|
||
existingNames.Add(parameters[0].exposedName);
|
||
m_ParameterControllers[parameters[0]].order = 0;
|
||
|
||
for (int i = 1; i < parameters.Length; ++i)
|
||
{
|
||
var controller = m_ParameterControllers[parameters[i]];
|
||
controller.order = i;
|
||
|
||
controller.CheckNameUnique(existingNames);
|
||
|
||
existingNames.Add(parameters[i].exposedName);
|
||
}
|
||
}
|
||
}
|
||
|
||
public ReadOnlyCollection<VFXGroupNodeController> groupNodes
|
||
{
|
||
get { return m_GroupNodeControllers.AsReadOnly(); }
|
||
}
|
||
public ReadOnlyCollection<VFXStickyNoteController> stickyNotes
|
||
{
|
||
get { return m_StickyNoteControllers.AsReadOnly(); }
|
||
}
|
||
|
||
List<VFXGroupNodeController> m_GroupNodeControllers = new List<VFXGroupNodeController>();
|
||
List<VFXStickyNoteController> m_StickyNoteControllers = new List<VFXStickyNoteController>();
|
||
|
||
public bool RecreateUI(ref bool groupNodeChanged)
|
||
{
|
||
bool changed = false;
|
||
var ui = graph.UIInfos;
|
||
|
||
if (ui.groupInfos != null)
|
||
{
|
||
HashSet<VFXNodeID> usedNodeIds = new HashSet<VFXNodeID>();
|
||
// first make sure that nodesID are at most in one groupnode.
|
||
|
||
for (int i = 0; i < ui.groupInfos.Length; ++i)
|
||
{
|
||
if (ui.groupInfos[i].contents != null)
|
||
{
|
||
for (int j = 0; j < ui.groupInfos[i].contents.Length; ++j)
|
||
{
|
||
if (usedNodeIds.Contains(ui.groupInfos[i].contents[j]))
|
||
{
|
||
Debug.Log("Element present in multiple groupnodes");
|
||
--j;
|
||
ui.groupInfos[i].contents = ui.groupInfos[i].contents.Where((t, k) => k != j).ToArray();
|
||
}
|
||
else
|
||
{
|
||
usedNodeIds.Add(ui.groupInfos[i].contents[j]);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (int i = m_GroupNodeControllers.Count; i < ui.groupInfos.Length; ++i)
|
||
{
|
||
VFXGroupNodeController groupNodeController = new VFXGroupNodeController(this, ui, i);
|
||
m_GroupNodeControllers.Add(groupNodeController);
|
||
changed = true;
|
||
groupNodeChanged = true;
|
||
}
|
||
|
||
while (ui.groupInfos.Length < m_GroupNodeControllers.Count)
|
||
{
|
||
m_GroupNodeControllers.Last().OnDisable();
|
||
m_GroupNodeControllers.RemoveAt(m_GroupNodeControllers.Count - 1);
|
||
changed = true;
|
||
groupNodeChanged = true;
|
||
}
|
||
}
|
||
if (ui.stickyNoteInfos != null)
|
||
{
|
||
for (int i = m_StickyNoteControllers.Count; i < ui.stickyNoteInfos.Length; ++i)
|
||
{
|
||
VFXStickyNoteController stickyNoteController = new VFXStickyNoteController(this, ui, i);
|
||
m_StickyNoteControllers.Add(stickyNoteController);
|
||
stickyNoteController.ApplyChanges();
|
||
changed = true;
|
||
}
|
||
|
||
while (ui.stickyNoteInfos.Length < m_StickyNoteControllers.Count)
|
||
{
|
||
m_StickyNoteControllers.Last().OnDisable();
|
||
m_StickyNoteControllers.RemoveAt(m_StickyNoteControllers.Count - 1);
|
||
changed = true;
|
||
}
|
||
}
|
||
|
||
return changed;
|
||
}
|
||
|
||
public void ValidateCategoryList()
|
||
{
|
||
if (!m_Syncing)
|
||
{
|
||
var ui = graph.UIInfos;
|
||
// Validate category list
|
||
var categories = ui.categories ?? new List<VFXUI.CategoryInfo>();
|
||
|
||
string[] missingCategories = m_ParameterControllers.Select(t => t.Key.category).Where(t => !string.IsNullOrEmpty(t)).Except(categories.Select(t => t.name)).ToArray();
|
||
|
||
HashSet<string> foundCategories = new HashSet<string>();
|
||
|
||
for (int i = 0; i < categories.Count; ++i)
|
||
{
|
||
string category = categories[i].name;
|
||
if (string.IsNullOrEmpty(category) || foundCategories.Contains(category))
|
||
{
|
||
categories.RemoveAt(i);
|
||
--i;
|
||
}
|
||
foundCategories.Add(category);
|
||
}
|
||
|
||
if (missingCategories.Length > 0)
|
||
{
|
||
categories.AddRange(missingCategories.Select(t => new VFXUI.CategoryInfo { name = t }));
|
||
ui.categories = categories;
|
||
ui.Modified(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
public void ForceReload()
|
||
{
|
||
Clear();
|
||
ModelChanged(model);
|
||
GraphChanged();
|
||
}
|
||
|
||
bool m_Syncing;
|
||
|
||
public bool SyncControllerFromModel(ref bool groupNodeChanged)
|
||
{
|
||
m_Syncing = true;
|
||
bool changed = false;
|
||
var toRemove = m_SyncedModels.Keys.Except(graph.children).ToList();
|
||
foreach (var m in toRemove)
|
||
{
|
||
RemoveControllersFromModel(m);
|
||
changed = true;
|
||
}
|
||
|
||
var toAdd = graph.children.Except(m_SyncedModels.Keys).ToList();
|
||
foreach (var m in toAdd)
|
||
{
|
||
AddControllersFromModel(m);
|
||
changed = true;
|
||
}
|
||
|
||
|
||
// make sure every parameter instance is created before we look for edges
|
||
foreach (var parameter in m_ParameterControllers.Values)
|
||
{
|
||
parameter.UpdateControllers();
|
||
}
|
||
|
||
changed |= RecreateNodeEdges();
|
||
changed |= RecreateFlowEdges();
|
||
|
||
changed |= RecreateUI(ref groupNodeChanged);
|
||
|
||
m_Syncing = false;
|
||
ValidateCategoryList();
|
||
UpdateSystems();
|
||
return changed;
|
||
}
|
||
|
||
Dictionary<VFXParameter, VFXParameterController> m_ParameterControllers = new Dictionary<VFXParameter, VFXParameterController>();
|
||
|
||
public IEnumerable<VFXParameterController> parameterControllers => m_ParameterControllers.Values;
|
||
|
||
|
||
public void ChangeCategory(VFXParameter parameter, string newCategory)
|
||
{
|
||
var parameterController = parameterControllers.Single(x => x.exposedName == parameter.exposedName);
|
||
parameterController.model.category = newCategory;
|
||
graph.Invalidate(VFXModel.InvalidationCause.kSettingChanged);
|
||
}
|
||
|
||
public int GetCategoryIndex(string category)
|
||
{
|
||
return graph.UIInfos.categories.FindIndex(x => string.Compare(x.name, category, StringComparison.OrdinalIgnoreCase) == 0);
|
||
}
|
||
|
||
public void MoveCategory(string category, int index)
|
||
{
|
||
if (graph.UIInfos.categories == null)
|
||
return;
|
||
int oldIndex = graph.UIInfos.categories.FindIndex(t => t.name == category);
|
||
|
||
if (oldIndex == -1 || oldIndex == index)
|
||
return;
|
||
graph.UIInfos.categories.RemoveAt(oldIndex);
|
||
if (index >= 0 && index < graph.UIInfos.categories.Count)
|
||
graph.UIInfos.categories.Insert(index, new VFXUI.CategoryInfo { name = category });
|
||
else
|
||
graph.UIInfos.categories.Add(new VFXUI.CategoryInfo { name = category });
|
||
|
||
graph.Invalidate(VFXModel.InvalidationCause.kUIChanged);
|
||
}
|
||
|
||
public bool RenameCategory(string oldName, string newName)
|
||
{
|
||
var category = graph.UIInfos.categories.SingleOrDefault(x => x.name == oldName);
|
||
if (!string.IsNullOrEmpty(category.name))
|
||
{
|
||
if (graph.UIInfos.categories.All(t => t.name != newName))
|
||
{
|
||
graph.UIInfos.categories.Remove(category);
|
||
category.name = newName;
|
||
graph.UIInfos.categories.Add(category);
|
||
|
||
foreach (var parameter in m_ParameterControllers)
|
||
{
|
||
if (parameter.Key.category == oldName)
|
||
{
|
||
parameter.Key.category = newName;
|
||
}
|
||
}
|
||
|
||
|
||
graph.Invalidate(VFXModel.InvalidationCause.kUIChanged);
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("Can't change name, category with the same name already exists");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("Can't change name, category not found");
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
public bool RemoveCategory(string category)
|
||
{
|
||
var index = graph.UIInfos.categories.FindIndex(x => string.Compare(x.name, category, StringComparison.OrdinalIgnoreCase) == 0);
|
||
|
||
if (index != -1)
|
||
{
|
||
graph.UIInfos.categories.RemoveAt(index);
|
||
var parametersToRemove = m_ParameterControllers
|
||
.Where(x => string.Compare(x.Key.category, category, StringComparison.OrdinalIgnoreCase) == 0)
|
||
.Select(x => x.Value)
|
||
.ToList();
|
||
|
||
Remove(parametersToRemove);
|
||
|
||
graph.Invalidate(VFXModel.InvalidationCause.kUIChanged);
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
public void SetParametersOrder(VFXParameterController controller, int index)
|
||
{
|
||
var orderedParameters = m_ParameterControllers
|
||
.Where(t => t.Value.isOutput == controller.isOutput && t.Value.model.category == controller.model.category)
|
||
.OrderBy(t => t.Value.order)
|
||
.Select(t => t.Value)
|
||
.ToList();
|
||
|
||
int oldIndex = orderedParameters.IndexOf(controller);
|
||
|
||
if (oldIndex != -1)
|
||
{
|
||
orderedParameters.RemoveAt(oldIndex);
|
||
|
||
if (oldIndex < index)
|
||
--index;
|
||
}
|
||
|
||
if (index < orderedParameters.Count)
|
||
orderedParameters.Insert(index, controller);
|
||
else
|
||
orderedParameters.Add(controller);
|
||
|
||
for (var i = 0; i < orderedParameters.Count; ++i)
|
||
{
|
||
orderedParameters[i].order = i;
|
||
}
|
||
NotifyChange(AnyThing);
|
||
}
|
||
|
||
public void GetCategoryExpanded(string categoryName, out bool isExpanded)
|
||
{
|
||
var category = graph.UIInfos.categories?.SingleOrDefault(x => x.name == categoryName);
|
||
if (!string.IsNullOrEmpty(category?.name))
|
||
{
|
||
isExpanded = !category.Value.collapsed;
|
||
}
|
||
else
|
||
{
|
||
isExpanded = false;
|
||
}
|
||
}
|
||
|
||
public void SetCategoryExpanded(string categoryName, bool expanded)
|
||
{
|
||
if (graph.UIInfos.categories != null)
|
||
{
|
||
for (int i = 0; i < graph.UIInfos.categories.Count; ++i)
|
||
{
|
||
var category = graph.UIInfos.categories[i];
|
||
if (category.name == categoryName && category.collapsed != !expanded)
|
||
{
|
||
category.collapsed = !expanded;
|
||
graph.UIInfos.categories[i] = category;
|
||
NotifyChange(AnyThing);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void AddControllersFromModel(VFXModel model)
|
||
{
|
||
List<VFXNodeController> newControllers = new List<VFXNodeController>();
|
||
if (model is VFXOperator)
|
||
{
|
||
if (model is VFXOperatorNumericCascadedUnified)
|
||
newControllers.Add(new VFXCascadedOperatorController(model as VFXOperator, this));
|
||
else if (model is VFXOperatorNumericUniform)
|
||
{
|
||
newControllers.Add(new VFXNumericUniformOperatorController(model as VFXOperator, this));
|
||
}
|
||
else if (model is VFXOperatorNumericUnified)
|
||
{
|
||
if (model is IVFXOperatorNumericUnifiedConstrained)
|
||
newControllers.Add(new VFXUnifiedConstraintOperatorController(model as VFXOperator, this));
|
||
else
|
||
newControllers.Add(new VFXUnifiedOperatorController(model as VFXOperator, this));
|
||
}
|
||
else if (model is VFXOperatorDynamicType)
|
||
{
|
||
newControllers.Add(new VFXDynamicTypeOperatorController(model as VFXOperator, this));
|
||
}
|
||
else
|
||
newControllers.Add(new VFXOperatorController(model as VFXOperator, this));
|
||
}
|
||
else if (model is VFXContext)
|
||
{
|
||
newControllers.Add(new VFXContextController(model as VFXContext, this));
|
||
}
|
||
else if (model is VFXParameter)
|
||
{
|
||
VFXParameter parameter = model as VFXParameter;
|
||
|
||
if (parameter.isOutput)
|
||
{
|
||
if (parameter.GetNbInputSlots() < 1)
|
||
{
|
||
parameter.AddSlot(VFXSlot.Create(new VFXProperty(typeof(float), "i"), VFXSlot.Direction.kInput));
|
||
}
|
||
while (parameter.GetNbInputSlots() > 1)
|
||
{
|
||
parameter.RemoveSlot(parameter.inputSlots[1]);
|
||
}
|
||
while (parameter.GetNbOutputSlots() > 0)
|
||
{
|
||
parameter.RemoveSlot(parameter.outputSlots[0]);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (parameter.GetNbOutputSlots() < 1)
|
||
{
|
||
parameter.AddSlot(VFXSlot.Create(new VFXProperty(typeof(float), "o"), VFXSlot.Direction.kOutput));
|
||
}
|
||
while (parameter.GetNbOutputSlots() > 1)
|
||
{
|
||
parameter.RemoveSlot(parameter.outputSlots[1]);
|
||
}
|
||
while (parameter.GetNbInputSlots() > 0)
|
||
{
|
||
parameter.RemoveSlot(parameter.inputSlots[0]);
|
||
}
|
||
}
|
||
|
||
parameter.ValidateNodes();
|
||
|
||
m_ParameterControllers[parameter] = new VFXParameterController(parameter, this);
|
||
|
||
m_SyncedModels[model] = new List<VFXNodeController>();
|
||
}
|
||
|
||
if (newControllers.Count > 0)
|
||
{
|
||
List<VFXNodeController> existingControllers;
|
||
if (m_SyncedModels.TryGetValue(model, out existingControllers))
|
||
{
|
||
Debug.LogError("adding a model to controllers twice");
|
||
}
|
||
m_SyncedModels[model] = newControllers;
|
||
foreach (var controller in newControllers)
|
||
{
|
||
controller.ForceUpdate();
|
||
}
|
||
}
|
||
}
|
||
|
||
public void AddControllerToModel(VFXModel model, VFXNodeController controller)
|
||
{
|
||
m_SyncedModels[model].Add(controller);
|
||
}
|
||
|
||
public void RemoveControllerFromModel(VFXModel model, VFXNodeController controller)
|
||
{
|
||
m_SyncedModels[model].Remove(controller);
|
||
}
|
||
|
||
private void RemoveControllersFromModel(VFXModel model)
|
||
{
|
||
List<VFXNodeController> controllers = null;
|
||
if (m_SyncedModels.TryGetValue(model, out controllers))
|
||
{
|
||
foreach (var controller in controllers)
|
||
{
|
||
controller.OnDisable();
|
||
}
|
||
m_SyncedModels.Remove(model);
|
||
}
|
||
if (model is VFXParameter)
|
||
{
|
||
m_ParameterControllers[model as VFXParameter].OnDisable();
|
||
m_ParameterControllers.Remove(model as VFXParameter);
|
||
}
|
||
}
|
||
|
||
public VFXNodeController GetNodeController(VFXModel model, int id)
|
||
{
|
||
if (model is VFXBlock)
|
||
{
|
||
VFXContextController controller = GetRootNodeController(model.GetParent(), 0) as VFXContextController;
|
||
if (controller == null)
|
||
return null;
|
||
return controller.blockControllers.FirstOrDefault(t => t.model == model);
|
||
}
|
||
else
|
||
{
|
||
return GetRootNodeController(model, id);
|
||
}
|
||
}
|
||
|
||
public void ChangeEventName(string oldName, string newName)
|
||
{
|
||
foreach (var context in m_SyncedModels.Keys.OfType<VFXBasicEvent>())
|
||
{
|
||
if (context.eventName == oldName)
|
||
context.SetSettingValue("eventName", newName);
|
||
}
|
||
}
|
||
|
||
public VFXNodeController GetRootNodeController(VFXModel model, int id)
|
||
{
|
||
List<VFXNodeController> controller = null;
|
||
m_SyncedModels.TryGetValue(model, out controller);
|
||
if (controller == null) return null;
|
||
|
||
return controller.FirstOrDefault(t => t.id == id);
|
||
}
|
||
|
||
public VFXStickyNoteController GetStickyNoteController(int index)
|
||
{
|
||
return m_StickyNoteControllers[index];
|
||
}
|
||
|
||
public VFXParameterController GetParameterController(VFXParameter parameter)
|
||
{
|
||
VFXParameterController controller = null;
|
||
m_ParameterControllers.TryGetValue(parameter, out controller);
|
||
return controller;
|
||
}
|
||
|
||
VFXUI.GroupInfo PrivateAddGroupNode(Vector2 position)
|
||
{
|
||
var ui = graph.UIInfos;
|
||
|
||
var newGroupInfo = new VFXUI.GroupInfo { title = "New Group Node", position = new Rect(position, Vector2.one * 100) };
|
||
|
||
if (ui.groupInfos != null)
|
||
ui.groupInfos = ui.groupInfos.Concat(Enumerable.Repeat(newGroupInfo, 1)).ToArray();
|
||
else
|
||
ui.groupInfos = new VFXUI.GroupInfo[] { newGroupInfo };
|
||
|
||
return ui.groupInfos.Last();
|
||
}
|
||
|
||
public void GroupNodes(VFXNodeController[] nodes)
|
||
{
|
||
// If a node from the selection already belongs to a group, remove it from this group
|
||
foreach (var g in groupNodes.ToArray())
|
||
{
|
||
g.RemoveNodes(nodes);
|
||
if (g.nodes.Count() ==0)
|
||
{
|
||
RemoveGroupNode(g);
|
||
}
|
||
}
|
||
|
||
var info = PrivateAddGroupNode(Vector2.zero);
|
||
|
||
info.contents = nodes.Select(t => new VFXNodeID(t.model, t.id)).ToArray();
|
||
|
||
m_Graph.Invalidate(VFXModel.InvalidationCause.kUIChanged);
|
||
}
|
||
|
||
public void PutInSameGroupNodeAs(VFXNodeController target, VFXNodeController example)
|
||
{
|
||
var ui = graph.UIInfos;
|
||
if (ui.groupInfos == null) return;
|
||
|
||
foreach (var groupNode in m_GroupNodeControllers)
|
||
{
|
||
if (groupNode.nodes.Contains(example))
|
||
{
|
||
groupNode.AddNode(target);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
List<VFXSystemController> m_Systems = new List<VFXSystemController>();
|
||
|
||
public ReadOnlyCollection<VFXSystemController> systems
|
||
{
|
||
get { return m_Systems.AsReadOnly(); }
|
||
}
|
||
|
||
|
||
public void UpdateSystems()
|
||
{
|
||
try
|
||
{
|
||
VFXContext[] directContexts = graph.children.OfType<VFXContext>().ToArray();
|
||
|
||
HashSet<VFXContext> initializes = new HashSet<VFXContext>(directContexts.Where(t => t.contextType == VFXContextType.Init).ToArray());
|
||
HashSet<VFXContext> updates = new HashSet<VFXContext>(directContexts.Where(t => t.contextType == VFXContextType.Update).ToArray());
|
||
|
||
List<Dictionary<VFXContext, int>> systems = new List<Dictionary<VFXContext, int>>();
|
||
|
||
|
||
while (initializes.Count > 0 || updates.Count > 0)
|
||
{
|
||
int generation = 0;
|
||
|
||
VFXContext currentContext;
|
||
if (initializes.Count > 0)
|
||
{
|
||
currentContext = initializes.First();
|
||
initializes.Remove(currentContext);
|
||
}
|
||
else
|
||
{
|
||
currentContext = updates.First();
|
||
updates.Remove(currentContext);
|
||
}
|
||
|
||
Dictionary<VFXContext, int> system = new Dictionary<VFXContext, int>();
|
||
|
||
system.Add(currentContext, generation);
|
||
|
||
var allChildren = currentContext.outputFlowSlot.Where(t => t != null).SelectMany(t => t.link.Select(u => u.context)).Where(t => t != null).ToList();
|
||
while (allChildren.Count() > 0)
|
||
{
|
||
++generation;
|
||
|
||
foreach (var child in allChildren)
|
||
{
|
||
initializes.Remove(child);
|
||
updates.Remove(child);
|
||
system.Add(child, generation);
|
||
}
|
||
|
||
var allSubChildren = allChildren.SelectMany(t => t.outputFlowSlot.Where(u => u != null).SelectMany(u => u.link.Select(v => v.context).Where(v => v != null)));
|
||
var allPreChildren = allChildren.SelectMany(t => t.inputFlowSlot.Where(u => u != null).SelectMany(u => u.link.Select(v => v.context).Where(v => v != null && v.contextType != VFXContextType.Spawner && v.contextType != VFXContextType.SpawnerGPU)));
|
||
|
||
allChildren = allSubChildren.Concat(allPreChildren).Except(system.Keys).ToList();
|
||
}
|
||
|
||
if (system.Count > 1)
|
||
systems.Add(system);
|
||
}
|
||
|
||
while (m_Systems.Count() < systems.Count())
|
||
{
|
||
VFXSystemController systemController = new VFXSystemController(graph.UIInfos);
|
||
m_Systems.Add(systemController);
|
||
}
|
||
|
||
while (m_Systems.Count() > systems.Count())
|
||
{
|
||
VFXSystemController systemController = m_Systems.Last();
|
||
m_Systems.RemoveAt(m_Systems.Count - 1);
|
||
systemController.OnDisable();
|
||
}
|
||
|
||
for (int i = 0; i < systems.Count(); ++i)
|
||
{
|
||
var contextToController = systems[i].Keys.Select(t => new KeyValuePair<VFXContextController, VFXContext>((VFXContextController)GetNodeController(t, 0), t)).Where(t => t.Key != null).ToDictionary(t => t.Value, t => t.Key);
|
||
m_Systems[i].contexts = contextToController.Values.ToArray();
|
||
VFXContextType type = VFXContextType.None;
|
||
VFXContext prevContext = null;
|
||
var orderedContexts = contextToController.Keys.OrderBy(t => t.contextType).ThenBy(t => systems[i][t]).ThenBy(t => t.position.x).ThenBy(t => t.position.y).ToArray();
|
||
|
||
char letter = 'A';
|
||
foreach (var context in orderedContexts)
|
||
{
|
||
if (context.contextType == type)
|
||
{
|
||
if (prevContext != null)
|
||
{
|
||
letter = 'A';
|
||
prevContext.letter = letter;
|
||
prevContext = null;
|
||
}
|
||
|
||
if (letter == 'Z') // loop back to A in the unlikely event that there are more than 26 contexts
|
||
letter = 'a';
|
||
else if (letter == 'z')
|
||
letter = 'α';
|
||
else if (letter == 'ω')
|
||
letter = 'A';
|
||
context.letter = ++letter;
|
||
}
|
||
else
|
||
{
|
||
context.letter = '\0';
|
||
prevContext = context;
|
||
}
|
||
type = context.contextType;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Debug.LogException(e);
|
||
}
|
||
}
|
||
|
||
private VFXGraph m_Graph;
|
||
|
||
private VFXUI m_UI;
|
||
|
||
private VFXView m_View; // Don't call directly as it is lazy initialized
|
||
}
|
||
}
|