Files
UnrealEngine/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.cpp
2025-05-18 13:04:45 +08:00

2171 lines
84 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimBlueprintCompiler.h"
#include "Animation/AnimInstance.h"
#include "EdGraphUtilities.h"
#include "K2Node_CallFunction.h"
#include "K2Node_Knot.h"
#include "K2Node_StructMemberSet.h"
#include "K2Node_VariableGet.h"
#include "AnimationGraphSchema.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Kismet/KismetArrayLibrary.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetReinstanceUtilities.h"
#include "AnimGraphNode_Root.h"
#include "Animation/AnimNode_CustomProperty.h"
#include "AnimationEditorUtils.h"
#include "AnimationGraph.h"
#include "AnimBlueprintPostCompileValidation.h"
#include "AnimGraphNode_LinkedInputPose.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_FunctionResult.h"
#include "AnimGraphNode_LinkedAnimLayer.h"
#include "String/ParseTokens.h"
#include "Algo/Sort.h"
#include "IClassVariableCreator.h"
#include "AnimBlueprintGeneratedClassCompiledData.h"
#include "AnimBlueprintCompilationContext.h"
#include "AnimBlueprintCompilerCreationContext.h"
#include "AnimBlueprintVariableCreationContext.h"
#include "Animation/AnimSubsystemInstance.h"
#include "AnimBlueprintExtension.h"
#include "UObject/FrameworkObjectVersion.h"
#include "UObject/UE5MainStreamObjectVersion.h"
#include "AnimGraphNodeBinding.h"
#define LOCTEXT_NAMESPACE "AnimBlueprintCompiler"
DECLARE_CYCLE_STAT(TEXT("Merge Ubergraph Pages In"), EAnimBlueprintCompilerStats_MergeUbergraphPagesIn, STATGROUP_KismetCompiler);
DECLARE_CYCLE_STAT(TEXT("Process All Animation Nodes"), EAnimBlueprintCompilerStats_ProcessAllAnimationNodes, STATGROUP_KismetCompiler);
DECLARE_CYCLE_STAT(TEXT("Process Animation Nodes"), EAnimBlueprintCompilerStats_ProcessAnimationNodes, STATGROUP_KismetCompiler);
DECLARE_CYCLE_STAT(TEXT("Expand Split Pins"), EAnimBlueprintCompilerStats_ExpandSplitPins, STATGROUP_KismetCompiler);
DECLARE_CYCLE_STAT(TEXT("Move Graphs"), EAnimBlueprintCompilerStats_MoveGraphs, STATGROUP_KismetCompiler);
DECLARE_CYCLE_STAT(TEXT("Clone Graph"), EAnimBlueprintCompilerStats_CloneGraph, STATGROUP_KismetCompiler);
DECLARE_CYCLE_STAT(TEXT("Process Animation Node"), EAnimBlueprintCompilerStats_ProcessAnimationNode, STATGROUP_KismetCompiler);
DECLARE_CYCLE_STAT(TEXT("Gather Fold Records"), EAnimBlueprintCompilerStats_GatherFoldRecords, STATGROUP_KismetCompiler);
//////////////////////////////////////////////////////////////////////////
// FAnimBlueprintCompiler
FAnimBlueprintCompilerContext::FAnimBlueprintCompilerContext(UAnimBlueprint* SourceSketch, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompileOptions)
: FKismetCompilerContext(SourceSketch, InMessageLog, InCompileOptions)
, NewAnimBlueprintConstants(nullptr)
, NewAnimBlueprintMutables(nullptr)
, NewMutablesProperty(nullptr)
, AnimBlueprint(SourceSketch)
, OldSparseClassDataStruct(nullptr)
, bIsDerivedAnimBlueprint(false)
{
// Add the animation graph schema to skip default function processing on them
KnownGraphSchemas.AddUnique(UAnimationGraphSchema::StaticClass());
// Make sure the skeleton has finished preloading
if (AnimBlueprint->TargetSkeleton != nullptr)
{
if (FLinkerLoad* Linker = AnimBlueprint->TargetSkeleton->GetLinker())
{
Linker->Preload(AnimBlueprint->TargetSkeleton);
}
}
// If we need to, refresh all extensions here
if(AnimBlueprint->bRefreshExtensions)
{
UAnimBlueprintExtension::RefreshExtensions(AnimBlueprint);
AnimBlueprint->bRefreshExtensions = false;
}
if (AnimBlueprint->HasAnyFlags(RF_NeedPostLoad))
{
//Compilation during loading .. need to verify node guids as some anim blueprints have duplicated guids
TArray<UEdGraph*> ChildGraphs;
ChildGraphs.Reserve(20);
TMap<FGuid, UEdGraphNode*> GuidMap;
GuidMap.Reserve(200);
// Tracking to see if we need to warn for deterministic cooking
bool bNodeGuidsRegenerated = false;
auto CheckGraph = [&bNodeGuidsRegenerated, &GuidMap, &ChildGraphs](UEdGraph* InGraph)
{
if (AnimationEditorUtils::IsAnimGraph(InGraph))
{
ChildGraphs.Reset();
AnimationEditorUtils::FindChildGraphsFromNodes(InGraph, ChildGraphs);
for (int32 Index = 0; Index < ChildGraphs.Num(); ++Index) // Not ranged for as we modify array within the loop
{
UEdGraph* ChildGraph = ChildGraphs[Index];
// Get subgraphs before continuing
AnimationEditorUtils::FindChildGraphsFromNodes(ChildGraph, ChildGraphs);
for (UEdGraphNode* Node : ChildGraph->Nodes)
{
if (Node)
{
if (UEdGraphNode** FoundNode = GuidMap.Find(Node->NodeGuid))
{
if (*FoundNode != Node)
{
bNodeGuidsRegenerated = true;
Node->CreateNewGuid(); // GUID is already being used, create a new one.
}
}
else
{
GuidMap.Add(Node->NodeGuid, Node);
}
}
}
}
}
};
for (UEdGraph* Graph : AnimBlueprint->FunctionGraphs)
{
CheckGraph(Graph);
}
for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
for(UEdGraph* Graph : InterfaceDesc.Graphs)
{
CheckGraph(Graph);
}
}
if(bNodeGuidsRegenerated)
{
UE_LOG(LogAnimation, Warning, TEXT(
"Animation Blueprint %s has nodes with invalid node guids that have been regenerated. This blueprint "
"will not cook deterministically until it is resaved."), *AnimBlueprint->GetPathName());
}
}
FAnimBlueprintCompilerCreationContext CreationContext(this);
UAnimBlueprintExtension::ForEachExtension(AnimBlueprint, [&CreationContext](UAnimBlueprintExtension* InExtension)
{
InExtension->BeginCompilation(CreationContext);
});
// Determine if there is an anim blueprint in the ancestry of this class
bIsDerivedAnimBlueprint = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint) != NULL;
// Regenerate temporary stub functions
// We do this here to catch the standard and 'fast' (compilation manager) compilation paths
CreateAnimGraphStubFunctions();
// Stash any existing sparse class data struct here, as we may regenerate it
if (AnimBlueprint->GeneratedClass && AnimBlueprint->GeneratedClass->GetSparseClassDataStruct())
{
OldSparseClassDataStruct = AnimBlueprint->GeneratedClass->GetSparseClassDataStruct();
}
}
FAnimBlueprintCompilerContext::~FAnimBlueprintCompilerContext()
{
DestroyAnimGraphStubFunctions();
UAnimBlueprintExtension::ForEachExtension(AnimBlueprint, [](UAnimBlueprintExtension* InExtension)
{
InExtension->EndCompilation();
});
}
void FAnimBlueprintCompilerContext::ForAllSubGraphs(UEdGraph* InGraph, TFunctionRef<void(UEdGraph*)> InPerGraphFunction)
{
TArray<UEdGraph*> AllGraphs;
AllGraphs.Add(InGraph);
InGraph->GetAllChildrenGraphs(AllGraphs);
for(UEdGraph* CurrGraph : AllGraphs)
{
InPerGraphFunction(CurrGraph);
}
};
void FAnimBlueprintCompilerContext::CreateClassVariablesFromBlueprint()
{
FKismetCompilerContext::CreateClassVariablesFromBlueprint();
if(!bIsDerivedAnimBlueprint)
{
auto ProcessGraph = [this](UEdGraph* InGraph)
{
TArray<IClassVariableCreator*> ClassVariableCreators;
InGraph->GetNodesOfClass(ClassVariableCreators);
FAnimBlueprintVariableCreationContext CreationContext(this);
for(IClassVariableCreator* ClassVariableCreator : ClassVariableCreators)
{
ClassVariableCreator->CreateClassVariablesFromBlueprint(CreationContext);
}
};
for (UEdGraph* UbergraphPage : Blueprint->UbergraphPages)
{
ForAllSubGraphs(UbergraphPage, ProcessGraph);
}
for (UEdGraph* Graph : Blueprint->FunctionGraphs)
{
ForAllSubGraphs(Graph, ProcessGraph);
}
for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
for(UEdGraph* Graph : InterfaceDesc.Graphs)
{
ForAllSubGraphs(Graph, ProcessGraph);
}
}
}
}
UEdGraphSchema_K2* FAnimBlueprintCompilerContext::CreateSchema()
{
AnimSchema = NewObject<UAnimationGraphSchema>();
return AnimSchema;
}
void FAnimBlueprintCompilerContext::ProcessAnimationNode(UAnimGraphNode_Base* VisualAnimNode)
{
BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_ProcessAnimationNode);
// Early out if this node has already been processed
if (AllocatedAnimNodes.Contains(VisualAnimNode))
{
return;
}
// Make sure the visual node has a runtime node template
const UScriptStruct* NodeType = VisualAnimNode->GetFNodeType();
if (NodeType == nullptr)
{
MessageLog.Error(TEXT("@@ has no animation node member"), VisualAnimNode);
return;
}
// Give the visual node a chance to do validation
{
const int32 PreValidationErrorCount = MessageLog.NumErrors;
VisualAnimNode->ValidateAnimNodeDuringCompilation(AnimBlueprint->TargetSkeleton, MessageLog);
VisualAnimNode->BakeDataDuringCompilation(MessageLog);
if (MessageLog.NumErrors != PreValidationErrorCount)
{
return;
}
}
// Create a property for the node
const FString NodeVariableName = ClassScopeNetNameMap.MakeValidName(VisualAnimNode);
const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault<UAnimationGraphSchema>();
FEdGraphPinType NodeVariableType;
NodeVariableType.PinCategory = UAnimationGraphSchema::PC_Struct;
NodeVariableType.PinSubCategoryObject = MakeWeakObjectPtr(const_cast<UScriptStruct*>(NodeType));
FStructProperty* NewProperty = CastField<FStructProperty>(CreateVariable(FName(*NodeVariableName), NodeVariableType));
if (NewProperty == nullptr)
{
MessageLog.Error(TEXT("ICE: Failed to create node property for @@"), VisualAnimNode);
}
// Create a handler property in constants
UScriptStruct* HandlerStruct = nullptr;
if(const UAnimGraphNodeBinding* Binding = VisualAnimNode->GetBinding())
{
HandlerStruct = Binding->GetAnimNodeHandlerStruct();
check(HandlerStruct->IsChildOf(FAnimNodeExposedValueHandler::StaticStruct()));
}
else
{
HandlerStruct = FAnimNodeExposedValueHandler::StaticStruct();
}
FEdGraphPinType HandlerVariableType;
HandlerVariableType.PinCategory = UAnimationGraphSchema::PC_Struct;
HandlerVariableType.PinSubCategoryObject = MakeWeakObjectPtr(HandlerStruct);
FStructProperty* NewHandlerProperty = CastField<FStructProperty>(CreateStructVariable(NewAnimBlueprintConstants, FName(*NodeVariableName), HandlerVariableType));
if (NewHandlerProperty == nullptr)
{
MessageLog.Error(TEXT("ICE: Failed to create node handler property for @@"), VisualAnimNode);
}
GatherFoldRecordsForAnimationNode(NodeType, NewProperty, VisualAnimNode);
// Register this node with the compile-time data structures
const int32 AllocatedIndex = AllocateNodeIndexCounter++;
AllocatedAnimNodes.Add(VisualAnimNode, NewProperty);
AllocatedAnimNodeHandlers.Add(VisualAnimNode, NewHandlerProperty);
AllocatedNodePropertiesToNodes.Add(NewProperty, VisualAnimNode);
AllocatedAnimNodeIndices.Add(VisualAnimNode, AllocatedIndex);
AllocatedPropertiesByIndex.Add(AllocatedIndex, NewProperty);
UAnimGraphNode_Base* TrueSourceObject = MessageLog.FindSourceObjectTypeChecked<UAnimGraphNode_Base>(VisualAnimNode);
SourceNodeToProcessedNodeMap.Add(TrueSourceObject, VisualAnimNode);
// Register the slightly more permanent debug information
UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = GetNewAnimBlueprintClass();
FAnimBlueprintDebugData& NewAnimBlueprintDebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData();
NewAnimBlueprintDebugData.NodePropertyToIndexMap.Add(TrueSourceObject, AllocatedIndex);
NewAnimBlueprintDebugData.NodeGuidToIndexMap.Add(TrueSourceObject->NodeGuid, AllocatedIndex);
NewAnimBlueprintDebugData.NodePropertyIndexToNodeMap.Add(AllocatedIndex, TrueSourceObject);
NewAnimBlueprintClass->GetDebugData().RegisterClassPropertyAssociation(TrueSourceObject, NewProperty);
FAnimBlueprintGeneratedClassCompiledData CompiledData(NewAnimBlueprintClass);
FAnimBlueprintCompilationContext CompilerContext(this);
VisualAnimNode->ProcessDuringCompilation(CompilerContext, CompiledData);
}
void FAnimBlueprintCompilerContext::ProcessExtensions()
{
TArray<UAnimBlueprintExtension*> Extensions = UAnimBlueprintExtension::GetExtensions(AnimBlueprint);
// Sort extensions by class name (for determinism)
Algo::Sort(Extensions, [](UAnimBlueprintExtension* InExtensionA, UAnimBlueprintExtension* InExtensionB)
{
return InExtensionA->GetClass()->GetName() < InExtensionB->GetClass()->GetName();
});
// Process all gathered class extensions
for(UAnimBlueprintExtension* Extension : Extensions)
{
const FString ExtensionVariableName = ClassScopeNetNameMap.MakeValidName(Extension);
const UScriptStruct* InstanceDataType = Extension->GetInstanceDataType();
check(InstanceDataType->IsChildOf(FAnimSubsystemInstance::StaticStruct()));
const UScriptStruct* ClassDataType = Extension->GetClassDataType();
check(ClassDataType->IsChildOf(FAnimSubsystem::StaticStruct()));
// Skip creating any properties if both are the default
if(InstanceDataType != FAnimSubsystemInstance::StaticStruct() || ClassDataType != FAnimSubsystem::StaticStruct())
{
// Process instance data (mutable)
{
FEdGraphPinType ExtensionVariableType;
ExtensionVariableType.PinCategory = UAnimationGraphSchema::PC_Struct;
ExtensionVariableType.PinSubCategoryObject = MakeWeakObjectPtr(const_cast<UScriptStruct*>(InstanceDataType));
FStructProperty* NewProperty = CastField<FStructProperty>(CreateVariable(FName(*ExtensionVariableName), ExtensionVariableType));
if (NewProperty == nullptr)
{
MessageLog.Error(*FText::Format(LOCTEXT("ExtensionPropertyCreationFailed", "Failed to create extension property for '{0}'"), FText::FromString(Extension->GetName())).ToString());
}
else
{
ExtensionToInstancePropertyMap.Add(Extension, NewProperty);
InstancePropertyToExtensionMap.Add(NewProperty, Extension);
}
}
// Process class data (constants)
{
FEdGraphPinType ExtensionVariableType;
ExtensionVariableType.PinCategory = UAnimationGraphSchema::PC_Struct;
ExtensionVariableType.PinSubCategoryObject = MakeWeakObjectPtr(const_cast<UScriptStruct*>(ClassDataType));
FStructProperty* NewProperty = CastField<FStructProperty>(CreateStructVariable(NewAnimBlueprintConstants, FName(*ExtensionVariableName), ExtensionVariableType));
if (NewProperty == nullptr)
{
MessageLog.Error(*FText::Format(LOCTEXT("ExtensionPropertyCreationFailed", "Failed to create extension property for '{0}'"), FText::FromString(Extension->GetName())).ToString());
}
else
{
ExtensionToClassPropertyMap.Add(Extension, NewProperty);
ClassPropertyToExtensionMap.Add(NewProperty, Extension);
}
}
}
}
}
void FAnimBlueprintCompilerContext::GatherFoldRecordsForAnimationNode(const UScriptStruct* InNodeType, FStructProperty* InNodeProperty, UAnimGraphNode_Base* InVisualAnimNode)
{
BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_GatherFoldRecords);
static const FName NAME_FoldProperty("FoldProperty");
// Run through node properties to see if any are eligible for folding
for(TFieldIterator<FProperty> It(InNodeType); It; ++It)
{
FProperty* SubProperty = *It;
if(FArrayProperty* ArrayProperty = CastField<FArrayProperty>(SubProperty))
{
bool bAllPinsExposed = true;
bool bAllPinsDisconnected = true;
const bool bAlwaysDynamic = InVisualAnimNode->AlwaysDynamicProperties.Contains(SubProperty->GetFName());
// If a value is exposed on a pin but disconnected, push the value to the (intermediate) node here to simplify later logic
if(SubProperty->HasAnyPropertyFlags(CPF_Edit|CPF_BlueprintVisible))
{
FStructProperty* AnimGraphNodeProperty = InVisualAnimNode->GetFNodeProperty();
// Check the anim node property is contained in the anim graph node
check(AnimGraphNodeProperty->GetOwner<UClass>() && InVisualAnimNode->GetClass()->IsChildOf(AnimGraphNodeProperty->GetOwner<UClass>()));
const void* Node = AnimGraphNodeProperty->ContainerPtrToValuePtr<void>(InVisualAnimNode);
// Check the anim node's property is contained in the anim node
check(SubProperty->GetOwner<UStruct>() && AnimGraphNodeProperty->Struct->IsChildOf(SubProperty->GetOwner<UStruct>()));
const void* TargetPtr = SubProperty->ContainerPtrToValuePtr<void>(Node);
// Run through each array element - we can only fold array properties if all values are constants
FScriptArrayHelper ArrayHelper(ArrayProperty, TargetPtr);
for(int32 ArrayIndex = 0; ArrayIndex < ArrayHelper.Num(); ++ArrayIndex)
{
const FString ArrayElementPinName = SubProperty->GetName() + FString::Printf(TEXT("_%d"), ArrayIndex);
UEdGraphPin* Pin = InVisualAnimNode->FindPin(ArrayElementPinName);
const bool bExposedOnPin = Pin != nullptr;
const bool bPinConnected = InVisualAnimNode->IsPinExposedAndLinked(ArrayElementPinName, EGPD_Input);
const bool bPinBound = InVisualAnimNode->IsPinExposedAndBound(ArrayElementPinName, EGPD_Input);
if(bExposedOnPin)
{
if(!(bPinConnected || bPinBound))
{
check(Pin);
if(!FBlueprintEditorUtils::PropertyValueFromString_Direct(ArrayProperty->Inner, Pin->GetDefaultAsString(), ArrayHelper.GetRawPtr(ArrayIndex)))
{
MessageLog.Warning(TEXT("Unable to push default value for array pin @@ on @@"), Pin, InVisualAnimNode);
}
}
else
{
bAllPinsDisconnected = false;
}
}
else
{
bAllPinsExposed = false;
}
}
}
if(SubProperty->HasMetaData(NAME_FoldProperty))
{
// Add folding candidate
AddFoldedPropertyRecord(InVisualAnimNode, InNodeProperty, SubProperty, bAllPinsExposed, !bAllPinsDisconnected, bAlwaysDynamic);
}
}
else
{
UEdGraphPin* Pin = InVisualAnimNode->FindPin(SubProperty->GetName());
const bool bExposedOnPin = Pin != nullptr;
const bool bPinConnected = InVisualAnimNode->IsPinExposedAndLinked(SubProperty->GetName(), EGPD_Input);
const bool bPinBound = InVisualAnimNode->IsPinExposedAndBound(SubProperty->GetName(), EGPD_Input);
const bool bAlwaysDynamic = InVisualAnimNode->AlwaysDynamicProperties.Contains(SubProperty->GetFName());
// If a value is exposed on a pin but disconnected, push the value to the (intermediate) node here to simplify later logic
if(SubProperty->HasAnyPropertyFlags(CPF_Edit|CPF_BlueprintVisible))
{
if(bExposedOnPin && !(bPinConnected || bPinBound))
{
check(Pin);
FStructProperty* AnimGraphNodeProperty = InVisualAnimNode->GetFNodeProperty();
// Check the anim node property is contained in the anim graph node
check(AnimGraphNodeProperty->GetOwner<UClass>() && InVisualAnimNode->GetClass()->IsChildOf(AnimGraphNodeProperty->GetOwner<UClass>()));
const void* Node = AnimGraphNodeProperty->ContainerPtrToValuePtr<void>(InVisualAnimNode);
// Check the anim node's property is contained in the anim node
check(SubProperty->GetOwner<UStruct>() && AnimGraphNodeProperty->Struct->IsChildOf(SubProperty->GetOwner<UStruct>()));
const void* TargetPtr = SubProperty->ContainerPtrToValuePtr<void>(Node);
if(!FBlueprintEditorUtils::PropertyValueFromString_Direct(SubProperty, Pin->GetDefaultAsString(), (uint8*)TargetPtr))
{
MessageLog.Warning(TEXT("Unable to push default value for pin @@ on @@"), Pin, InVisualAnimNode);
}
}
}
if(SubProperty->HasMetaData(NAME_FoldProperty))
{
// Add folding candidate
AddFoldedPropertyRecord(InVisualAnimNode, InNodeProperty, SubProperty, bExposedOnPin, bPinConnected || bPinBound, bAlwaysDynamic);
}
}
}
}
int32 FAnimBlueprintCompilerContext::GetAllocationIndexOfNode(UAnimGraphNode_Base* VisualAnimNode)
{
ProcessAnimationNode(VisualAnimNode);
int32* pResult = AllocatedAnimNodeIndices.Find(VisualAnimNode);
return (pResult != NULL) ? *pResult : INDEX_NONE;
}
bool FAnimBlueprintCompilerContext::ShouldForceKeepNode(const UEdGraphNode* Node) const
{
// Force keep anim nodes during the standard pruning step. Isolated ones will then be removed via PruneIsolatedAnimationNodes.
// Anim graph nodes are always culled at their expansion step anyways.
return Node->IsA<UAnimGraphNode_Base>();
}
void FAnimBlueprintCompilerContext::PostExpansionStep(const UEdGraph* Graph)
{
FAnimBlueprintGeneratedClassCompiledData CompiledData(GetNewAnimBlueprintClass());
FAnimBlueprintPostExpansionStepContext CompilerContext(this);
UAnimBlueprintExtension::ForEachExtension(AnimBlueprint, [Graph, &CompiledData, &CompilerContext](UAnimBlueprintExtension* InExtension)
{
InExtension->PostExpansionStep(Graph, CompilerContext, CompiledData);
});
}
void FAnimBlueprintCompilerContext::PruneIsolatedAnimationNodes(const TArray<UAnimGraphNode_Base*>& RootSet, TArray<UAnimGraphNode_Base*>& GraphNodes)
{
struct FNodeVisitorDownPoseWires
{
TSet<UEdGraphNode*> VisitedNodes;
const UAnimationGraphSchema* Schema;
FNodeVisitorDownPoseWires()
{
Schema = GetDefault<UAnimationGraphSchema>();
}
void TraverseNodes(UEdGraphNode* Node)
{
VisitedNodes.Add(Node);
// Follow every exec output pin
for (int32 i = 0; i < Node->Pins.Num(); ++i)
{
UEdGraphPin* MyPin = Node->Pins[i];
if ((MyPin->Direction == EGPD_Input) && (Schema->IsPosePin(MyPin->PinType)))
{
for (int32 j = 0; j < MyPin->LinkedTo.Num(); ++j)
{
UEdGraphPin* OtherPin = MyPin->LinkedTo[j];
UEdGraphNode* OtherNode = OtherPin->GetOwningNode();
if (!VisitedNodes.Contains(OtherNode))
{
TraverseNodes(OtherNode);
}
}
}
}
}
};
// Prune the nodes that aren't reachable via an animation pose link
FNodeVisitorDownPoseWires Visitor;
for (auto RootIt = RootSet.CreateConstIterator(); RootIt; ++RootIt)
{
UAnimGraphNode_Base* RootNode = *RootIt;
Visitor.TraverseNodes(RootNode);
}
for (int32 NodeIndex = 0; NodeIndex < GraphNodes.Num(); ++NodeIndex)
{
UAnimGraphNode_Base* Node = GraphNodes[NodeIndex];
// We cant prune linked input poses as even if they are not linked to the root, they are needed for the dynamic link phase at runtime
if (!Visitor.VisitedNodes.Contains(Node) && !IsNodePure(Node) && !Node->IsA<UAnimGraphNode_LinkedInputPose>())
{
Node->BreakAllNodeLinks();
GraphNodes.RemoveAtSwap(NodeIndex);
--NodeIndex;
}
}
}
void FAnimBlueprintCompilerContext::ProcessAnimationNodes(TArray<UAnimGraphNode_Base*>& AnimNodeList)
{
BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_ProcessAnimationNodes);
// Process the remaining nodes
for (UAnimGraphNode_Base* AnimNode : AnimNodeList)
{
ProcessAnimationNode(AnimNode);
}
}
void FAnimBlueprintCompilerContext::GetLinkedAnimNodes(UAnimGraphNode_Base* InGraphNode, TArray<UAnimGraphNode_Base*> &LinkedAnimNodes) const
{
for(UEdGraphPin* Pin : InGraphNode->Pins)
{
if(Pin->Direction == EEdGraphPinDirection::EGPD_Input &&
Pin->PinType.PinCategory == TEXT("struct"))
{
if(UScriptStruct* Struct = Cast<UScriptStruct>(Pin->PinType.PinSubCategoryObject.Get()))
{
if(Struct->IsChildOf(FPoseLinkBase::StaticStruct()))
{
GetLinkedAnimNodes_TraversePin(Pin, LinkedAnimNodes);
}
}
}
}
}
void FAnimBlueprintCompilerContext::GetLinkedAnimNodes_TraversePin(UEdGraphPin* InPin, TArray<UAnimGraphNode_Base*>& LinkedAnimNodes) const
{
if(!InPin)
{
return;
}
for(UEdGraphPin* LinkedPin : InPin->LinkedTo)
{
if(!LinkedPin)
{
continue;
}
UEdGraphNode* OwningNode = LinkedPin->GetOwningNode();
if(UK2Node_Knot* InnerKnot = Cast<UK2Node_Knot>(OwningNode))
{
GetLinkedAnimNodes_TraversePin(InnerKnot->GetInputPin(), LinkedAnimNodes);
}
else if(UAnimGraphNode_Base* AnimNode = Cast<UAnimGraphNode_Base>(OwningNode))
{
GetLinkedAnimNodes_ProcessAnimNode(AnimNode, LinkedAnimNodes);
}
}
}
void FAnimBlueprintCompilerContext::GetLinkedAnimNodes_ProcessAnimNode(UAnimGraphNode_Base* AnimNode, TArray<UAnimGraphNode_Base *>& LinkedAnimNodes) const
{
if(!AllocatedAnimNodes.Contains(AnimNode))
{
UAnimGraphNode_Base* TrueSourceNode = MessageLog.FindSourceObjectTypeChecked<UAnimGraphNode_Base>(AnimNode);
if(UAnimGraphNode_Base*const* AllocatedNode = SourceNodeToProcessedNodeMap.Find(TrueSourceNode))
{
LinkedAnimNodes.Add(*AllocatedNode);
}
else
{
FString ErrorString = FText::Format(LOCTEXT("MissingLinkFmt", "Missing allocated node for {0} while searching for node links - likely due to the node having outstanding errors."), FText::FromString(AnimNode->GetName())).ToString();
MessageLog.Error(*ErrorString);
}
}
else
{
LinkedAnimNodes.Add(AnimNode);
}
}
void FAnimBlueprintCompilerContext::ProcessAllAnimationNodes()
{
BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_ProcessAllAnimationNodes);
// Validate that we have a skeleton
if ((AnimBlueprint->TargetSkeleton == nullptr) && !AnimBlueprint->bIsNewlyCreated && !AnimBlueprint->bIsTemplate)
{
MessageLog.Error(*LOCTEXT("NoSkeleton", "@@ - The skeleton asset for this animation Blueprint is missing, so it cannot be compiled!").ToString(), AnimBlueprint);
return;
}
// Build the raw node lists
TArray<UAnimGraphNode_Base*> RootAnimNodeList;
ConsolidatedEventGraph->GetNodesOfClass<UAnimGraphNode_Base>(RootAnimNodeList);
// We recursively build the node lists for pre- and post-processing phases to make sure
// we catch any handler-relevant nodes in sub-graphs
TArray<UAnimGraphNode_Base*> AllSubGraphsAnimNodeList;
ForAllSubGraphs(ConsolidatedEventGraph, [&AllSubGraphsAnimNodeList](UEdGraph* InGraph)
{
InGraph->GetNodesOfClass<UAnimGraphNode_Base>(AllSubGraphsAnimNodeList);
});
// Find the root nodes
TArray<UAnimGraphNode_Base*> RootSet;
AllocateNodeIndexCounter = 0;
for (UAnimGraphNode_Base* SourceNode : RootAnimNodeList)
{
UAnimGraphNode_Base* TrueNode = MessageLog.FindSourceObjectTypeChecked<UAnimGraphNode_Base>(SourceNode);
TrueNode->BlueprintUsage = EBlueprintUsage::NoProperties;
if(SourceNode->IsNodeRootSet())
{
RootSet.Add(SourceNode);
}
}
if (RootAnimNodeList.Num() > 0)
{
// Prune any anim nodes (they will be skipped by PruneIsolatedNodes above)
PruneIsolatedAnimationNodes(RootSet, RootAnimNodeList);
// Validate the graph
ValidateGraphIsWellFormed(ConsolidatedEventGraph);
FAnimBlueprintGeneratedClassCompiledData CompiledData(GetNewAnimBlueprintClass());
FAnimBlueprintCompilationContext CompilerContext(this);
UAnimBlueprintExtension::ForEachExtension(AnimBlueprint, [&AllSubGraphsAnimNodeList, &CompiledData, &CompilerContext](UAnimBlueprintExtension* InExtension)
{
InExtension->PreProcessAnimationNodes(AllSubGraphsAnimNodeList, CompilerContext, CompiledData);
});
// Process the animation nodes
ProcessAnimationNodes(RootAnimNodeList);
// Process any extensions
ProcessExtensions();
// Fold any constants
ProcessFoldedPropertyRecords();
UAnimBlueprintExtension::ForEachExtension(AnimBlueprint, [&AllSubGraphsAnimNodeList, &CompiledData, &CompilerContext](UAnimBlueprintExtension* InExtension)
{
InExtension->PostProcessAnimationNodes(AllSubGraphsAnimNodeList, CompilerContext, CompiledData);
});
}
else
{
MessageLog.Error(*LOCTEXT("ExpectedAFunctionEntry_Error", "Expected at least one animation node, but did not find any").ToString());
}
}
void FAnimBlueprintCompilerContext::CopyTermDefaultsToDefaultObject(UObject* DefaultObject)
{
Super::CopyTermDefaultsToDefaultObject(DefaultObject);
UAnimInstance* DefaultAnimInstance = Cast<UAnimInstance>(DefaultObject);
UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = GetNewAnimBlueprintClass();
if (bIsDerivedAnimBlueprint && DefaultAnimInstance)
{
//Need To have a sparse class data struct for BuildConstantProperties below
if (NewAnimBlueprintClass->GetSparseClassDataStruct() == nullptr)
{
RecreateSparseClassData();
}
else
{
// Ensure we have constant properties & anim node data rebuilt
NewAnimBlueprintClass->BuildConstantProperties();
}
CopyAnimNodeDataFromRoot();
// If we are a derived animation graph; apply any stored overrides.
// Restore values from the root class to catch values where the override has been removed.
UAnimBlueprintGeneratedClass* RootAnimClass = NewAnimBlueprintClass;
while (UAnimBlueprintGeneratedClass* NextClass = Cast<UAnimBlueprintGeneratedClass>(RootAnimClass->GetSuperClass()))
{
RootAnimClass = NextClass;
}
UObject* RootDefaultObject = RootAnimClass->GetDefaultObject();
for (TFieldIterator<FProperty> It(RootAnimClass); It; ++It)
{
FProperty* RootProp = *It;
if (FStructProperty* RootStructProp = CastField<FStructProperty>(RootProp))
{
if (RootStructProp->Struct->IsChildOf(FAnimNode_Base::StaticStruct()))
{
FStructProperty* ChildStructProp = FindFProperty<FStructProperty>(NewAnimBlueprintClass, *RootStructProp->GetName());
check(ChildStructProp);
uint8* SourcePtr = RootStructProp->ContainerPtrToValuePtr<uint8>(RootDefaultObject);
uint8* DestPtr = ChildStructProp->ContainerPtrToValuePtr<uint8>(DefaultAnimInstance);
check(SourcePtr && DestPtr);
RootStructProp->CopyCompleteValue(DestPtr, SourcePtr);
}
}
}
// Copy from root sparse class data to our new class
if(NewAnimBlueprintClass->GetConstantNodeData() && RootAnimClass->GetConstantNodeData())
{
checkf(NewAnimBlueprintClass->GetSparseClassDataStruct()->IsChildOf(RootAnimClass->GetSparseClassDataStruct()), TEXT("SparseClassDataStruct for AnimBPClass '%s' (Struct:%s) is not a child of SparseClassDataStruct for AnimBPClass '%s' (Struct:%s)"), *GetPathNameSafe(NewAnimBlueprintClass), *GetFullNameSafe(NewAnimBlueprintClass->GetSparseClassDataStruct()), *GetPathNameSafe(RootAnimClass), *GetFullNameSafe(RootAnimClass->GetSparseClassDataStruct()));
for (TFieldIterator<FProperty> PropertyIt(RootAnimClass->GetSparseClassDataStruct()); PropertyIt; ++PropertyIt)
{
FProperty* RootProperty = *PropertyIt;
FProperty* ChildProperty = FindFProperty<FProperty>(NewAnimBlueprintClass->GetSparseClassDataStruct(), *RootProperty->GetName());
check(ChildProperty);
const uint8* SourcePtr = RootProperty->ContainerPtrToValuePtr<uint8>(RootAnimClass->GetConstantNodeData());
uint8* DestPtr = const_cast<uint8*>(ChildProperty->ContainerPtrToValuePtr<uint8>(NewAnimBlueprintClass->GetConstantNodeData()));
check(SourcePtr && DestPtr);
RootProperty->CopyCompleteValue(DestPtr, SourcePtr);
}
}
// Copy from root mutable data to our new mutable data
if (NewAnimBlueprintClass->GetMutableNodeData(DefaultObject) && RootAnimClass->GetMutableNodeData(RootDefaultObject))
{
// These properties should have been linked and cached by now
check(RootAnimClass->MutableNodeDataProperty);
check(RootAnimClass->MutableNodeDataProperty->Struct);
check(NewAnimBlueprintClass->MutableNodeDataProperty);
check(NewAnimBlueprintClass->MutableNodeDataProperty->Struct);
for (TFieldIterator<FProperty> PropertyIt(RootAnimClass->MutableNodeDataProperty->Struct); PropertyIt; ++PropertyIt)
{
FProperty* RootProperty = *PropertyIt;
FProperty* ChildProperty = FindFProperty<FProperty>(NewAnimBlueprintClass->MutableNodeDataProperty->Struct, *RootProperty->GetName());
check(ChildProperty != nullptr);
const uint8* SourcePtr = RootProperty->ContainerPtrToValuePtr<uint8>(RootAnimClass->GetMutableNodeData(RootDefaultObject));
uint8* DestPtr = ChildProperty->ContainerPtrToValuePtr<uint8>(NewAnimBlueprintClass->GetMutableNodeData(DefaultObject));
check(SourcePtr != nullptr && DestPtr != nullptr);
RootProperty->CopyCompleteValue(DestPtr, SourcePtr);
}
}
// Re-initialize node data tables (they would be overwritten in the loop above)
NewAnimBlueprintClass->InitializeAnimNodeData(DefaultObject, true);
}
// Give game-specific logic a chance to replace animations
if(DefaultAnimInstance)
{
DefaultAnimInstance->ApplyAnimOverridesToCDO(MessageLog);
}
if (bIsDerivedAnimBlueprint && DefaultAnimInstance)
{
// Patch the overridden values into the CDO
TArray<FAnimParentNodeAssetOverride*> AssetOverrides;
AnimBlueprint->GetAssetOverrides(AssetOverrides);
for (FAnimParentNodeAssetOverride* Override : AssetOverrides)
{
if (Override->NewAsset)
{
int32 NodeIndex = NewAnimBlueprintClass->GetNodeIndexFromGuid(Override->ParentNodeGuid, EPropertySearchMode::Hierarchy);
if(NodeIndex != INDEX_NONE)
{
const UAnimGraphNode_Base* GraphNode = Cast<UAnimGraphNode_Base>(NewAnimBlueprintClass->GetVisualNodeFromNodePropertyIndex(NodeIndex, EPropertySearchMode::Hierarchy));
FAnimNode_Base* BaseNode = NewAnimBlueprintClass->GetPropertyInstance<FAnimNode_Base>(DefaultAnimInstance, Override->ParentNodeGuid, EPropertySearchMode::Hierarchy);
if (GraphNode && BaseNode)
{
FAnimBlueprintNodeOverrideAssetsContext Context(BaseNode, GraphNode->GetFNodeType());
Context.AddAsset(Override->NewAsset);
GraphNode->OverrideAssets(Context);
}
}
}
}
return;
}
if(DefaultAnimInstance)
{
int32 LinkIndexCount = 0;
TMap<UAnimGraphNode_Base*, int32> LinkIndexMap;
TMap<UAnimGraphNode_Base*, uint8*> NodeBaseAddresses;
FAnimBlueprintGeneratedClassCompiledData CompiledData(NewAnimBlueprintClass);
FAnimBlueprintCopyTermDefaultsContext CompilerContext(this);
// Initialize animation nodes from their templates
for (TFieldIterator<FProperty> It(DefaultAnimInstance->GetClass(), EFieldIteratorFlags::ExcludeSuper); It; ++It)
{
FProperty* TargetProperty = *It;
if (UAnimGraphNode_Base* VisualAnimNode = AllocatedNodePropertiesToNodes.FindRef(TargetProperty))
{
const FStructProperty* SourceNodeProperty = VisualAnimNode->GetFNodeProperty();
check(SourceNodeProperty != NULL);
check(CastFieldChecked<FStructProperty>(TargetProperty)->Struct == SourceNodeProperty->Struct);
uint8* DestinationPtr = TargetProperty->ContainerPtrToValuePtr<uint8>(DefaultAnimInstance);
const uint8* SourcePtr = SourceNodeProperty->ContainerPtrToValuePtr<uint8>(VisualAnimNode);
FAnimBlueprintNodeCopyTermDefaultsContext NodeContext(DefaultObject, TargetProperty, DestinationPtr, SourcePtr, LinkIndexCount);
UAnimGraphNode_Base* OriginalAnimNode = Cast<UAnimGraphNode_Base>(MessageLog.FindSourceObject(VisualAnimNode));
OriginalAnimNode->CopyTermDefaultsToDefaultObject(CompilerContext, NodeContext, CompiledData);
LinkIndexMap.Add(VisualAnimNode, LinkIndexCount);
NodeBaseAddresses.Add(VisualAnimNode, DestinationPtr);
++LinkIndexCount;
}
}
// Applies a set of folded property records to a data area (i.e. the constant or mutable structs)
auto PatchDataArea = [](void* InData, UScriptStruct* InStruct, const TArray<TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>>& InRecords)
{
for(const TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>& Record : InRecords)
{
if(Record->GeneratedProperty != nullptr)
{
FStructProperty* AnimGraphNodeProperty = Record->AnimGraphNode->GetFNodeProperty();
// Check the anim node property is contained in the anim graph node
check(AnimGraphNodeProperty->GetOwner<UClass>() && Record->AnimGraphNode->GetClass()->IsChildOf(AnimGraphNodeProperty->GetOwner<UClass>()));
const void* Node = AnimGraphNodeProperty->ContainerPtrToValuePtr<void>(Record->AnimGraphNode);
// Check the anim node's property is contained in the anim node
check(Record->Property->GetOwner<UStruct>() && AnimGraphNodeProperty->Struct->IsChildOf(Record->Property->GetOwner<UStruct>()));
const void* SourcePtr = Record->Property->ContainerPtrToValuePtr<void>(Node);
// Check the generated property is a member of the constants struct
check(Record->GeneratedProperty->GetOwner<UStruct>() && InStruct->IsChildOf(Record->GeneratedProperty->GetOwner<UStruct>()));
void* TargetPtr = Record->GeneratedProperty->ContainerPtrToValuePtr<void>(InData);
// Extract underlying property for enums
FProperty* PropertyToCopyWith = Record->GeneratedProperty;
if(const FEnumProperty* EnumProperty = CastField<const FEnumProperty>(Record->GeneratedProperty))
{
PropertyToCopyWith = EnumProperty->GetUnderlyingProperty();
}
PropertyToCopyWith->CopyCompleteValue(TargetPtr, SourcePtr);
}
}
};
if(void* Constants = NewAnimBlueprintClass->GetOrCreateSparseClassData())
{
UScriptStruct* ConstantsStruct = NewAnimBlueprintClass->GetSparseClassDataStruct();
check(ConstantsStruct == NewAnimBlueprintConstants);
// Initialize extensions from their templates
for (TFieldIterator<FStructProperty> It(ConstantsStruct, EFieldIteratorFlags::ExcludeSuper); It; ++It)
{
FStructProperty* TargetProperty = *It;
if (UAnimBlueprintExtension* Extension = ClassPropertyToExtensionMap.FindRef(TargetProperty))
{
if(const FStructProperty* SourceExtensionProperty = Extension->GetClassDataProperty())
{
check(SourceExtensionProperty != NULL);
check(TargetProperty->Struct == SourceExtensionProperty->Struct);
uint8* DestinationPtr = TargetProperty->ContainerPtrToValuePtr<uint8>(Constants);
const uint8* SourcePtr = SourceExtensionProperty->ContainerPtrToValuePtr<uint8>(Extension);
FAnimBlueprintExtensionCopyTermDefaultsContext NodeContext(DefaultObject, TargetProperty, DestinationPtr, SourcePtr, LinkIndexCount);
Extension->CopyTermDefaultsToSparseClassData(CompilerContext, NodeContext);
}
}
}
// Patch constants
PatchDataArea(Constants, ConstantsStruct, ConstantPropertyRecords);
}
if(NewMutablesProperty)
{
check(NewMutablesProperty->GetOwner<UClass>() && DefaultObject->GetClass()->IsChildOf(NewMutablesProperty->GetOwner<UClass>()));
void* Mutables = NewMutablesProperty->ContainerPtrToValuePtr<void>(DefaultObject);
UScriptStruct* MutablesStruct = NewMutablesProperty->Struct;
check(MutablesStruct == NewAnimBlueprintMutables);
// Patch mutables
PatchDataArea(Mutables, MutablesStruct, MutablePropertyRecords);
}
// Initialize extensions from their templates
for (TFieldIterator<FStructProperty> It(DefaultAnimInstance->GetClass(), EFieldIteratorFlags::ExcludeSuper); It; ++It)
{
FStructProperty* TargetProperty = *It;
if (UAnimBlueprintExtension* Extension = InstancePropertyToExtensionMap.FindRef(TargetProperty))
{
uint8* DestinationPtr = TargetProperty->ContainerPtrToValuePtr<uint8>(DefaultAnimInstance);
const uint8* SourcePtr = nullptr;
const FStructProperty* SourceExtensionProperty = Extension->GetInstanceDataProperty();
if (SourceExtensionProperty)
{
check(TargetProperty->Struct == SourceExtensionProperty->Struct);
SourcePtr = SourceExtensionProperty->ContainerPtrToValuePtr<uint8>(Extension);
}
FAnimBlueprintExtensionCopyTermDefaultsContext ExtensionContext(DefaultObject, TargetProperty, DestinationPtr, SourcePtr, LinkIndexCount);
Extension->CopyTermDefaultsToDefaultObject(DefaultAnimInstance, CompilerContext, ExtensionContext);
}
}
// And wire up node links
for (auto PoseLinkIt = ValidPoseLinkList.CreateIterator(); PoseLinkIt; ++PoseLinkIt)
{
FPoseLinkMappingRecord& Record = *PoseLinkIt;
UAnimGraphNode_Base* LinkingNode = Record.GetLinkingNode();
UAnimGraphNode_Base* LinkedNode = Record.GetLinkedNode();
// @TODO this is quick solution for crash - if there were previous errors and some nodes were not added, they could still end here -
// this check avoids that and since there are already errors, compilation won't be successful.
// but I'd prefer stopping compilation earlier to avoid getting here in first place
if (LinkIndexMap.Contains(LinkingNode) && LinkIndexMap.Contains(LinkedNode))
{
const int32 SourceNodeIndex = LinkIndexMap.FindChecked(LinkingNode);
const int32 LinkedNodeIndex = LinkIndexMap.FindChecked(LinkedNode);
uint8* DestinationPtr = NodeBaseAddresses.FindChecked(LinkingNode);
Record.PatchLinkIndex(DestinationPtr, LinkedNodeIndex, SourceNodeIndex);
}
}
UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = CastChecked<UAnimBlueprintGeneratedClass>(NewClass);
// copy threaded update flag to CDO
DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = AnimBlueprint->bUseMultiThreadedAnimationUpdate;
// Verify thread-safety
if(GetDefault<UEngine>()->bAllowMultiThreadedAnimationUpdate && DefaultAnimInstance->bUseMultiThreadedAnimationUpdate)
{
// If we are a child anim BP, check parent classes & their CDOs
if (UAnimBlueprintGeneratedClass* ParentClass = Cast<UAnimBlueprintGeneratedClass>(AnimBlueprintGeneratedClass->GetSuperClass()))
{
UAnimBlueprint* ParentAnimBlueprint = Cast<UAnimBlueprint>(ParentClass->ClassGeneratedBy);
if (ParentAnimBlueprint && !ParentAnimBlueprint->bUseMultiThreadedAnimationUpdate)
{
DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false;
}
UAnimInstance* ParentDefaultObject = Cast<UAnimInstance>(ParentClass->GetDefaultObject(false));
if (ParentDefaultObject && !ParentDefaultObject->bUseMultiThreadedAnimationUpdate)
{
DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false;
}
}
// iterate all properties to determine validity
for (FStructProperty* Property : TFieldRange<FStructProperty>(AnimBlueprintGeneratedClass, EFieldIteratorFlags::IncludeSuper))
{
if(Property->Struct->IsChildOf(FAnimNode_Base::StaticStruct()))
{
FAnimNode_Base* AnimNode = Property->ContainerPtrToValuePtr<FAnimNode_Base>(DefaultAnimInstance);
if(!AnimNode->CanUpdateInWorkerThread())
{
MessageLog.Warning(*FText::Format(LOCTEXT("HasIncompatibleNode", "Found incompatible node \"{0}\" in blend graph. Disable threaded update or use member variable access."), FText::FromName(Property->Struct->GetFName())).ToString())
->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph")));;
DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false;
}
}
}
if (FunctionList.Num() > 0)
{
// find the ubergraph in the function list
FKismetFunctionContext* UbergraphFunctionContext = nullptr;
for (FKismetFunctionContext& FunctionContext : FunctionList)
{
if (FunctionList[0].Function->GetName().StartsWith(TEXT("ExecuteUbergraph")))
{
UbergraphFunctionContext = &FunctionContext;
break;
}
}
if (UbergraphFunctionContext)
{
// run through the per-node compiled statements looking for struct-sets used by anim nodes
for (const TPair<UEdGraphNode*, TArray<FBlueprintCompiledStatement*>>& StatementPair : UbergraphFunctionContext->StatementsPerNode)
{
if (UK2Node_StructMemberSet* StructMemberSetNode = Cast<UK2Node_StructMemberSet>(StatementPair.Key))
{
UK2Node* SourceNode = CastChecked<UK2Node>(MessageLog.FindSourceObject(StructMemberSetNode));
if (SourceNode && (StructMemberSetNode->StructType->IsChildOf(FAnimNode_Base::StaticStruct()) || StructMemberSetNode->StructType->IsChildOf(FAnimBlueprintMutableData::StaticStruct())))
{
UEdGraph* SourceGraph = SourceNode->GetGraph();
const bool bEmitErrors = false;
bool bIsThreadSafe = true;
static const FBoolConfigValueHelper UseLegacyAnimBlueprintThreadSafetyChecks(TEXT("Kismet"), TEXT("bUseLegacyAnimBlueprintThreadSafetyChecks"), GEngineIni);
if(UseLegacyAnimBlueprintThreadSafetyChecks)
{
for (FBlueprintCompiledStatement* Statement : StatementPair.Value)
{
if (Statement->Type == KCST_CallFunction && Statement->FunctionToCall)
{
// pure function?
const bool bPureFunctionCall = Statement->FunctionToCall->HasAnyFunctionFlags(FUNC_BlueprintPure);
// function called on something other than function library or anim instance?
UClass* FunctionClass = CastChecked<UClass>(Statement->FunctionToCall->GetOuter());
const bool bFunctionLibraryCall = FunctionClass->IsChildOf<UBlueprintFunctionLibrary>();
const bool bAnimInstanceCall = FunctionClass->IsChildOf<UAnimInstance>();
// Allowed/denied? Some functions are not really 'pure', so we give people the opportunity to mark them up.
// Mark up the class if it is generally thread safe, then unsafe functions can be marked up individually. We assume
// that classes are unsafe by default, as well as if they are marked up NotBlueprintThreadSafe.
const bool bClassThreadSafe = FunctionClass->HasMetaData(TEXT("BlueprintThreadSafe"));
const bool bClassNotThreadSafe = FunctionClass->HasMetaData(TEXT("NotBlueprintThreadSafe")) || !FunctionClass->HasMetaData(TEXT("BlueprintThreadSafe"));
const bool bFunctionThreadSafe = Statement->FunctionToCall->HasMetaData(TEXT("BlueprintThreadSafe"));
const bool bFunctionNotThreadSafe = Statement->FunctionToCall->HasMetaData(TEXT("NotBlueprintThreadSafe"));
const bool bThreadSafe = (bClassThreadSafe && !bFunctionNotThreadSafe) || (bClassNotThreadSafe && bFunctionThreadSafe);
const bool bValidForUsage = bPureFunctionCall && bThreadSafe && (bFunctionLibraryCall || bAnimInstanceCall);
if (!bValidForUsage)
{
UEdGraphNode* FunctionNode = nullptr;
if (Statement->FunctionContext && Statement->FunctionContext->SourcePin)
{
FunctionNode = Statement->FunctionContext->SourcePin->GetOwningNode();
}
else if (Statement->LHS && Statement->LHS->SourcePin)
{
FunctionNode = Statement->LHS->SourcePin->GetOwningNode();
}
if (FunctionNode)
{
MessageLog.Warning(*LOCTEXT("NotThreadSafeWarningNodeContext", "Node @@ uses potentially thread-unsafe call @@. Disable threaded update or use a thread-safe call. Function may need BlueprintThreadSafe metadata adding.").ToString(), SourceNode, FunctionNode)
->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph")));
}
else if (Statement->FunctionToCall)
{
MessageLog.Warning(*FText::Format(LOCTEXT("NotThreadSafeWarningFunctionContext", "Node @@ uses potentially thread-unsafe call {0}. Disable threaded update or use a thread-safe call. Function may need BlueprintThreadSafe metadata adding."), Statement->FunctionToCall->GetDisplayNameText()).ToString(), SourceNode)
->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph")));
}
else
{
MessageLog.Warning(*LOCTEXT("NotThreadSafeWarningUnknownContext", "Node @@ uses potentially thread-unsafe call. Disable threaded update or use a thread-safe call.").ToString(), SourceNode)
->AddToken(FDocumentationToken::Create(TEXT("Engine/Animation/AnimBlueprints/AnimGraph")));
}
bIsThreadSafe = false;
}
}
}
}
else
{
bIsThreadSafe = FKismetCompilerUtilities::CheckFunctionCompiledStatementsThreadSafety(SourceNode, SourceGraph, StatementPair.Value, MessageLog, bEmitErrors);
}
DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = bIsThreadSafe;
}
}
}
}
}
}
}
}
void FAnimBlueprintCompilerContext::ExpandSplitPins(UEdGraph* InGraph)
{
BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_ExpandSplitPins);
for (auto NodeIt = InGraph->Nodes.CreateIterator(); NodeIt; ++NodeIt)
{
UK2Node* K2Node = Cast<UK2Node>(*NodeIt);
if (K2Node != nullptr)
{
K2Node->ExpandSplitPins(*this, InGraph);
}
}
}
// Merges in any all ubergraph pages into the gathering ubergraph
void FAnimBlueprintCompilerContext::MergeUbergraphPagesIn(UEdGraph* Ubergraph)
{
BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_MergeUbergraphPagesIn);
Super::MergeUbergraphPagesIn(Ubergraph);
if (bIsDerivedAnimBlueprint)
{
// Skip any work related to an anim graph, it's all done by the parent class
// We do need to make sure that anim node data is correctly copied & remapped to this class
RecreateSparseClassData();
CopyAnimNodeDataFromRoot();
}
else
{
RecreateSparseClassData();
RecreateMutables();
{
BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_MoveGraphs);
// Move all animation graph nodes and associated pure logic chains into the consolidated event graph
auto MoveGraph = [this](UEdGraph* InGraph)
{
if (InGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass()))
{
UEdGraph* ClonedGraph;
{
BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_CloneGraph);
// Merge all the animation nodes, contents, etc... into the ubergraph
ClonedGraph = FEdGraphUtilities::CloneGraph(InGraph, NULL, &MessageLog, true);
}
// Prune the graph up-front
const bool bIncludePotentialRootNodes = false;
PruneIsolatedNodes(ClonedGraph, bIncludePotentialRootNodes);
const bool bIsLoading = Blueprint->bIsRegeneratingOnLoad || IsAsyncLoading();
const bool bIsCompiling = Blueprint->bBeingCompiled;
ClonedGraph->MoveNodesToAnotherGraph(ConsolidatedEventGraph, bIsLoading, bIsCompiling);
// Move subgraphs too
ConsolidatedEventGraph->SubGraphs.Append(ClonedGraph->SubGraphs);
}
};
for (UEdGraph* Graph : Blueprint->FunctionGraphs)
{
MoveGraph(Graph);
}
for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
for(UEdGraph* Graph : InterfaceDesc.Graphs)
{
MoveGraph(Graph);
}
}
}
// Make sure we expand any split pins here before we process animation nodes.
ForAllSubGraphs(ConsolidatedEventGraph, [this](UEdGraph* InGraph)
{
ExpandSplitPins(InGraph);
});
// Compile the animation graph
ProcessAllAnimationNodes();
}
}
void FAnimBlueprintCompilerContext::CopyAnimNodeDataFromRoot() const
{
check(bIsDerivedAnimBlueprint);
UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = GetNewAnimBlueprintClass();
UAnimBlueprintGeneratedClass* RootAnimClass = NewAnimBlueprintClass;
while (UAnimBlueprintGeneratedClass* NextClass = Cast<UAnimBlueprintGeneratedClass>(RootAnimClass->GetSuperClass()))
{
RootAnimClass = NextClass;
}
// Copy constant/folded data from root class, remapping the class
NewAnimBlueprintClass->AnimNodeData = RootAnimClass->AnimNodeData;
NewAnimBlueprintClass->NodeTypeMap = RootAnimClass->NodeTypeMap;
for(FAnimNodeData& NodeData : NewAnimBlueprintClass->AnimNodeData)
{
NodeData.AnimClassInterface = NewAnimBlueprintClass;
}
}
void FAnimBlueprintCompilerContext::ProcessOneFunctionGraph(UEdGraph* SourceGraph, bool bInternalFunction)
{
if(!KnownGraphSchemas.FindByPredicate([SourceGraph](const TSubclassOf<UEdGraphSchema>& InSchemaClass)
{
return SourceGraph->Schema->IsChildOf(InSchemaClass.Get());
}))
{
// Not known as a schema that this compiler looks at, pass to the default
Super::ProcessOneFunctionGraph(SourceGraph, bInternalFunction);
}
}
void FAnimBlueprintCompilerContext::EnsureProperGeneratedClass(UClass*& TargetUClass)
{
if( TargetUClass && !((UObject*)TargetUClass)->IsA(UAnimBlueprintGeneratedClass::StaticClass()) )
{
FKismetCompilerUtilities::ConsignToOblivion(TargetUClass, Blueprint->bIsRegeneratingOnLoad);
TargetUClass = NULL;
}
}
void FAnimBlueprintCompilerContext::RecreateSparseClassData()
{
UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = GetNewAnimBlueprintClass();
// Set up our sparse class data struct
if (bIsDerivedAnimBlueprint)
{
// Get parent class
UAnimBlueprint* ParentAnimBP = UAnimBlueprint::GetParentAnimBlueprint(AnimBlueprint);
UAnimBlueprintGeneratedClass* ParentAnimClass = Cast<UAnimBlueprintGeneratedClass>(ParentAnimBP->GeneratedClass);
check(ParentAnimClass);
check(ParentAnimClass->GetSparseClassDataStruct());
// Derive sparse class data from parent class
NewAnimBlueprintConstants = NewObject<UScriptStruct>(NewAnimBlueprintClass, UAnimBlueprintGeneratedClass::GetConstantsStructName(), RF_Public);
NewAnimBlueprintConstants->SetSuperStruct(ParentAnimClass->GetSparseClassDataStruct());
// Just link & assign sparse class data struct here, no additional members are added
NewAnimBlueprintConstants->StaticLink(true);
NewAnimBlueprintClass->SetSparseClassDataStruct(NewAnimBlueprintConstants);
NewAnimBlueprintClass->GetOrCreateSparseClassData();
NewAnimBlueprintClass->BuildConstantProperties();
}
else
{
UClass* ParentClass = NewAnimBlueprintClass->GetSuperClass();
check(ParentClass);
// Create new sparse class data struct
NewAnimBlueprintConstants = NewObject<UScriptStruct>(NewAnimBlueprintClass, UAnimBlueprintGeneratedClass::GetConstantsStructName(), RF_Public);
// Inherit from archetype struct if there is any
UScriptStruct* ParentStruct = ParentClass->GetSparseClassDataStruct();
UScriptStruct* SuperStruct = ParentStruct ? ParentStruct : FAnimBlueprintConstantData::StaticStruct();
NewAnimBlueprintConstants->SetSuperStruct(SuperStruct);
}
if(OldSparseClassDataStruct)
{
FLinkerLoad::PRIVATE_PatchNewObjectIntoExport(OldSparseClassDataStruct, NewAnimBlueprintConstants);
}
}
void FAnimBlueprintCompilerContext::RecreateMutables()
{
UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = GetNewAnimBlueprintClass();
UScriptStruct* OldMutablesStruct = FindObject<UScriptStruct>(NewAnimBlueprintClass, *UAnimBlueprintGeneratedClass::GetMutablesStructName().ToString());
// Set up our mutables struct
NewAnimBlueprintMutables = NewObject<UScriptStruct>(NewAnimBlueprintClass, UAnimBlueprintGeneratedClass::GetMutablesStructName(), RF_Public);
NewAnimBlueprintMutables->SetSuperStruct(FAnimBlueprintMutableData::StaticStruct());
if(OldMutablesStruct)
{
FLinkerLoad::PRIVATE_PatchNewObjectIntoExport(OldMutablesStruct, NewAnimBlueprintMutables);
}
}
void FAnimBlueprintCompilerContext::SpawnNewClass(const FString& NewClassName)
{
NewClass = FindObject<UAnimBlueprintGeneratedClass>(Blueprint->GetOutermost(), *NewClassName);
if (NewClass == NULL)
{
NewClass = NewObject<UAnimBlueprintGeneratedClass>(Blueprint->GetOutermost(), *NewClassName, RF_Public | RF_Transactional);
}
else
{
// Already existed, but wasn't linked in the Blueprint yet due to load ordering issues
FBlueprintCompileReinstancer::Create(NewClass);
}
FAnimBlueprintGeneratedClassCompiledData CompiledData(GetNewAnimBlueprintClass());
FAnimBlueprintCompilationBracketContext CompilerContext(this);
UAnimBlueprintExtension::ForEachExtension(AnimBlueprint, [this, &CompiledData, &CompilerContext](UAnimBlueprintExtension* InExtension)
{
InExtension->StartCompilingClass(GetNewAnimBlueprintClass(), CompilerContext, CompiledData);
});
}
void FAnimBlueprintCompilerContext::OnPostCDOCompiled(const UObject::FPostCDOCompiledContext& Context)
{
if (Context.bIsSkeletonOnly)
{
return;
}
UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = GetNewAnimBlueprintClass();
NewAnimBlueprintClass->OnPostLoadDefaults(NewAnimBlueprintClass->GetDefaultObject(false));
}
void FAnimBlueprintCompilerContext::OnNewClassSet(UBlueprintGeneratedClass* ClassToUse)
{
NewClass = CastChecked<UAnimBlueprintGeneratedClass>(ClassToUse);
}
void FAnimBlueprintCompilerContext::CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOldCDO)
{
UAnimBlueprintGeneratedClass* AnimBlueprintClassToClean = CastChecked<UAnimBlueprintGeneratedClass>(ClassToClean);
UScriptStruct* CurrentSparseClassDataStruct = AnimBlueprintClassToClean->GetSparseClassDataStruct();
if(CurrentSparseClassDataStruct && CurrentSparseClassDataStruct->GetOuter() == AnimBlueprintClassToClean)
{
// Only 'clear' (which renames the struct aside) if we own the sparse class data
// We do this because this could be a parent classes struct and we dont want to be
// altering other classes during compilation
AnimBlueprintClassToClean->ClearSparseClassDataStruct(Blueprint->bIsRegeneratingOnLoad);
}
else
{
AnimBlueprintClassToClean->SetSparseClassDataStruct(nullptr);
}
// Calling super will set this classes superclass, which will reset its sparse class data to the parent (archetype)
Super::CleanAndSanitizeClass(ClassToClean, InOldCDO);
// Clear reference to archetype sparse class data (set in the super call above), we will be regenerating it
AnimBlueprintClassToClean->SetSparseClassDataStruct(nullptr);
AnimBlueprintClassToClean->AnimBlueprintDebugData = FAnimBlueprintDebugData();
// Reset the baked data
//@TODO: Move this into PurgeClass
AnimBlueprintClassToClean->BakedStateMachines.Empty();
AnimBlueprintClassToClean->AnimNotifies.Empty();
AnimBlueprintClassToClean->AnimBlueprintFunctions.Empty();
AnimBlueprintClassToClean->OrderedSavedPoseIndicesMap.Empty();
AnimBlueprintClassToClean->AnimNodeProperties.Empty();
AnimBlueprintClassToClean->LinkedAnimGraphNodeProperties.Empty();
AnimBlueprintClassToClean->LinkedAnimLayerNodeProperties.Empty();
AnimBlueprintClassToClean->PreUpdateNodeProperties.Empty();
AnimBlueprintClassToClean->DynamicResetNodeProperties.Empty();
AnimBlueprintClassToClean->StateMachineNodeProperties.Empty();
AnimBlueprintClassToClean->InitializationNodeProperties.Empty();
AnimBlueprintClassToClean->GraphAssetPlayerInformation.Empty();
AnimBlueprintClassToClean->GraphBlendOptions.Empty();
AnimBlueprintClassToClean->AnimNodeData.Empty();
AnimBlueprintClassToClean->NodeTypeMap.Empty();
// Copy over runtime data from the blueprint to the class
AnimBlueprintClassToClean->TargetSkeleton = AnimBlueprint->TargetSkeleton;
UAnimBlueprint* RootAnimBP = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint);
bIsDerivedAnimBlueprint = RootAnimBP != NULL;
FAnimBlueprintGeneratedClassCompiledData CompiledData(AnimBlueprintClassToClean);
FAnimBlueprintCompilationBracketContext CompilerContext(this);
UAnimBlueprintExtension::ForEachExtension(AnimBlueprint, [this, &CompiledData, &CompilerContext, AnimBlueprintClassToClean](UAnimBlueprintExtension* InExtension)
{
InExtension->StartCompilingClass(AnimBlueprintClassToClean, CompilerContext, CompiledData);
});
}
void FAnimBlueprintCompilerContext::FinishCompilingClass(UClass* Class)
{
const UAnimBlueprint* PossibleRoot = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint);
const UAnimBlueprint* Src = PossibleRoot ? PossibleRoot : AnimBlueprint;
UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = CastChecked<UAnimBlueprintGeneratedClass>(Class);
AnimBlueprintGeneratedClass->SyncGroupNames.Reset();
AnimBlueprintGeneratedClass->SyncGroupNames.Reserve(Src->Groups.Num());
for (const FAnimGroupInfo& GroupInfo : Src->Groups)
{
AnimBlueprintGeneratedClass->SyncGroupNames.Add(GroupInfo.Name);
}
// Add graph blend options to class if blend values were actually customized
auto AddBlendOptions = [AnimBlueprintGeneratedClass](UEdGraph* Graph)
{
UAnimationGraph* AnimGraph = Cast<UAnimationGraph>(Graph);
if (AnimGraph && (AnimGraph->BlendOptions.BlendInTime >= 0.0f || AnimGraph->BlendOptions.BlendOutTime >= 0.0f))
{
AnimBlueprintGeneratedClass->GraphBlendOptions.Add(AnimGraph->GetFName(), AnimGraph->BlendOptions);
}
};
for (UEdGraph* Graph : Blueprint->FunctionGraphs)
{
AddBlendOptions(Graph);
}
for (FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
if (InterfaceDesc.Interface != nullptr && InterfaceDesc.Interface->IsChildOf<UAnimLayerInterface>())
{
for (UEdGraph* Graph : InterfaceDesc.Graphs)
{
AddBlendOptions(Graph);
}
}
}
FAnimBlueprintGeneratedClassCompiledData CompiledData(GetNewAnimBlueprintClass());
FAnimBlueprintCompilationBracketContext CompilerContext(this);
UAnimBlueprintExtension::ForEachExtension(AnimBlueprint, [this, &CompiledData, &CompilerContext](UAnimBlueprintExtension* InExtension)
{
InExtension->FinishCompilingClass(GetNewAnimBlueprintClass(), CompilerContext, CompiledData);
});
Super::FinishCompilingClass(Class);
}
void FAnimBlueprintCompilerContext::PostCompile()
{
Super::PostCompile();
for (UPoseWatch* PoseWatch : AnimBlueprint->PoseWatches)
{
AnimationEditorUtils::SetPoseWatch(PoseWatch, AnimBlueprint);
}
UAnimBlueprintGeneratedClass* AnimBlueprintGeneratedClass = CastChecked<UAnimBlueprintGeneratedClass>(NewClass);
if(UAnimInstance* DefaultAnimInstance = Cast<UAnimInstance>(AnimBlueprintGeneratedClass->GetDefaultObject()))
{
// iterate all anim node and call PostCompile
if(const USkeleton* CurrentSkeleton = AnimBlueprint->TargetSkeleton)
{
for (FStructProperty* Property : TFieldRange<FStructProperty>(AnimBlueprintGeneratedClass, EFieldIteratorFlags::IncludeSuper))
{
if (Property->Struct->IsChildOf(FAnimNode_Base::StaticStruct()))
{
FAnimNode_Base* AnimNode = Property->ContainerPtrToValuePtr<FAnimNode_Base>(DefaultAnimInstance);
AnimNode->PostCompile(CurrentSkeleton);
}
}
}
}
}
void FAnimBlueprintCompilerContext::PostCompileDiagnostics()
{
FKismetCompilerContext::PostCompileDiagnostics();
UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = GetNewAnimBlueprintClass();
#if WITH_EDITORONLY_DATA // ANIMINST_PostCompileValidation
// See if AnimInstance implements a PostCompileValidation Class.
// If so, instantiate it, and let it perform Validation of our newly compiled AnimBlueprint.
if (const UAnimInstance* const DefaultAnimInstance = Cast<UAnimInstance>(NewAnimBlueprintClass->GetDefaultObject()))
{
if (DefaultAnimInstance->PostCompileValidationClassName.IsValid())
{
UClass* PostCompileValidationClass = LoadClass<UObject>(nullptr, *DefaultAnimInstance->PostCompileValidationClassName.ToString());
if (PostCompileValidationClass)
{
UAnimBlueprintPostCompileValidation* PostCompileValidation = NewObject<UAnimBlueprintPostCompileValidation>(GetTransientPackage(), PostCompileValidationClass);
if (PostCompileValidation)
{
FAnimBPCompileValidationParams PCV_Params(DefaultAnimInstance, NewAnimBlueprintClass, MessageLog, AllocatedNodePropertiesToNodes);
PostCompileValidation->DoPostCompileValidation(PCV_Params);
}
}
}
}
#endif // WITH_EDITORONLY_DATA
if (!bIsDerivedAnimBlueprint)
{
bool bUsingCopyPoseFromMesh = false;
// Run thru all nodes and make sure they like the final results
for (auto NodeIt = AllocatedAnimNodeIndices.CreateConstIterator(); NodeIt; ++NodeIt)
{
if (UAnimGraphNode_Base* Node = NodeIt.Key())
{
Node->ValidateAnimNodePostCompile(MessageLog, NewAnimBlueprintClass, NodeIt.Value());
bUsingCopyPoseFromMesh = bUsingCopyPoseFromMesh || Node->UsingCopyPoseFromMesh();
}
}
// Update CDO
if (UAnimInstance* const DefaultAnimInstance = Cast<UAnimInstance>(NewAnimBlueprintClass->GetDefaultObject()))
{
DefaultAnimInstance->bUsingCopyPoseFromMesh = bUsingCopyPoseFromMesh;
}
}
}
void FAnimBlueprintCompilerContext::CreateAnimGraphStubFunctions()
{
TArray<UEdGraph*> NewGraphs;
auto CreateStubForGraph = [this, &NewGraphs](UEdGraph* InGraph)
{
if(InGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass()))
{
// Check to see if we are implementing an interface, and if so, use the signature from that graph instead
// as we may not have yet been conformed to it (it happens later in compilation)
UEdGraph* GraphToUseforSignature = InGraph;
for(const FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
UClass* InterfaceClass = InterfaceDesc.Interface;
if(InterfaceClass)
{
if(UAnimBlueprint* InterfaceAnimBlueprint = Cast<UAnimBlueprint>(InterfaceClass->ClassGeneratedBy))
{
TArray<UEdGraph*> AllGraphs;
InterfaceAnimBlueprint->GetAllGraphs(AllGraphs);
UEdGraph** FoundSourceGraph = AllGraphs.FindByPredicate([InGraph](UEdGraph* InGraphToCheck){ return InGraphToCheck->GetFName() == InGraph->GetFName(); });
if(FoundSourceGraph)
{
GraphToUseforSignature = *FoundSourceGraph;
break;
}
}
}
}
// Find the root and linked input pose nodes
TArray<UAnimGraphNode_Root*> Roots;
GraphToUseforSignature->GetNodesOfClass(Roots);
TArray<UAnimGraphNode_LinkedInputPose*> LinkedInputPoseNodes;
GraphToUseforSignature->GetNodesOfClass(LinkedInputPoseNodes);
if(Roots.Num() > 0)
{
UAnimGraphNode_Root* RootNode = Roots[0];
// Make sure there was only one root node
for (int32 RootIndex = 1; RootIndex < Roots.Num(); ++RootIndex)
{
MessageLog.Error(
*LOCTEXT("ExpectedOneRoot_Error", "Expected only one root node in graph @@, but found both @@ and @@").ToString(),
InGraph,
RootNode,
Roots[RootIndex]
);
}
// Verify no duplicate inputs
for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode0 : LinkedInputPoseNodes)
{
for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode1 : LinkedInputPoseNodes)
{
if(LinkedInputPoseNode0 != LinkedInputPoseNode1)
{
if(LinkedInputPoseNode0->Node.Name == LinkedInputPoseNode1->Node.Name)
{
MessageLog.Error(
*LOCTEXT("DuplicateInputNode_Error", "Found duplicate input node @@ in graph @@").ToString(),
LinkedInputPoseNode1,
InGraph
);
}
}
}
}
// Create a simple generated graph for our anim 'function'. Decorate it to avoid naming conflicts with the original graph.
FName NewGraphName(*(GraphToUseforSignature->GetName() + ANIM_FUNC_DECORATOR));
UEdGraph* StubGraph = NewObject<UEdGraph>(Blueprint, NewGraphName);
NewGraphs.Add(StubGraph);
StubGraph->Schema = UEdGraphSchema_K2::StaticClass();
StubGraph->SetFlags(RF_Transient);
// Add an entry node
UK2Node_FunctionEntry* EntryNode = SpawnIntermediateNode<UK2Node_FunctionEntry>(RootNode, StubGraph);
EntryNode->NodePosX = -200;
EntryNode->CustomGeneratedFunctionName = GraphToUseforSignature->GetFName(); // Note that the function generated from this temporary graph is undecorated
const bool bIsDefaultAnimGraph = GraphToUseforSignature->GetFName() == UEdGraphSchema_K2::GN_AnimGraph;
EntryNode->MetaData.Category = ((RootNode->Node.GetGroup() == NAME_None) || (bIsDefaultAnimGraph && RootNode->Node.GetGroup() == FAnimNode_Root::DefaultSharedGroup))
? FText::GetEmpty()
: FText::FromName(RootNode->Node.GetGroup());
// Add linked input poses as parameters
for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode : LinkedInputPoseNodes)
{
// Add user defined pins for each linked input pose
TSharedPtr<FUserPinInfo> PosePinInfo = MakeShared<FUserPinInfo>();
PosePinInfo->PinType = UAnimationGraphSchema::MakeLocalSpacePosePin();
PosePinInfo->PinName = LinkedInputPoseNode->Node.Name;
PosePinInfo->DesiredPinDirection = EGPD_Output;
EntryNode->UserDefinedPins.Add(PosePinInfo);
// Add user defined pins for each linked input pose parameter
for(UEdGraphPin* LinkedInputPoseNodePin : LinkedInputPoseNode->Pins)
{
if(!LinkedInputPoseNodePin->bOrphanedPin && LinkedInputPoseNodePin->Direction == EGPD_Output && !UAnimationGraphSchema::IsPosePin(LinkedInputPoseNodePin->PinType))
{
TSharedPtr<FUserPinInfo> ParameterPinInfo = MakeShared<FUserPinInfo>();
ParameterPinInfo->PinType = LinkedInputPoseNodePin->PinType;
ParameterPinInfo->PinName = LinkedInputPoseNodePin->PinName;
ParameterPinInfo->DesiredPinDirection = EGPD_Output;
EntryNode->UserDefinedPins.Add(ParameterPinInfo);
}
}
}
EntryNode->AllocateDefaultPins();
UEdGraphPin* EntryExecPin = EntryNode->FindPinChecked(UEdGraphSchema_K2::PN_Then, EGPD_Output);
UK2Node_FunctionResult* ResultNode = SpawnIntermediateNode<UK2Node_FunctionResult>(RootNode, StubGraph);
ResultNode->NodePosX = 200;
// Add root as the 'return value'
TSharedPtr<FUserPinInfo> PinInfo = MakeShared<FUserPinInfo>();
PinInfo->PinType = UAnimationGraphSchema::MakeLocalSpacePosePin();
PinInfo->PinName = GraphToUseforSignature->GetFName();
PinInfo->DesiredPinDirection = EGPD_Input;
ResultNode->UserDefinedPins.Add(PinInfo);
ResultNode->AllocateDefaultPins();
UEdGraphPin* ResultExecPin = ResultNode->FindPinChecked(UEdGraphSchema_K2::PN_Execute, EGPD_Input);
// Link up entry to exit
EntryExecPin->MakeLinkTo(ResultExecPin);
}
else
{
MessageLog.Error(*LOCTEXT("NoRootNodeFound_Error", "Could not find a root node for the graph @@").ToString(), InGraph);
}
}
};
for(UEdGraph* Graph : Blueprint->FunctionGraphs)
{
CreateStubForGraph(Graph);
}
for(FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
for(UEdGraph* Graph : InterfaceDesc.Graphs)
{
CreateStubForGraph(Graph);
}
}
Blueprint->FunctionGraphs.Append(NewGraphs);
GeneratedStubGraphs.Append(NewGraphs);
}
void FAnimBlueprintCompilerContext::DestroyAnimGraphStubFunctions()
{
Blueprint->FunctionGraphs.RemoveAll([this](UEdGraph* InGraph)
{
return GeneratedStubGraphs.Contains(InGraph);
});
GeneratedStubGraphs.Empty();
}
void FAnimBlueprintCompilerContext::PrecompileFunction(FKismetFunctionContext& Context, EInternalCompilerFlags InternalFlags)
{
Super::PrecompileFunction(Context, InternalFlags);
if(Context.Function)
{
auto CompareEntryPointName =
[Function = Context.Function](UEdGraph* InGraph)
{
if(InGraph)
{
TArray<UK2Node_FunctionEntry*> EntryPoints;
InGraph->GetNodesOfClass<UK2Node_FunctionEntry>(EntryPoints);
if(EntryPoints.Num() == 1 && EntryPoints[0])
{
return EntryPoints[0]->CustomGeneratedFunctionName == Function->GetFName();
}
}
return true;
};
if(GeneratedStubGraphs.ContainsByPredicate(CompareEntryPointName))
{
Context.Function->SetMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly, TEXT("true"));
Context.Function->SetMetaData(FBlueprintMetadata::MD_AnimBlueprintFunction, TEXT("true"));
}
}
}
void FAnimBlueprintCompilerContext::SetCalculatedMetaDataAndFlags(UFunction* Function, UK2Node_FunctionEntry* EntryNode, const UEdGraphSchema_K2* K2Schema)
{
Super::SetCalculatedMetaDataAndFlags(Function, EntryNode, K2Schema);
if(Function)
{
auto CompareEntryPointName =
[Function](UEdGraph* InGraph)
{
if(InGraph)
{
TArray<UK2Node_FunctionEntry*> EntryPoints;
InGraph->GetNodesOfClass<UK2Node_FunctionEntry>(EntryPoints);
if(EntryPoints.Num() == 1 && EntryPoints[0])
{
return EntryPoints[0]->CustomGeneratedFunctionName == Function->GetFName();
}
}
return true;
};
// Match by name to generated graph's entry points
if(GeneratedStubGraphs.ContainsByPredicate(CompareEntryPointName))
{
Function->SetMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly, TEXT("true"));
Function->SetMetaData(FBlueprintMetadata::MD_AnimBlueprintFunction, TEXT("true"));
}
}
}
void FAnimBlueprintCompilerContext::AddAttributesToNode(UAnimGraphNode_Base* InNode, TArrayView<const FName> InAttributes) const
{
if(UAnimGraphNode_Base* OriginalNode = CastChecked<UAnimGraphNode_Base>(MessageLog.FindSourceObject(InNode)))
{
TArray<FName>& AttributeSet = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().NodeAttributes.FindOrAdd(OriginalNode);
AttributeSet.Reserve(AttributeSet.Num() + InAttributes.Num());
for(const FName& Attribute : InAttributes)
{
AttributeSet.AddUnique(Attribute);
}
}
}
TArrayView<const FName> FAnimBlueprintCompilerContext::GetAttributesFromNode(UAnimGraphNode_Base* InNode) const
{
if(UAnimGraphNode_Base* OriginalNode = CastChecked<UAnimGraphNode_Base>(MessageLog.FindSourceObject(InNode)))
{
if(const TArray<FName>* AttributeSetPtr = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().NodeAttributes.Find(OriginalNode))
{
return MakeArrayView(*AttributeSetPtr);
}
}
return TArrayView<const FName>();
}
FProperty* FAnimBlueprintCompilerContext::CreateUniqueVariable(UObject* InForObject, const FEdGraphPinType& Type)
{
const FString VariableName = ClassScopeNetNameMap.MakeValidName(InForObject);
FProperty* Variable = CreateVariable(*VariableName, Type);
Variable->SetMetaData(FBlueprintMetadata::MD_Private, TEXT("true"));
return Variable;
}
FProperty* FAnimBlueprintCompilerContext::CreateStructVariable(UScriptStruct* InStruct, const FName VarName, const FEdGraphPinType& VarType)
{
FProperty* NewProperty = FKismetCompilerUtilities::CreatePropertyOnScope(InStruct, VarName, VarType, nullptr, CPF_None, Schema, MessageLog);
if (NewProperty != nullptr)
{
// This fixes a rare bug involving asynchronous loading of BPs in editor builds. The pattern was established
// in FKismetCompilerContext::CompileFunctions where we do this for the uber graph function. By setting
// the RF_LoadCompleted we prevent the linker from overwriting our regenerated property, although the
// circumstances under which this occurs are murky. More testing of BPs loading asynchronously in the editor
// needs to be added:
NewProperty->SetFlags(RF_LoadCompleted);
FKismetCompilerUtilities::LinkAddedProperty(InStruct, NewProperty);
}
else
{
MessageLog.Error(
*FText::Format(
LOCTEXT("VariableInvalidType_ErrorFmt", "The variable {0} declared in @@ has an invalid type {1}"),
FText::FromName(VarName),
UEdGraphSchema_K2::TypeToText(VarType)
).ToString(),
Blueprint
);
}
return NewProperty;
}
void FAnimBlueprintCompilerContext::AddFoldedPropertyRecord(UAnimGraphNode_Base* InAnimGraphNode, FStructProperty* InAnimNodeProperty, FProperty* InProperty, bool bInExposedOnPin, bool bInPinConnected, bool bInAlwaysDynamic)
{
const bool bConstant = !bInAlwaysDynamic && (!bInExposedOnPin || (bInExposedOnPin && !bInPinConnected));
if(!InProperty->HasAnyPropertyFlags(CPF_EditorOnly))
{
MessageLog.Warning(*FString::Printf(TEXT("Property %s on @@ is foldable, but not editor only"), *InProperty->GetName()), InAnimGraphNode);
}
// Create record and add it our lookup map
TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord> Record = MakeShared<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>(InAnimGraphNode, InAnimNodeProperty, InProperty, bConstant);
TArray<TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>>& Array = NodeToFoldedPropertyRecordMap.FindOrAdd(InAnimGraphNode);
Array.Add(Record);
// Record it in the appropriate data area
if(bConstant)
{
ConstantPropertyRecords.Add(Record);
}
else
{
MutablePropertyRecords.Add(Record);
}
}
void FAnimBlueprintCompilerContext::ProcessFoldedPropertyRecords()
{
UAnimBlueprintGeneratedClass* NewAnimBlueprintClass = GetNewAnimBlueprintClass();
if(ConstantPropertyRecords.Num() > 0)
{
// Set constants struct as sparse class data
check(NewAnimBlueprintConstants);
auto GetRecordValue = [](const TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>& InRecord)
{
FStructProperty* AnimGraphNodeProperty = InRecord->AnimGraphNode->GetFNodeProperty();
check(AnimGraphNodeProperty->GetOwner<UClass>() && InRecord->AnimGraphNode->GetClass()->IsChildOf(AnimGraphNodeProperty->GetOwner<UClass>()));
const void* Node = AnimGraphNodeProperty->ContainerPtrToValuePtr<void>(InRecord->AnimGraphNode);
check(InRecord->Property->GetOwner<UStruct>() && AnimGraphNodeProperty->Struct->IsChildOf(InRecord->Property->GetOwner<UStruct>()));
return InRecord->Property->ContainerPtrToValuePtr<void>(Node);
};
// Reduce any constant properties before patching
for(int32 RecordIndex0 = 0; RecordIndex0 < ConstantPropertyRecords.Num(); ++RecordIndex0)
{
const TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>& Record0 = ConstantPropertyRecords[RecordIndex0];
if(Record0->FoldIndex == INDEX_NONE)
{
const void* Value0 = GetRecordValue(Record0);
for(int32 RecordIndex1 = RecordIndex0 + 1; RecordIndex1 < ConstantPropertyRecords.Num(); ++RecordIndex1)
{
const TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>& Record1 = ConstantPropertyRecords[RecordIndex1];
if(Record1->FoldIndex == INDEX_NONE)
{
if(Record1->Property->SameType(Record0->Property))
{
const void* Value1 = GetRecordValue(Record1);
// same type, now test for equality
if(Record0->Property->Identical(Value0, Value1))
{
// Values are the same - fold
Record1->FoldIndex = RecordIndex0;
}
}
}
}
}
}
}
// Builds a 'data area', returns the total number of properties that were inserted into that area's struct
auto BuildDataArea = [this, NewAnimBlueprintClass](TArray<TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>>& InRecords, UScriptStruct* InStruct)
{
int32 PropertyIndex = 0;
if(InStruct)
{
const UAnimationGraphSchema* AnimationGraphSchema = GetDefault<UAnimationGraphSchema>();
FAnimBlueprintDebugData& AnimBlueprintDebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData();
for(const TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>& Record : InRecords)
{
// Skip folded records
if(Record->FoldIndex == INDEX_NONE)
{
FEdGraphPinType VariableType;
if(AnimationGraphSchema->ConvertPropertyToPinType(Record->Property, VariableType))
{
// Patch into sparse class data
TStringBuilder<64> PropertyNameStringBuilder;
// Add prefix for internal use
PropertyNameStringBuilder.Append(TEXT("__"));
// If we are an object property, we should be named according to the underlying class type to avoid
// warnings if we tagged-property-serialize a colliding name with an updated struct layout
if(const FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Record->Property))
{
PropertyNameStringBuilder.Append(ObjectProperty->PropertyClass->GetName());
}
else
{
PropertyNameStringBuilder.Append(Record->Property->GetClass()->GetName());
}
const FName PropertyName = FName(PropertyNameStringBuilder.ToString(), InRecords.Num() - 1 - PropertyIndex);
Record->GeneratedProperty = CreateStructVariable(InStruct, PropertyName, VariableType);
if (Record->GeneratedProperty == nullptr)
{
MessageLog.Error(*FString::Printf(TEXT("Property %s on node @@ could not be patched into data area."), *Record->Property->GetName()), Record->AnimGraphNode);
}
else
{
// Propagate some relevant property flags
Record->GeneratedProperty->SetPropertyFlags(Record->Property->GetPropertyFlags() & CPF_EditFixedSize);
// Properties need to be BP visible to allow them to be set by the generated exec chains in CreateEvaluationHandlerForNode
Record->GeneratedProperty->SetPropertyFlags(CPF_BlueprintVisible);
Record->PropertyIndex = PropertyIndex++;
// Get the source graph node of the given one as the debug utilities and other users of the graph pin to folded property map
// are working with the source graph nodes.
UEdGraphNode* GraphNode = CastChecked<UEdGraphNode>(MessageLog.FindSourceObject(Record->AnimGraphNode));
if (GraphNode)
{
UEdGraphPin* GraphPin = GraphNode->FindPin(Record->Property->GetName());
if (GraphPin)
{
// Link the graph pin with the generated (folded) property.
AnimBlueprintDebugData.GraphPinToFoldedPropertyMap.Add(GraphPin, Record->GeneratedProperty);
// Also add all linked pins directly here. This is needed for pins on nodes that are not processed by the compiler as e.g. get variable nodes.
for (UEdGraphPin* LinkedToPin : GraphPin->LinkedTo)
{
AnimBlueprintDebugData.GraphPinToFoldedPropertyMap.Add(LinkedToPin, Record->GeneratedProperty);
}
}
}
}
}
else
{
MessageLog.Error(*FString::Printf(TEXT("Property %s on node @@ could not be patched into data area."), *Record->Property->GetName()), Record->AnimGraphNode);
}
}
}
if(InRecords.Num() > 0)
{
InStruct->StaticLink(true);
}
}
return PropertyIndex;
};
// Next, patch into the relevant data areas
const int32 NumConstantProperties = BuildDataArea(ConstantPropertyRecords, NewAnimBlueprintConstants);
// Set constants as sparse class data
if(ConstantPropertyRecords.Num() > 0)
{
NewAnimBlueprintClass->SetSparseClassDataStruct(NewAnimBlueprintConstants);
}
const int32 NumMutableProperties = BuildDataArea(MutablePropertyRecords, NewAnimBlueprintMutables);
// Create the property for our mutables, if we have any
if(MutablePropertyRecords.Num() > 0)
{
const FName MutablesStructName("__AnimBlueprintMutables");
FEdGraphPinType MutablesPinType;
MutablesPinType.PinCategory = UAnimationGraphSchema::PC_Struct;
MutablesPinType.PinSubCategoryObject = MakeWeakObjectPtr(NewAnimBlueprintMutables);
NewMutablesProperty = CastFieldChecked<FStructProperty>(CreateVariable(MutablesStructName, MutablesPinType));
NewMutablesProperty->SetMetaData(TEXT("BlueprintCompilerGeneratedDefaults"), TEXT("true"));
}
else
{
// No more references to this struct, as it was not used to generate the property above, so mark it as garbage to ensure it doesnt get saved into the package
NewAnimBlueprintMutables->MarkAsGarbage();
}
// Set up per-node mappings
NewAnimBlueprintClass->AnimNodeData.Empty();
NewAnimBlueprintClass->AnimNodeData.SetNum(AllocatedAnimNodeIndices.Num());
NewAnimBlueprintClass->NodeTypeMap.Empty();
// First index & setup the node data
for(const TPair<int32, FProperty*>& IndexPropertyPair : AllocatedPropertiesByIndex)
{
int32 NodeIndex = AllocatedPropertiesByIndex.Num() - 1 - IndexPropertyPair.Key;
FStructProperty* NodeStructProperty = CastFieldChecked<FStructProperty>(IndexPropertyPair.Value);
const UScriptStruct* Struct = NodeStructProperty->Struct;
const FAnimNodeStructData AnimNodeStructData = NewAnimBlueprintClass->NodeTypeMap.Add(Struct, FAnimNodeStructData(Struct));
const int32 NumProperties = AnimNodeStructData.GetNumProperties();
// Add any super-structs as values can be accessed via base classes
const UScriptStruct* SuperStruct = Cast<UScriptStruct>(Struct->GetSuperStruct());
while(SuperStruct)
{
NewAnimBlueprintClass->NodeTypeMap.Add(SuperStruct, FAnimNodeStructData(SuperStruct));
SuperStruct = Cast<UScriptStruct>(SuperStruct->GetSuperStruct());
}
FAnimNodeData& NodeData = NewAnimBlueprintClass->AnimNodeData[NodeIndex];
NodeData.AnimClassInterface = NewAnimBlueprintClass;
NodeData.NodeIndex = NodeIndex;
check(NumProperties >= 0);
NodeData.Entries.SetNum(NumProperties);
for(uint32& Entry : NodeData.Entries)
{
Entry = ANIM_NODE_DATA_INVALID_ENTRY;
}
}
auto BuildAnimNodeData = [this, &NewAnimBlueprintClass](const TArray<TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>>& InRecords, int32 InTotalPropertyCount)
{
const int32 NumNodes = AllocatedAnimNodeIndices.Num();
for(const TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>& Record : InRecords)
{
int32 NodeIndex = NumNodes - 1 - AllocatedAnimNodeIndices.FindChecked(Record->AnimGraphNode);
FAnimNodeData& NodeData = NewAnimBlueprintClass->AnimNodeData[NodeIndex];
int32 PropertyIndex = Record->PropertyIndex;
if(Record->FoldIndex != INDEX_NONE)
{
PropertyIndex = InRecords[Record->FoldIndex]->PropertyIndex;
}
check(PropertyIndex >= 0 && PropertyIndex < InTotalPropertyCount);
PropertyIndex = InTotalPropertyCount - 1 - PropertyIndex;
uint32 PropertyEntry = PropertyIndex;
if(!Record->bIsOnClass)
{
PropertyEntry |= ANIM_NODE_DATA_INSTANCE_DATA_FLAG;
}
const FAnimNodeStructData& AnimNodeStructData = NewAnimBlueprintClass->NodeTypeMap.FindChecked(Record->AnimNodeProperty->Struct);
const int32 EntryIndex = AnimNodeStructData.GetPropertyIndex(Record->Property->GetFName());
NodeData.Entries[EntryIndex] = PropertyEntry;
}
};
BuildAnimNodeData(ConstantPropertyRecords, NumConstantProperties);
BuildAnimNodeData(MutablePropertyRecords, NumMutableProperties);
const int32 NumNodes = AllocatedAnimNodeIndices.Num();
// Patch anim node data flags
for(const TPair<UAnimGraphNode_Base*, int32>& GraphNodeIndexPair : AllocatedAnimNodeIndices)
{
UAnimGraphNode_Base* AnimGraphNode = GraphNodeIndexPair.Key;
const int32 NodeIndex = NumNodes - 1 - GraphNodeIndexPair.Value;
FAnimNodeData& NodeData = NewAnimBlueprintClass->AnimNodeData[NodeIndex];
UClass* ClassToUse = AnimGraphNode->GetBlueprintClassFromNode();
if(AnimGraphNode->InitialUpdateFunction.ResolveMember<UFunction>(ClassToUse))
{
NodeData.SetNodeFlags(EAnimNodeDataFlags::HasInitialUpdateFunction);
}
if(AnimGraphNode->UpdateFunction.ResolveMember<UFunction>(ClassToUse))
{
NodeData.SetNodeFlags(EAnimNodeDataFlags::HasUpdateFunction);
}
if(AnimGraphNode->BecomeRelevantFunction.ResolveMember<UFunction>(ClassToUse))
{
NodeData.SetNodeFlags(EAnimNodeDataFlags::HasBecomeRelevantFunction);
}
}
}
bool FAnimBlueprintCompilerContext::IsAnimGraphNodeFolded(UAnimGraphNode_Base* InNode) const
{
return NodeToFoldedPropertyRecordMap.Find(InNode) != nullptr;
}
const IAnimBlueprintCompilationContext::FFoldedPropertyRecord* FAnimBlueprintCompilerContext::GetFoldedPropertyRecord(UAnimGraphNode_Base* InNode, FName InPropertyName) const
{
if(const TArray<TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>>* FoundRecordsPtr = NodeToFoldedPropertyRecordMap.Find(InNode))
{
for(const TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>& Record : *FoundRecordsPtr)
{
if(Record->Property->GetFName() == InPropertyName)
{
return &Record.Get();
}
}
}
return nullptr;
}
void FAnimBlueprintCompilerContext::PreCompileUpdateBlueprintOnLoad(UBlueprint* BP)
{
if (UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP))
{
if (AnimBP->GetLinkerCustomVersion(FFrameworkObjectVersion::GUID) < FFrameworkObjectVersion::AnimBlueprintSubgraphFix)
{
AnimationEditorUtils::RegenerateSubGraphArrays(AnimBP);
}
}
}
//////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE