2021-04-22 04:57:09 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "AnimBlueprintExtension_PropertyAccess.h"
2021-05-24 04:47:52 -04:00
2022-08-24 22:45:13 -04:00
# include "Containers/EnumAsByte.h"
# include "Containers/UnrealString.h"
# include "EdGraph/EdGraph.h"
# include "EdGraph/EdGraphNode.h"
# include "EdGraph/EdGraphPin.h"
# include "EdGraph/EdGraphSchema.h"
2021-05-24 04:47:52 -04:00
# include "EdGraphSchema_K2.h"
2022-08-24 22:45:13 -04:00
# include "Engine/Blueprint.h"
# include "Engine/BlueprintGeneratedClass.h"
# include "Engine/MemberReference.h"
2021-04-22 04:57:09 -04:00
# include "Features/IModularFeatures.h"
2022-08-24 22:45:13 -04:00
# include "HAL/Platform.h"
2021-04-22 04:57:09 -04:00
# include "IAnimBlueprintCompilationBracketContext.h"
2022-08-24 22:45:13 -04:00
# include "IPropertyAccessCompiler.h"
# include "IPropertyAccessEditor.h"
# include "Internationalization/Internationalization.h"
# include "K2Node.h"
# include "K2Node_CallFunction.h"
2021-05-24 04:47:52 -04:00
# include "K2Node_GetArrayItem.h"
# include "K2Node_VariableGet.h"
2021-04-22 04:57:09 -04:00
# include "Kismet2/CompilerResultsLog.h"
2022-08-24 22:45:13 -04:00
# include "KismetCompiler.h"
# include "Misc/AssertionMacros.h"
# include "Templates/Casts.h"
# include "Templates/Function.h"
# include "Templates/SubclassOf.h"
# include "UObject/Class.h"
# include "UObject/Object.h"
# include "UObject/UnrealType.h"
# include "UObject/WeakObjectPtrTemplates.h"
2021-04-22 04:57:09 -04:00
2021-05-24 04:47:52 -04:00
# define LOCTEXT_NAMESPACE "UAnimBlueprintExtension_PropertyAccess"
const FName UAnimBlueprintExtension_PropertyAccess : : ContextId_Automatic ( NAME_None ) ;
const FName UAnimBlueprintExtension_PropertyAccess : : ContextId_UnBatched_ThreadSafe ( TEXT ( " UnBatched_ThreadSafe " ) ) ;
const FName UAnimBlueprintExtension_PropertyAccess : : ContextId_Batched_WorkerThreadPreEventGraph ( TEXT ( " Batched_WorkerThreadPreEventGraph " ) ) ;
const FName UAnimBlueprintExtension_PropertyAccess : : ContextId_Batched_WorkerThreadPostEventGraph ( TEXT ( " Batched_WorkerThreadPostEventGraph " ) ) ;
const FName UAnimBlueprintExtension_PropertyAccess : : ContextId_Batched_GameThreadPreEventGraph ( TEXT ( " Batched_GameThreadPreEventGraph " ) ) ;
const FName UAnimBlueprintExtension_PropertyAccess : : ContextId_Batched_GameThreadPostEventGraph ( TEXT ( " Batched_GameThreadPostEventGraph " ) ) ;
FPropertyAccessHandle UAnimBlueprintExtension_PropertyAccess : : AddCopy ( TArrayView < FString > InSourcePath , TArrayView < FString > InDestPath , const FName & InContextId , UObject * InObject )
2021-04-22 04:57:09 -04:00
{
if ( PropertyAccessLibraryCompiler . IsValid ( ) )
{
2021-05-24 04:47:52 -04:00
return PropertyAccessLibraryCompiler - > AddCopy ( InSourcePath , InDestPath , InContextId , InObject ) ;
2021-04-22 04:57:09 -04:00
}
2021-05-24 04:47:52 -04:00
return FPropertyAccessHandle ( ) ;
2021-04-22 04:57:09 -04:00
}
void UAnimBlueprintExtension_PropertyAccess : : HandleStartCompilingClass ( const UClass * InClass , IAnimBlueprintCompilationBracketContext & InCompilationContext , IAnimBlueprintGeneratedClassCompiledData & OutCompiledData )
{
IPropertyAccessEditor & PropertyAccessEditor = IModularFeatures : : Get ( ) . GetModularFeature < IPropertyAccessEditor > ( " PropertyAccessEditor " ) ;
2021-05-24 04:47:52 -04:00
FCompilerResultsLog & MessageLog = InCompilationContext . GetMessageLog ( ) ;
FPropertyAccessLibraryCompilerArgs Args ( Subsystem . Library , InClass ) ;
Args . OnDetermineBatchId = FOnPropertyAccessDetermineBatchId : : CreateLambda ( [ & MessageLog ] ( const FPropertyAccessCopyContext & InContext ) - > int32
{
if ( InContext . ContextId = = ContextId_Automatic )
{
if ( InContext . bSourceThreadSafe & & InContext . bDestThreadSafe )
{
// Can only be in the worker thread batch if both endpoints are thread-safe
return ( int32 ) EAnimPropertyAccessCallSite : : WorkerThread_Unbatched ;
}
else
{
return ( int32 ) EAnimPropertyAccessCallSite : : GameThread_Batched_PreEventGraph ;
}
}
else if ( InContext . ContextId = = ContextId_UnBatched_ThreadSafe )
{
if ( InContext . bSourceThreadSafe & & InContext . bDestThreadSafe )
{
// Can only be in the worker thread batch if both endpoints are thread-safe
return ( int32 ) EAnimPropertyAccessCallSite : : WorkerThread_Unbatched ;
}
else
{
if ( ! InContext . bSourceThreadSafe )
{
MessageLog . Warning ( * FText : : Format ( LOCTEXT ( " ThreadSafetyIncompatible " , " @@ '{0}' is not thread-safe, access will be performed on the game thread (pre-event graph) and cached " ) , InContext . SourcePathAsText ) . ToString ( ) , InContext . Object ) ;
}
if ( ! InContext . bDestThreadSafe )
{
MessageLog . Warning ( * FText : : Format ( LOCTEXT ( " ThreadSafetyIncompatible " , " @@ '{0}' is not thread-safe, access will be performed on the game thread (pre-event graph) and cached " ) , InContext . DestPathAsText ) . ToString ( ) , InContext . Object ) ;
}
return ( int32 ) EAnimPropertyAccessCallSite : : GameThread_Batched_PreEventGraph ;
}
}
else if ( InContext . ContextId = = ContextId_Batched_GameThreadPreEventGraph )
{
return ( int32 ) EAnimPropertyAccessCallSite : : GameThread_Batched_PreEventGraph ;
}
else if ( InContext . ContextId = = ContextId_Batched_GameThreadPostEventGraph )
{
return ( int32 ) EAnimPropertyAccessCallSite : : GameThread_Batched_PostEventGraph ;
}
else if ( InContext . ContextId = = ContextId_Batched_WorkerThreadPreEventGraph )
{
if ( InContext . bSourceThreadSafe & & InContext . bDestThreadSafe )
{
return ( int32 ) EAnimPropertyAccessCallSite : : WorkerThread_Batched_PreEventGraph ;
}
else
{
if ( ! InContext . bSourceThreadSafe )
{
MessageLog . Warning ( * FText : : Format ( LOCTEXT ( " ThreadSafetyIncompatible " , " @@ '{0}' is not thread-safe, access will be performed on the game thread (pre-event graph) and cached " ) , InContext . SourcePathAsText ) . ToString ( ) , InContext . Object ) ;
}
if ( ! InContext . bDestThreadSafe )
{
MessageLog . Warning ( * FText : : Format ( LOCTEXT ( " ThreadSafetyIncompatible " , " @@ '{0}' is not thread-safe, access will be performed on the game thread (pre-event graph) and cached " ) , InContext . DestPathAsText ) . ToString ( ) , InContext . Object ) ;
}
return ( int32 ) EAnimPropertyAccessCallSite : : GameThread_Batched_PreEventGraph ;
}
}
else if ( InContext . ContextId = = ContextId_Batched_WorkerThreadPostEventGraph )
{
if ( InContext . bSourceThreadSafe & & InContext . bDestThreadSafe )
{
return ( int32 ) EAnimPropertyAccessCallSite : : WorkerThread_Batched_PostEventGraph ;
}
else
{
if ( ! InContext . bSourceThreadSafe )
{
MessageLog . Warning ( * FText : : Format ( LOCTEXT ( " ThreadSafetyIncompatible " , " @@ '{0}' is not thread-safe, access will be performed on the game thread (pre-event graph) and cached " ) , InContext . SourcePathAsText ) . ToString ( ) , InContext . Object ) ;
}
if ( ! InContext . bDestThreadSafe )
{
MessageLog . Warning ( * FText : : Format ( LOCTEXT ( " ThreadSafetyIncompatible " , " @@ '{0}' is not thread-safe, access will be performed on the game thread (pre-event graph) and cached " ) , InContext . DestPathAsText ) . ToString ( ) , InContext . Object ) ;
}
return ( int32 ) EAnimPropertyAccessCallSite : : GameThread_Batched_PreEventGraph ;
}
}
else
{
MessageLog . Warning ( * FText : : Format ( LOCTEXT ( " UnknownContext " , " @@ has unknown context '{0}', access will be performed on the game thread (pre-event graph) and cached " ) , FText : : FromName ( InContext . ContextId ) ) . ToString ( ) , InContext . Object ) ;
return ( int32 ) EAnimPropertyAccessCallSite : : GameThread_Batched_PreEventGraph ;
}
} ) ;
2021-04-22 04:57:09 -04:00
2021-05-24 04:47:52 -04:00
PropertyAccessLibraryCompiler = PropertyAccessEditor . MakePropertyAccessCompiler ( Args ) ;
2021-04-22 04:57:09 -04:00
PropertyAccessLibraryCompiler - > BeginCompilation ( ) ;
}
void UAnimBlueprintExtension_PropertyAccess : : HandleFinishCompilingClass ( const UClass * InClass , IAnimBlueprintCompilationBracketContext & InCompilationContext , IAnimBlueprintGeneratedClassCompiledData & OutCompiledData )
{
OnPreLibraryCompiledDelegate . Broadcast ( ) ;
if ( ! PropertyAccessLibraryCompiler - > FinishCompilation ( ) )
{
PropertyAccessLibraryCompiler - > IterateErrors ( [ & InCompilationContext ] ( const FText & InErrorText , UObject * InObject )
{
// Output any property access errors as warnings
if ( InObject )
{
InCompilationContext . GetMessageLog ( ) . Warning ( * InErrorText . ToString ( ) , InObject ) ;
}
else
{
InCompilationContext . GetMessageLog ( ) . Warning ( * InErrorText . ToString ( ) ) ;
}
} ) ;
}
OnPostLibraryCompiledDelegate . Broadcast ( InCompilationContext , OutCompiledData ) ;
PropertyAccessLibraryCompiler . Reset ( ) ;
}
2021-05-24 04:47:52 -04:00
FCompiledPropertyAccessHandle UAnimBlueprintExtension_PropertyAccess : : GetCompiledHandle ( FPropertyAccessHandle InHandle ) const
2021-04-22 04:57:09 -04:00
{
if ( PropertyAccessLibraryCompiler . IsValid ( ) )
{
2021-05-24 04:47:52 -04:00
return PropertyAccessLibraryCompiler - > GetCompiledHandle ( InHandle ) ;
2021-04-22 04:57:09 -04:00
}
2021-05-24 04:47:52 -04:00
return FCompiledPropertyAccessHandle ( ) ;
}
FText UAnimBlueprintExtension_PropertyAccess : : GetCompiledHandleContext ( FCompiledPropertyAccessHandle InHandle )
{
UEnum * EnumClass = StaticEnum < EAnimPropertyAccessCallSite > ( ) ;
check ( EnumClass ! = nullptr ) ;
return EnumClass - > GetDisplayNameTextByValue ( InHandle . GetBatchId ( ) ) ;
}
FText UAnimBlueprintExtension_PropertyAccess : : GetCompiledHandleContextDesc ( FCompiledPropertyAccessHandle InHandle )
{
UEnum * EnumClass = StaticEnum < EAnimPropertyAccessCallSite > ( ) ;
check ( EnumClass ! = nullptr ) ;
return EnumClass - > GetToolTipTextByIndex ( InHandle . GetBatchId ( ) ) ;
}
bool UAnimBlueprintExtension_PropertyAccess : : ContextRequiresCachedVariable ( FName InName )
{
if ( InName = = ContextId_Automatic )
{
// Indeterminate, so assume false - calling code will need to opt-in
return false ;
}
else if ( InName = = ContextId_UnBatched_ThreadSafe )
{
return false ;
}
else if ( InName = = ContextId_Batched_WorkerThreadPreEventGraph )
{
return true ;
}
else if ( InName = = ContextId_Batched_WorkerThreadPostEventGraph )
{
return true ;
}
else if ( InName = = ContextId_Batched_GameThreadPreEventGraph )
{
return true ;
}
else if ( InName = = ContextId_Batched_GameThreadPostEventGraph )
{
return true ;
}
return false ;
}
void UAnimBlueprintExtension_PropertyAccess : : ExpandPropertyAccess ( FKismetCompilerContext & InCompilerContext , TArrayView < FString > InSourcePath , UEdGraph * InParentGraph , UEdGraphPin * InTargetPin ) const
{
check ( InParentGraph ) ;
check ( InTargetPin ) ;
IPropertyAccessEditor & PropertyAccessEditor = IModularFeatures : : Get ( ) . GetModularFeature < IPropertyAccessEditor > ( " PropertyAccessEditor " ) ;
2021-10-25 20:05:28 -04:00
const UEdGraphSchema_K2 * K2Schema = GetDefault < UEdGraphSchema_K2 > ( ) ;
2021-05-24 04:47:52 -04:00
UEdGraphNode * SourceNode = InTargetPin - > GetOwningNode ( ) ;
check ( SourceNode ) ;
// Track the current pin in the pure chain
UEdGraphPin * CurrentPin = nullptr ;
auto SpawnVariableGetNode = [ & CurrentPin , & InCompilerContext , & SourceNode , & InParentGraph ] ( FName InPropertyName )
{
const UEdGraphSchema * GraphSchema = InParentGraph - > GetSchema ( ) ;
if ( CurrentPin = = nullptr )
{
// No current pin means we must be at the start of a chain with a 'self' member
UK2Node_VariableGet * VariableGetNode = InCompilerContext . SpawnIntermediateNode < UK2Node_VariableGet > ( SourceNode , InParentGraph ) ;
VariableGetNode - > VariableReference . SetSelfMember ( InPropertyName ) ;
VariableGetNode - > AllocateDefaultPins ( ) ;
// Find pin that we just created - variable out pin is now CurrentPin
CurrentPin = VariableGetNode - > FindPinChecked ( VariableGetNode - > GetVarName ( ) ) ;
}
else
{
// Current pin means we must be a class or a struct context
if ( UClass * Class = Cast < UClass > ( CurrentPin - > PinType . PinSubCategoryObject . Get ( ) ) )
{
UK2Node_VariableGet * VariableGetNode = InCompilerContext . SpawnIntermediateNode < UK2Node_VariableGet > ( SourceNode , InParentGraph ) ;
VariableGetNode - > VariableReference . SetExternalMember ( InPropertyName , Class ) ;
VariableGetNode - > AllocateDefaultPins ( ) ;
// Link current to target, connection must succeed
UEdGraphPin * TargetPin = VariableGetNode - > FindPinChecked ( UEdGraphSchema_K2 : : PN_Self ) ;
bool bSucceeded = GraphSchema - > TryCreateConnection ( CurrentPin , TargetPin ) ;
if ( ! bSucceeded )
{
InCompilerContext . MessageLog . Error ( * LOCTEXT ( " VariableConnectionFailed " , " @@ ICE: could not connect variable when expanding node " ) . ToString ( ) , SourceNode ) ;
}
// Find pin that we just created - variable out pin is now CurrentPin
CurrentPin = VariableGetNode - > FindPinChecked ( VariableGetNode - > GetVarName ( ) ) ;
}
else if ( UScriptStruct * ScriptStruct = Cast < UScriptStruct > ( CurrentPin - > PinType . PinSubCategoryObject . Get ( ) ) )
{
// Create a break struct/split pin node
const UEdGraphSchema_K2 * K2Schema = CastChecked < UEdGraphSchema_K2 > ( InParentGraph - > GetSchema ( ) ) ;
UK2Node * SplitPinNode = K2Schema - > CreateSplitPinNode ( CurrentPin , UEdGraphSchema_K2 : : FCreateSplitPinNodeParams ( & InCompilerContext , InParentGraph ) ) ;
2021-12-13 13:34:38 -05:00
UEdGraphPin * InputPin = SplitPinNode - > FindPinByPredicate ( [ ScriptStruct ] ( UEdGraphPin * InPin )
{
if ( InPin & & InPin - > Direction = = EGPD_Input & & InPin - > PinType . PinCategory = = UEdGraphSchema_K2 : : PC_Struct )
{
if ( UScriptStruct * PinStruct = Cast < UScriptStruct > ( InPin - > PinType . PinSubCategoryObject . Get ( ) ) )
{
return PinStruct = = ScriptStruct ;
}
}
return false ;
} ) ;
check ( InputPin ) ;
2021-05-25 04:35:25 -04:00
CurrentPin - > MakeLinkTo ( InputPin ) ;
2021-05-24 04:47:52 -04:00
// Current pin is the property of the struct
CurrentPin = SplitPinNode - > FindPinChecked ( InPropertyName ) ;
}
}
} ;
IPropertyAccessEditor : : FResolvePropertyAccessArgs Args ;
Args . PropertyFunction = [ & CurrentPin , & InCompilerContext , & SourceNode , & InParentGraph , & SpawnVariableGetNode ] ( int32 InSegmentIndex , FProperty * InProperty , int32 InStaticArrayIndex )
{
SpawnVariableGetNode ( InProperty - > GetFName ( ) ) ;
} ;
Args . ArrayFunction = [ & CurrentPin , & InCompilerContext , & SourceNode , & InParentGraph , & SpawnVariableGetNode ] ( int32 InSegmentIndex , FArrayProperty * InProperty , int32 InArrayIndex )
{
// First spawn a variable get to get the array into CurrentPin
SpawnVariableGetNode ( InProperty - > GetFName ( ) ) ;
// Spawn an array item getter
UK2Node_GetArrayItem * GetArrayItemNode = InCompilerContext . SpawnIntermediateNode < UK2Node_GetArrayItem > ( SourceNode , InParentGraph ) ;
GetArrayItemNode - > AllocateDefaultPins ( ) ;
UEdGraphPin * ArrayPin = GetArrayItemNode - > GetTargetArrayPin ( ) ;
UEdGraphPin * ResultPin = GetArrayItemNode - > GetResultPin ( ) ;
UEdGraphPin * IndexPin = GetArrayItemNode - > GetIndexPin ( ) ;
check ( ArrayPin & & ResultPin & & IndexPin ) ;
// Fill in the array index pin default value
IndexPin - > DefaultValue = FString : : FromInt ( InArrayIndex ) ;
// Connect up the array variable output (CurrentPin) to the array pin
const UEdGraphSchema * GraphSchema = InParentGraph - > GetSchema ( ) ;
check ( CurrentPin ) ;
bool bSucceeded = GraphSchema - > TryCreateConnection ( CurrentPin , ArrayPin ) ;
if ( ! bSucceeded )
{
InCompilerContext . MessageLog . Error ( * LOCTEXT ( " ArrayConnectionFailed " , " @@ ICE: could not connect array when expanding node " ) . ToString ( ) , SourceNode ) ;
}
// Array element is now the current pin
CurrentPin = ResultPin ;
} ;
Args . FunctionFunction = [ & CurrentPin , & InCompilerContext , & SourceNode , & InParentGraph ] ( int32 InSegmentIndex , UFunction * InFunction , FProperty * InReturnProperty )
{
// Spawn a function call
UK2Node_CallFunction * CallFunctionNode = InCompilerContext . SpawnIntermediateNode < UK2Node_CallFunction > ( SourceNode , InParentGraph ) ;
CallFunctionNode - > SetFromFunction ( InFunction ) ;
CallFunctionNode - > AllocateDefaultPins ( ) ;
if ( CurrentPin )
{
// If we have a current pin, hook it up to self
UEdGraphPin * SelfPin = CallFunctionNode - > FindPinChecked ( UEdGraphSchema_K2 : : PN_Self ) ;
const UEdGraphSchema * GraphSchema = InParentGraph - > GetSchema ( ) ;
bool bSucceeded = GraphSchema - > TryCreateConnection ( CurrentPin , SelfPin ) ;
if ( ! bSucceeded )
{
InCompilerContext . MessageLog . Error ( * LOCTEXT ( " FunctionConnectionFailed " , " @@ ICE: could not connect function when expanding node " ) . ToString ( ) , SourceNode ) ;
}
}
// The new current pin is the return value
UEdGraphPin * ReturnValuePin = CallFunctionNode - > GetReturnValuePin ( ) ;
check ( ReturnValuePin ) ;
CurrentPin = ReturnValuePin ;
} ;
FPropertyAccessResolveResult Result = PropertyAccessEditor . ResolvePropertyAccess ( InCompilerContext . Blueprint - > SkeletonGeneratedClass , InSourcePath , Args ) ;
if ( Result . Result = = EPropertyAccessResolveResult : : Succeeded )
{
const UEdGraphSchema * GraphSchema = InParentGraph - > GetSchema ( ) ;
// Link current pin to target
check ( CurrentPin ) ;
if ( InTargetPin - > Direction = = CurrentPin - > Direction )
{
2021-10-25 20:05:28 -04:00
if ( K2Schema - > ArePinTypesCompatible ( InTargetPin - > PinType , CurrentPin - > PinType , InCompilerContext . NewClass ) )
{
InCompilerContext . MovePinLinksToIntermediate ( * InTargetPin , * CurrentPin ) ;
}
else
{
// Need to create a conversion node. We will support basic conversion here for now as we are only looking for primitive types/casts
FName FuncName = NAME_None ;
UClass * FuncOwner = nullptr ;
if ( K2Schema - > SearchForAutocastFunction ( CurrentPin - > PinType , InTargetPin - > PinType , FuncName , FuncOwner ) )
{
UK2Node_CallFunction * AutoCastNode = InCompilerContext . SpawnIntermediateNode < UK2Node_CallFunction > ( SourceNode , InParentGraph ) ;
AutoCastNode - > FunctionReference . SetExternalMember ( FuncName , FuncOwner ) ;
AutoCastNode - > AllocateDefaultPins ( ) ;
// Find output pin & connect
UEdGraphPin * OutputPin = AutoCastNode - > FindPinByPredicate ( [ K2Schema , InTargetPin , & InCompilerContext ] ( UEdGraphPin * InPin )
{
return InPin - > Direction = = EGPD_Output & & K2Schema - > ArePinTypesCompatible ( InPin - > PinType , InTargetPin - > PinType , InCompilerContext . NewClass ) ;
} ) ;
UEdGraphPin * InputPin = AutoCastNode - > FindPinByPredicate ( [ K2Schema , CurrentPin , & InCompilerContext ] ( UEdGraphPin * InPin )
{
return InPin - > Direction = = EGPD_Input & & K2Schema - > ArePinTypesCompatible ( InPin - > PinType , CurrentPin - > PinType , InCompilerContext . NewClass ) ;
} ) ;
if ( InputPin & & OutputPin )
{
bool bSucceeded = GraphSchema - > TryCreateConnection ( CurrentPin , InputPin ) ;
if ( ! bSucceeded )
{
2022-03-04 16:10:23 -05:00
InCompilerContext . MessageLog . Error ( * LOCTEXT ( " AutocastConnectionFailed " , " @@ ICE: could not connect autocast when expanding node " ) . ToString ( ) , SourceNode ) ;
2021-10-25 20:05:28 -04:00
}
else
{
CurrentPin = OutputPin ;
InCompilerContext . MovePinLinksToIntermediate ( * InTargetPin , * CurrentPin ) ;
}
}
else
{
2022-03-04 16:10:23 -05:00
InCompilerContext . MessageLog . Error ( * LOCTEXT ( " AutocastPinsConnectionFailed " , " @@ ICE: could not find pins on autocast when expanding node " ) . ToString ( ) , SourceNode ) ;
2021-10-25 20:05:28 -04:00
}
}
else
{
2022-03-04 16:10:23 -05:00
InCompilerContext . MessageLog . Error ( * LOCTEXT ( " AutocastFunctionExpansionFailed " , " @@ could not make auto-cast function when expanding node " ) . ToString ( ) , SourceNode ) ;
2021-10-25 20:05:28 -04:00
}
}
2021-05-24 04:47:52 -04:00
}
else
{
bool bSucceeded = GraphSchema - > TryCreateConnection ( CurrentPin , InTargetPin ) ;
if ( ! bSucceeded )
{
InCompilerContext . MessageLog . Error ( * LOCTEXT ( " TargetConnectionFailed " , " @@ ICE: could not connect target when expanding node " ) . ToString ( ) , SourceNode ) ;
}
}
}
}
# undef LOCTEXT_NAMESPACE