2023-03-28 13:24:16 -04:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using UnityEngine ;
using UnityEditor.Graphing ;
using UnityEditor.ShaderGraph.Drawing.Colors ;
using UnityEditor.ShaderGraph.Internal ;
using UnityEditor.ShaderGraph.Drawing ;
using UnityEditor.ShaderGraph.Serialization ;
using UnityEngine.Assertions ;
using UnityEngine.Pool ;
namespace UnityEditor.ShaderGraph
{
[Serializable]
abstract class AbstractMaterialNode : JsonObject , IGroupItem , IRectInterface
{
[SerializeField]
JsonRef < GroupData > m_Group = null ;
[SerializeField]
private string m_Name ;
[SerializeField]
private DrawState m_DrawState ;
[NonSerialized]
bool m_HasError ;
[NonSerialized]
bool m_IsValid = true ;
[NonSerialized]
bool m_IsActive = true ;
[NonSerialized]
bool m_WasUsedByGenerator = false ;
[SerializeField]
List < JsonData < MaterialSlot > > m_Slots = new List < JsonData < MaterialSlot > > ( ) ;
public GraphData owner { get ; set ; }
internal virtual bool ExposeToSearcher = > true ;
OnNodeModified m_OnModified ;
public GroupData group
{
get = > m_Group ;
set
{
if ( m_Group = = value )
return ;
m_Group = value ;
Dirty ( ModificationScope . Topological ) ;
}
}
public void RegisterCallback ( OnNodeModified callback )
{
m_OnModified + = callback ;
}
public void UnregisterCallback ( OnNodeModified callback )
{
m_OnModified - = callback ;
}
public void Dirty ( ModificationScope scope )
{
if ( m_OnModified ! = null )
m_OnModified ( this , scope ) ;
}
public string name
{
get { return m_Name ; }
set { m_Name = value ; }
}
public string [ ] synonyms ;
protected virtual string documentationPage = > name ;
public virtual string documentationURL = > NodeUtils . GetDocumentationString ( documentationPage ) ;
public virtual bool canDeleteNode = > owner ! = null & & owner . outputNode ! = this ;
public DrawState drawState
{
get { return m_DrawState ; }
set
{
m_DrawState = value ;
Dirty ( ModificationScope . Layout ) ;
}
}
Rect IRectInterface . rect
{
get = > drawState . position ;
set
{
var state = drawState ;
state . position = value ;
drawState = state ;
}
}
public virtual bool canSetPrecision
{
get { return true ; }
}
// this is the precision after the inherit/automatic behavior has been calculated
// it does NOT include fallback to any graph default precision
public GraphPrecision graphPrecision { get ; set ; } = GraphPrecision . Single ;
private ConcretePrecision m_ConcretePrecision = ConcretePrecision . Single ;
public ConcretePrecision concretePrecision
{
get = > m_ConcretePrecision ;
set = > m_ConcretePrecision = value ;
}
[SerializeField]
private Precision m_Precision = Precision . Inherit ;
public Precision precision
{
get = > m_Precision ;
set = > m_Precision = value ;
}
[SerializeField]
bool m_PreviewExpanded = true ;
public bool previewExpanded
{
get { return m_PreviewExpanded ; }
set
{
if ( previewExpanded = = value )
return ;
m_PreviewExpanded = value ;
Dirty ( ModificationScope . Node ) ;
}
}
// by default, if this returns null, the system will allow creation of any previous version
public virtual IEnumerable < int > allowedNodeVersions = > null ;
// Nodes that want to have a preview area can override this and return true
public virtual bool hasPreview
{
get { return false ; }
}
[SerializeField]
internal PreviewMode m_PreviewMode = PreviewMode . Inherit ;
public virtual PreviewMode previewMode
{
get { return m_PreviewMode ; }
}
public virtual bool allowedInSubGraph
{
get { return ! ( this is BlockNode ) ; }
}
public virtual bool allowedInMainGraph
{
get { return true ; }
}
public virtual bool allowedInLayerGraph
{
get { return true ; }
}
public virtual bool hasError
{
get { return m_HasError ; }
protected set { m_HasError = value ; }
}
public virtual bool isActive
{
get { return m_IsActive ; }
}
internal virtual bool wasUsedByGenerator
{
get { return m_WasUsedByGenerator ; }
}
internal void SetUsedByGenerator ( )
{
m_WasUsedByGenerator = true ;
}
//There are times when isActive needs to be set to a value explicitly, and
//not be changed by active forest parsing (what we do when we need to figure out
//what nodes should or should not be active, usually from an edit; see NodeUtils).
//In this case, we allow for explicit setting of an active value that cant be overriden.
//Implicit implies that active forest parsing can edit the nodes isActive property
public enum ActiveState
{
Implicit = 0 ,
ExplicitInactive = 1 ,
ExplicitActive = 2
}
private ActiveState m_ActiveState = ActiveState . Implicit ;
public ActiveState activeState
{
get = > m_ActiveState ;
}
public void SetOverrideActiveState ( ActiveState overrideState , bool updateConnections = true )
{
if ( m_ActiveState = = overrideState )
{
return ;
}
m_ActiveState = overrideState ;
switch ( m_ActiveState )
{
case ActiveState . Implicit :
if ( updateConnections )
{
NodeUtils . ReevaluateActivityOfConnectedNodes ( this ) ;
}
break ;
case ActiveState . ExplicitInactive :
if ( m_IsActive = = false )
{
break ;
}
else
{
m_IsActive = false ;
Dirty ( ModificationScope . Node ) ;
if ( updateConnections )
{
NodeUtils . ReevaluateActivityOfConnectedNodes ( this ) ;
}
break ;
}
case ActiveState . ExplicitActive :
if ( m_IsActive = = true )
{
break ;
}
else
{
m_IsActive = true ;
Dirty ( ModificationScope . Node ) ;
if ( updateConnections )
{
NodeUtils . ReevaluateActivityOfConnectedNodes ( this ) ;
}
break ;
}
}
}
public void SetActive ( bool value , bool updateConnections = true )
{
if ( m_IsActive = = value )
return ;
if ( m_ActiveState ! = ActiveState . Implicit )
{
Debug . LogError ( $"Cannot set IsActive on Node {this} when value is explicitly overriden by ActiveState {m_ActiveState}" ) ;
return ;
}
// Update this node
m_IsActive = value ;
Dirty ( ModificationScope . Node ) ;
if ( updateConnections )
{
NodeUtils . ReevaluateActivityOfConnectedNodes ( this ) ;
}
}
public virtual bool isValid
{
get { return m_IsValid ; }
set
{
if ( m_IsValid = = value )
return ;
m_IsValid = value ;
}
}
string m_DefaultVariableName ;
string m_NameForDefaultVariableName ;
string defaultVariableName
{
get
{
if ( m_NameForDefaultVariableName ! = name )
{
m_DefaultVariableName = string . Format ( "{0}_{1}" , NodeUtils . GetHLSLSafeName ( name ? ? "node" ) , objectId ) ;
m_NameForDefaultVariableName = name ;
}
return m_DefaultVariableName ;
}
}
#region Custom Colors
[SerializeField]
CustomColorData m_CustomColors = new CustomColorData ( ) ;
public bool TryGetColor ( string provider , ref Color color )
{
return m_CustomColors . TryGetColor ( provider , out color ) ;
}
public void ResetColor ( string provider )
{
m_CustomColors . Remove ( provider ) ;
}
public void SetColor ( string provider , Color color )
{
m_CustomColors . Set ( provider , color ) ;
}
#endregion
protected AbstractMaterialNode ( )
{
m_DrawState . expanded = true ;
}
public void GetInputSlots < T > ( List < T > foundSlots ) where T : MaterialSlot
{
foreach ( var slot in m_Slots . SelectValue ( ) )
{
if ( slot . isInputSlot & & slot is T )
foundSlots . Add ( ( T ) slot ) ;
}
}
public virtual void GetInputSlots < T > ( MaterialSlot startingSlot , List < T > foundSlots ) where T : MaterialSlot
{
GetInputSlots ( foundSlots ) ;
}
public void GetOutputSlots < T > ( List < T > foundSlots ) where T : MaterialSlot
{
foreach ( var slot in m_Slots . SelectValue ( ) )
{
if ( slot . isOutputSlot & & slot is T materialSlot )
{
foundSlots . Add ( materialSlot ) ;
}
}
}
public virtual void GetOutputSlots < T > ( MaterialSlot startingSlot , List < T > foundSlots ) where T : MaterialSlot
{
GetOutputSlots ( foundSlots ) ;
}
public void GetSlots < T > ( List < T > foundSlots ) where T : MaterialSlot
{
foreach ( var slot in m_Slots . SelectValue ( ) )
{
if ( slot is T materialSlot )
{
foundSlots . Add ( materialSlot ) ;
}
}
}
public virtual void CollectShaderProperties ( PropertyCollector properties , GenerationMode generationMode )
{
foreach ( var inputSlot in this . GetInputSlots < MaterialSlot > ( ) )
{
var edges = owner . GetEdges ( inputSlot . slotReference ) ;
if ( edges . Any ( e = > e . outputSlot . node . isActive ) )
continue ;
inputSlot . AddDefaultProperty ( properties , generationMode ) ;
}
}
public string GetSlotValue ( int inputSlotId , GenerationMode generationMode , ConcretePrecision concretePrecision )
{
string slotValue = GetSlotValue ( inputSlotId , generationMode ) ;
return slotValue . Replace ( PrecisionUtil . Token , concretePrecision . ToShaderString ( ) ) ;
}
public string GetSlotValue ( int inputSlotId , GenerationMode generationMode )
{
var inputSlot = FindSlot < MaterialSlot > ( inputSlotId ) ;
if ( inputSlot = = null )
return string . Empty ;
var edges = owner . GetEdges ( inputSlot . slotReference ) ;
if ( edges . Any ( ) )
{
var fromSocketRef = edges . First ( ) . outputSlot ;
var fromNode = fromSocketRef . node ;
return fromNode . GetOutputForSlot ( fromSocketRef , inputSlot . concreteValueType , generationMode ) ;
}
return inputSlot . GetDefaultValue ( generationMode ) ;
}
public AbstractShaderProperty GetSlotProperty ( int inputSlotId )
{
if ( owner = = null )
return null ;
var inputSlot = FindSlot < MaterialSlot > ( inputSlotId ) ;
if ( inputSlot ? . slotReference . node = = null )
return null ;
var edges = owner . GetEdges ( inputSlot . slotReference ) ;
if ( edges . Any ( ) )
{
var fromSocketRef = edges . First ( ) . outputSlot ;
var fromNode = fromSocketRef . node ;
if ( fromNode = = null )
return null ; // this is an error condition... we have an edge that connects to a non-existant node?
if ( fromNode is PropertyNode propNode )
{
return propNode . property ;
}
if ( fromNode is RedirectNodeData redirectNode )
{
return redirectNode . GetSlotProperty ( RedirectNodeData . kInputSlotID ) ;
}
#if PROCEDURAL_VT_IN_GRAPH
if ( fromNode is ProceduralVirtualTextureNode pvtNode )
{
return pvtNode . AsShaderProperty ( ) ;
}
#endif // PROCEDURAL_VT_IN_GRAPH
return null ;
}
return null ;
}
protected internal virtual string GetOutputForSlot ( SlotReference fromSocketRef , ConcreteSlotValueType valueType , GenerationMode generationMode )
{
var slot = FindOutputSlot < MaterialSlot > ( fromSocketRef . slotId ) ;
if ( slot = = null )
return string . Empty ;
if ( fromSocketRef . node . isActive )
return GenerationUtils . AdaptNodeOutput ( this , slot . id , valueType ) ;
else
return slot . GetDefaultValue ( generationMode ) ;
}
public AbstractMaterialNode GetInputNodeFromSlot ( int inputSlotId )
{
var inputSlot = FindSlot < MaterialSlot > ( inputSlotId ) ;
if ( inputSlot = = null )
return null ;
var edges = owner . GetEdges ( inputSlot . slotReference ) . ToArray ( ) ;
AbstractMaterialNode fromNode = null ;
if ( edges . Count ( ) > 0 )
{
var fromSocketRef = edges [ 0 ] . outputSlot ;
fromNode = fromSocketRef . node ;
}
return fromNode ;
}
public static ConcreteSlotValueType ConvertDynamicVectorInputTypeToConcrete ( IEnumerable < ConcreteSlotValueType > inputTypes )
{
var concreteSlotValueTypes = inputTypes as IList < ConcreteSlotValueType > ? ? inputTypes . ToList ( ) ;
var inputTypesDistinct = concreteSlotValueTypes . Distinct ( ) . ToList ( ) ;
switch ( inputTypesDistinct . Count )
{
case 0 :
return ConcreteSlotValueType . Vector1 ;
case 1 :
if ( SlotValueHelper . AreCompatible ( SlotValueType . DynamicVector , inputTypesDistinct . First ( ) ) )
return inputTypesDistinct . First ( ) ;
break ;
default :
// find the 'minumum' channel width excluding 1 as it can promote
inputTypesDistinct . RemoveAll ( x = > x = = ConcreteSlotValueType . Vector1 ) ;
var ordered = inputTypesDistinct . OrderByDescending ( x = > x ) ;
if ( ordered . Any ( ) )
return ordered . FirstOrDefault ( ) ;
break ;
}
return ConcreteSlotValueType . Vector1 ;
}
public static ConcreteSlotValueType ConvertDynamicMatrixInputTypeToConcrete ( IEnumerable < ConcreteSlotValueType > inputTypes )
{
var concreteSlotValueTypes = inputTypes as IList < ConcreteSlotValueType > ? ? inputTypes . ToList ( ) ;
var inputTypesDistinct = concreteSlotValueTypes . Distinct ( ) . ToList ( ) ;
switch ( inputTypesDistinct . Count )
{
case 0 :
return ConcreteSlotValueType . Matrix2 ;
case 1 :
return inputTypesDistinct . FirstOrDefault ( ) ;
default :
var ordered = inputTypesDistinct . OrderByDescending ( x = > x ) ;
if ( ordered . Any ( ) )
return ordered . FirstOrDefault ( ) ;
break ;
}
return ConcreteSlotValueType . Matrix2 ;
}
protected const string k_validationErrorMessage = "Error found during node validation" ;
// evaluate ALL the precisions...
public virtual void UpdatePrecision ( List < MaterialSlot > inputSlots )
{
// first let's reduce from precision ==> graph precision
if ( precision = = Precision . Inherit )
{
// inherit means calculate it automatically based on inputs
// If no inputs were found use the precision of the Graph
if ( inputSlots . Count = = 0 )
{
graphPrecision = GraphPrecision . Graph ;
}
else
{
int curGraphPrecision = ( int ) GraphPrecision . Half ;
foreach ( var inputSlot in inputSlots )
{
// If input port doesn't have an edge use the Graph's precision for that input
var edges = owner ? . GetEdges ( inputSlot . slotReference ) . ToList ( ) ;
if ( ! edges . Any ( ) )
{
// disconnected inputs use graph precision
curGraphPrecision = Math . Min ( curGraphPrecision , ( int ) GraphPrecision . Graph ) ;
}
else
{
var outputSlotRef = edges [ 0 ] . outputSlot ;
var outputNode = outputSlotRef . node ;
curGraphPrecision = Math . Min ( curGraphPrecision , ( int ) outputNode . graphPrecision ) ;
}
}
graphPrecision = ( GraphPrecision ) curGraphPrecision ;
}
}
else
{
// not inherited, just use the node's selected precision
graphPrecision = precision . ToGraphPrecision ( GraphPrecision . Graph ) ;
}
// calculate the concrete precision, with fall-back to the graph concrete precision
concretePrecision = graphPrecision . ToConcrete ( owner . graphDefaultConcretePrecision ) ;
}
public virtual void EvaluateDynamicMaterialSlots ( List < MaterialSlot > inputSlots , List < MaterialSlot > outputSlots )
{
var dynamicInputSlotsToCompare = DictionaryPool < DynamicVectorMaterialSlot , ConcreteSlotValueType > . Get ( ) ;
var skippedDynamicSlots = ListPool < DynamicVectorMaterialSlot > . Get ( ) ;
var dynamicMatrixInputSlotsToCompare = DictionaryPool < DynamicMatrixMaterialSlot , ConcreteSlotValueType > . Get ( ) ;
var skippedDynamicMatrixSlots = ListPool < DynamicMatrixMaterialSlot > . Get ( ) ;
// iterate the input slots
{
foreach ( var inputSlot in inputSlots )
{
inputSlot . hasError = false ;
// if there is a connection
var edges = owner . GetEdges ( inputSlot . slotReference ) . ToList ( ) ;
if ( ! edges . Any ( ) )
{
if ( inputSlot is DynamicVectorMaterialSlot )
skippedDynamicSlots . Add ( inputSlot as DynamicVectorMaterialSlot ) ;
if ( inputSlot is DynamicMatrixMaterialSlot )
skippedDynamicMatrixSlots . Add ( inputSlot as DynamicMatrixMaterialSlot ) ;
continue ;
}
// get the output details
var outputSlotRef = edges [ 0 ] . outputSlot ;
var outputNode = outputSlotRef . node ;
if ( outputNode = = null )
continue ;
var outputSlot = outputNode . FindOutputSlot < MaterialSlot > ( outputSlotRef . slotId ) ;
if ( outputSlot = = null )
continue ;
if ( outputSlot . hasError )
{
inputSlot . hasError = true ;
continue ;
}
var outputConcreteType = outputSlot . concreteValueType ;
// dynamic input... depends on output from other node.
// we need to compare ALL dynamic inputs to make sure they
// are compatible.
if ( inputSlot is DynamicVectorMaterialSlot )
{
dynamicInputSlotsToCompare . Add ( ( DynamicVectorMaterialSlot ) inputSlot , outputConcreteType ) ;
continue ;
}
else if ( inputSlot is DynamicMatrixMaterialSlot )
{
dynamicMatrixInputSlotsToCompare . Add ( ( DynamicMatrixMaterialSlot ) inputSlot , outputConcreteType ) ;
continue ;
}
}
// we can now figure out the dynamic slotType
// from here set all the
var dynamicType = ConvertDynamicVectorInputTypeToConcrete ( dynamicInputSlotsToCompare . Values ) ;
foreach ( var dynamicKvP in dynamicInputSlotsToCompare )
dynamicKvP . Key . SetConcreteType ( dynamicType ) ;
foreach ( var skippedSlot in skippedDynamicSlots )
skippedSlot . SetConcreteType ( dynamicType ) ;
// and now dynamic matrices
var dynamicMatrixType = ConvertDynamicMatrixInputTypeToConcrete ( dynamicMatrixInputSlotsToCompare . Values ) ;
foreach ( var dynamicKvP in dynamicMatrixInputSlotsToCompare )
dynamicKvP . Key . SetConcreteType ( dynamicMatrixType ) ;
foreach ( var skippedSlot in skippedDynamicMatrixSlots )
skippedSlot . SetConcreteType ( dynamicMatrixType ) ;
bool inputError = inputSlots . Any ( x = > x . hasError ) ;
if ( inputError )
{
owner . AddConcretizationError ( objectId , string . Format ( "Node {0} had input error" , objectId ) ) ;
hasError = true ;
}
// configure the output slots now
// their slotType will either be the default output slotType
// or the above dynamic slotType for dynamic nodes
// or error if there is an input error
foreach ( var outputSlot in outputSlots )
{
outputSlot . hasError = false ;
if ( inputError )
{
outputSlot . hasError = true ;
continue ;
}
if ( outputSlot is DynamicVectorMaterialSlot dynamicVectorMaterialSlot )
{
dynamicVectorMaterialSlot . SetConcreteType ( dynamicType ) ;
continue ;
}
else if ( outputSlot is DynamicMatrixMaterialSlot dynamicMatrixMaterialSlot )
{
dynamicMatrixMaterialSlot . SetConcreteType ( dynamicMatrixType ) ;
continue ;
}
}
if ( outputSlots . Any ( x = > x . hasError ) )
{
owner . AddConcretizationError ( objectId , string . Format ( "Node {0} had output error" , objectId ) ) ;
hasError = true ;
}
CalculateNodeHasError ( ) ;
ListPool < DynamicVectorMaterialSlot > . Release ( skippedDynamicSlots ) ;
DictionaryPool < DynamicVectorMaterialSlot , ConcreteSlotValueType > . Release ( dynamicInputSlotsToCompare ) ;
ListPool < DynamicMatrixMaterialSlot > . Release ( skippedDynamicMatrixSlots ) ;
DictionaryPool < DynamicMatrixMaterialSlot , ConcreteSlotValueType > . Release ( dynamicMatrixInputSlotsToCompare ) ;
}
}
public virtual void Concretize ( )
{
hasError = false ;
owner ? . ClearErrorsForNode ( this ) ;
using ( var inputSlots = PooledList < MaterialSlot > . Get ( ) )
using ( var outputSlots = PooledList < MaterialSlot > . Get ( ) )
{
GetInputSlots ( inputSlots ) ;
GetOutputSlots ( outputSlots ) ;
UpdatePrecision ( inputSlots ) ;
EvaluateDynamicMaterialSlots ( inputSlots , outputSlots ) ;
}
}
public virtual void ValidateNode ( )
{
}
public virtual bool canCutNode = > true ;
public virtual bool canCopyNode = > true ;
protected virtual void CalculateNodeHasError ( )
{
foreach ( var slot in this . GetInputSlots < MaterialSlot > ( ) )
{
if ( slot . isConnected )
{
var edge = owner . GetEdges ( slot . slotReference ) . First ( ) ;
var outputNode = edge . outputSlot . node ;
var outputSlot = outputNode . GetOutputSlots < MaterialSlot > ( ) . First ( s = > s . id = = edge . outputSlot . slotId ) ;
if ( ! slot . IsCompatibleWith ( outputSlot ) )
{
owner . AddConcretizationError ( objectId , $"Slot {slot.RawDisplayName()} cannot accept input of type {outputSlot.concreteValueType}." ) ;
hasError = true ;
return ;
}
}
}
}
protected string GetRayTracingError ( ) = > $ @ "
2023-05-07 18:43:11 -04:00
#if defined ( SHADER_STAGE_RAY_TRACING ) & & defined ( RAYTRACING_SHADER_GRAPH_DEFAULT )
2023-03-28 13:24:16 -04:00
#error ' { name } ' node is not supported in ray tracing , please provide an alternate implementation , relying for instance on the ' Raytracing Quality ' keyword
#endif ";
public virtual void CollectPreviewMaterialProperties ( List < PreviewProperty > properties )
{
using ( var tempSlots = PooledList < MaterialSlot > . Get ( ) )
using ( var tempPreviewProperties = PooledList < PreviewProperty > . Get ( ) )
using ( var tempEdges = PooledList < IEdge > . Get ( ) )
{
GetInputSlots ( tempSlots ) ;
foreach ( var s in tempSlots )
{
tempPreviewProperties . Clear ( ) ;
tempEdges . Clear ( ) ;
if ( owner ! = null )
{
owner . GetEdges ( s . slotReference , tempEdges ) ;
if ( tempEdges . Any ( ) )
continue ;
}
s . GetPreviewProperties ( tempPreviewProperties , GetVariableNameForSlot ( s . id ) ) ;
for ( int i = 0 ; i < tempPreviewProperties . Count ; i + + )
{
if ( tempPreviewProperties [ i ] . name = = null )
continue ;
properties . Add ( tempPreviewProperties [ i ] ) ;
}
}
}
}
public virtual string GetVariableNameForSlot ( int slotId )
{
var slot = FindSlot < MaterialSlot > ( slotId ) ;
if ( slot = = null )
throw new ArgumentException ( string . Format ( "Attempting to use MaterialSlot({0}) on node of type {1} where this slot can not be found" , slotId , this ) , "slotId" ) ;
return string . Format ( "_{0}_{1}_{2}" , GetVariableNameForNode ( ) , NodeUtils . GetHLSLSafeName ( slot . shaderOutputName ) , unchecked ( ( uint ) slotId ) ) ;
}
public string GetConnnectionStateVariableNameForSlot ( int slotId )
{
return ShaderInput . GetConnectionStateVariableName ( GetVariableNameForSlot ( slotId ) ) ;
}
public virtual string GetVariableNameForNode ( )
{
return defaultVariableName ;
}
public MaterialSlot AddSlot ( MaterialSlot slot , bool attemptToModifyExistingInstance = true )
{
if ( slot = = null )
{
throw new ArgumentException ( $"Trying to add null slot to node {this}" ) ;
}
MaterialSlot foundSlot = FindSlot < MaterialSlot > ( slot . id ) ;
if ( slot = = foundSlot )
return foundSlot ;
// Try to keep the existing instance to avoid unnecessary changes to file
if ( attemptToModifyExistingInstance & & foundSlot ! = null & & slot . GetType ( ) = = foundSlot . GetType ( ) )
{
foundSlot . displayName = slot . RawDisplayName ( ) ;
foundSlot . CopyDefaultValue ( slot ) ;
return foundSlot ;
}
// keep the same ordering by replacing the first match, if it exists
int firstIndex = m_Slots . FindIndex ( s = > s . value . id = = slot . id ) ;
if ( firstIndex > = 0 )
{
m_Slots [ firstIndex ] = slot ;
// remove additional matches to get rid of unused duplicates
m_Slots . RemoveAllFromRange ( s = > s . value . id = = slot . id , firstIndex + 1 , m_Slots . Count - ( firstIndex + 1 ) ) ;
}
else
m_Slots . Add ( slot ) ;
slot . owner = this ;
OnSlotsChanged ( ) ;
if ( foundSlot = = null )
return slot ;
// foundSlot is of a different type; try to copy values
// I think this is to support casting if implemented in CopyValuesFrom ?
slot . CopyValuesFrom ( foundSlot ) ;
foundSlot . owner = null ;
return slot ;
}
public void RemoveSlot ( int slotId )
{
// Remove edges that use this slot
// no owner can happen after creation
// but before added to graph
if ( owner ! = null )
{
var edges = owner . GetEdges ( GetSlotReference ( slotId ) ) ;
foreach ( var edge in edges . ToArray ( ) )
owner . RemoveEdge ( edge ) ;
}
//remove slots
m_Slots . RemoveAll ( x = > x . value . id = = slotId ) ;
OnSlotsChanged ( ) ;
}
protected virtual void OnSlotsChanged ( )
{
Dirty ( ModificationScope . Topological ) ;
owner ? . ClearErrorsForNode ( this ) ;
}
public void RemoveSlotsNameNotMatching ( IEnumerable < int > slotIds , bool supressWarnings = false )
{
var invalidSlots = m_Slots . Select ( x = > x . value . id ) . Except ( slotIds ) ;
foreach ( var invalidSlot in invalidSlots . ToArray ( ) )
{
if ( ! supressWarnings )
Debug . LogWarningFormat ( "Removing Invalid MaterialSlot: {0}" , invalidSlot ) ;
RemoveSlot ( invalidSlot ) ;
}
}
public bool SetSlotOrder ( List < int > desiredOrderSlotIds )
{
bool changed = false ;
int writeIndex = 0 ;
for ( int orderIndex = 0 ; orderIndex < desiredOrderSlotIds . Count ; orderIndex + + )
{
var id = desiredOrderSlotIds [ orderIndex ] ;
var matchIndex = m_Slots . FindIndex ( s = > s . value . id = = id ) ;
if ( matchIndex < 0 )
{
// no matching slot with that id.. skip it
}
else
{
if ( writeIndex ! = matchIndex )
{
// swap the matching slot into position
var slot = m_Slots [ matchIndex ] ;
m_Slots [ matchIndex ] = m_Slots [ writeIndex ] ;
m_Slots [ writeIndex ] = slot ;
changed = true ;
}
writeIndex + + ;
}
}
return changed ;
}
public SlotReference GetSlotReference ( int slotId )
{
var slot = FindSlot < MaterialSlot > ( slotId ) ;
if ( slot = = null )
throw new ArgumentException ( "Slot could not be found" , "slotId" ) ;
return new SlotReference ( this , slotId ) ;
}
public T FindSlot < T > ( int slotId ) where T : MaterialSlot
{
foreach ( var slot in m_Slots . SelectValue ( ) )
{
if ( slot . id = = slotId & & slot is T )
return ( T ) slot ;
}
return default ( T ) ;
}
public T FindInputSlot < T > ( int slotId ) where T : MaterialSlot
{
foreach ( var slot in m_Slots . SelectValue ( ) )
{
if ( slot . isInputSlot & & slot . id = = slotId & & slot is T )
return ( T ) slot ;
}
return default ( T ) ;
}
public T FindOutputSlot < T > ( int slotId ) where T : MaterialSlot
{
foreach ( var slot in m_Slots . SelectValue ( ) )
{
if ( slot . isOutputSlot & & slot . id = = slotId & & slot is T )
return ( T ) slot ;
}
return default ( T ) ;
}
public virtual IEnumerable < MaterialSlot > GetInputsWithNoConnection ( )
{
return this . GetInputSlots < MaterialSlot > ( ) . Where ( x = > ! owner . GetEdges ( GetSlotReference ( x . id ) ) . Any ( ) ) ;
}
public void SetupSlots ( )
{
foreach ( var s in m_Slots . SelectValue ( ) )
s . owner = this ;
}
public virtual void UpdateNodeAfterDeserialization ( )
{ }
public bool IsSlotConnected ( int slotId )
{
var slot = FindSlot < MaterialSlot > ( slotId ) ;
return slot ! = null & & owner . GetEdges ( slot . slotReference ) . Any ( ) ;
}
public virtual void Setup ( ) { }
protected void EnqueSlotsForSerialization ( )
{
foreach ( var slot in m_Slots )
{
slot . OnBeforeSerialize ( ) ;
}
}
}
}