// 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 ChildGraphs; ChildGraphs.Reserve(20); TMap 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 InPerGraphFunction) { TArray 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 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(); 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(); FEdGraphPinType NodeVariableType; NodeVariableType.PinCategory = UAnimationGraphSchema::PC_Struct; NodeVariableType.PinSubCategoryObject = MakeWeakObjectPtr(const_cast(NodeType)); FStructProperty* NewProperty = CastField(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(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(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 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(InstanceDataType)); FStructProperty* NewProperty = CastField(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(ClassDataType)); FStructProperty* NewProperty = CastField(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 It(InNodeType); It; ++It) { FProperty* SubProperty = *It; if(FArrayProperty* ArrayProperty = CastField(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() && InVisualAnimNode->GetClass()->IsChildOf(AnimGraphNodeProperty->GetOwner())); const void* Node = AnimGraphNodeProperty->ContainerPtrToValuePtr(InVisualAnimNode); // Check the anim node's property is contained in the anim node check(SubProperty->GetOwner() && AnimGraphNodeProperty->Struct->IsChildOf(SubProperty->GetOwner())); const void* TargetPtr = SubProperty->ContainerPtrToValuePtr(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() && InVisualAnimNode->GetClass()->IsChildOf(AnimGraphNodeProperty->GetOwner())); const void* Node = AnimGraphNodeProperty->ContainerPtrToValuePtr(InVisualAnimNode); // Check the anim node's property is contained in the anim node check(SubProperty->GetOwner() && AnimGraphNodeProperty->Struct->IsChildOf(SubProperty->GetOwner())); const void* TargetPtr = SubProperty->ContainerPtrToValuePtr(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(); } 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& RootSet, TArray& GraphNodes) { struct FNodeVisitorDownPoseWires { TSet VisitedNodes; const UAnimationGraphSchema* Schema; FNodeVisitorDownPoseWires() { Schema = GetDefault(); } 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()) { Node->BreakAllNodeLinks(); GraphNodes.RemoveAtSwap(NodeIndex); --NodeIndex; } } } void FAnimBlueprintCompilerContext::ProcessAnimationNodes(TArray& 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 &LinkedAnimNodes) const { for(UEdGraphPin* Pin : InGraphNode->Pins) { if(Pin->Direction == EEdGraphPinDirection::EGPD_Input && Pin->PinType.PinCategory == TEXT("struct")) { if(UScriptStruct* Struct = Cast(Pin->PinType.PinSubCategoryObject.Get())) { if(Struct->IsChildOf(FPoseLinkBase::StaticStruct())) { GetLinkedAnimNodes_TraversePin(Pin, LinkedAnimNodes); } } } } } void FAnimBlueprintCompilerContext::GetLinkedAnimNodes_TraversePin(UEdGraphPin* InPin, TArray& LinkedAnimNodes) const { if(!InPin) { return; } for(UEdGraphPin* LinkedPin : InPin->LinkedTo) { if(!LinkedPin) { continue; } UEdGraphNode* OwningNode = LinkedPin->GetOwningNode(); if(UK2Node_Knot* InnerKnot = Cast(OwningNode)) { GetLinkedAnimNodes_TraversePin(InnerKnot->GetInputPin(), LinkedAnimNodes); } else if(UAnimGraphNode_Base* AnimNode = Cast(OwningNode)) { GetLinkedAnimNodes_ProcessAnimNode(AnimNode, LinkedAnimNodes); } } } void FAnimBlueprintCompilerContext::GetLinkedAnimNodes_ProcessAnimNode(UAnimGraphNode_Base* AnimNode, TArray& LinkedAnimNodes) const { if(!AllocatedAnimNodes.Contains(AnimNode)) { UAnimGraphNode_Base* TrueSourceNode = MessageLog.FindSourceObjectTypeChecked(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 RootAnimNodeList; ConsolidatedEventGraph->GetNodesOfClass(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 AllSubGraphsAnimNodeList; ForAllSubGraphs(ConsolidatedEventGraph, [&AllSubGraphsAnimNodeList](UEdGraph* InGraph) { InGraph->GetNodesOfClass(AllSubGraphsAnimNodeList); }); // Find the root nodes TArray RootSet; AllocateNodeIndexCounter = 0; for (UAnimGraphNode_Base* SourceNode : RootAnimNodeList) { UAnimGraphNode_Base* TrueNode = MessageLog.FindSourceObjectTypeChecked(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(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(RootAnimClass->GetSuperClass())) { RootAnimClass = NextClass; } UObject* RootDefaultObject = RootAnimClass->GetDefaultObject(); for (TFieldIterator It(RootAnimClass); It; ++It) { FProperty* RootProp = *It; if (FStructProperty* RootStructProp = CastField(RootProp)) { if (RootStructProp->Struct->IsChildOf(FAnimNode_Base::StaticStruct())) { FStructProperty* ChildStructProp = FindFProperty(NewAnimBlueprintClass, *RootStructProp->GetName()); check(ChildStructProp); uint8* SourcePtr = RootStructProp->ContainerPtrToValuePtr(RootDefaultObject); uint8* DestPtr = ChildStructProp->ContainerPtrToValuePtr(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 PropertyIt(RootAnimClass->GetSparseClassDataStruct()); PropertyIt; ++PropertyIt) { FProperty* RootProperty = *PropertyIt; FProperty* ChildProperty = FindFProperty(NewAnimBlueprintClass->GetSparseClassDataStruct(), *RootProperty->GetName()); check(ChildProperty); const uint8* SourcePtr = RootProperty->ContainerPtrToValuePtr(RootAnimClass->GetConstantNodeData()); uint8* DestPtr = const_cast(ChildProperty->ContainerPtrToValuePtr(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 PropertyIt(RootAnimClass->MutableNodeDataProperty->Struct); PropertyIt; ++PropertyIt) { FProperty* RootProperty = *PropertyIt; FProperty* ChildProperty = FindFProperty(NewAnimBlueprintClass->MutableNodeDataProperty->Struct, *RootProperty->GetName()); check(ChildProperty != nullptr); const uint8* SourcePtr = RootProperty->ContainerPtrToValuePtr(RootAnimClass->GetMutableNodeData(RootDefaultObject)); uint8* DestPtr = ChildProperty->ContainerPtrToValuePtr(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 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(NewAnimBlueprintClass->GetVisualNodeFromNodePropertyIndex(NodeIndex, EPropertySearchMode::Hierarchy)); FAnimNode_Base* BaseNode = NewAnimBlueprintClass->GetPropertyInstance(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 LinkIndexMap; TMap NodeBaseAddresses; FAnimBlueprintGeneratedClassCompiledData CompiledData(NewAnimBlueprintClass); FAnimBlueprintCopyTermDefaultsContext CompilerContext(this); // Initialize animation nodes from their templates for (TFieldIterator 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(TargetProperty)->Struct == SourceNodeProperty->Struct); uint8* DestinationPtr = TargetProperty->ContainerPtrToValuePtr(DefaultAnimInstance); const uint8* SourcePtr = SourceNodeProperty->ContainerPtrToValuePtr(VisualAnimNode); FAnimBlueprintNodeCopyTermDefaultsContext NodeContext(DefaultObject, TargetProperty, DestinationPtr, SourcePtr, LinkIndexCount); UAnimGraphNode_Base* OriginalAnimNode = Cast(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>& InRecords) { for(const TSharedRef& 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() && Record->AnimGraphNode->GetClass()->IsChildOf(AnimGraphNodeProperty->GetOwner())); const void* Node = AnimGraphNodeProperty->ContainerPtrToValuePtr(Record->AnimGraphNode); // Check the anim node's property is contained in the anim node check(Record->Property->GetOwner() && AnimGraphNodeProperty->Struct->IsChildOf(Record->Property->GetOwner())); const void* SourcePtr = Record->Property->ContainerPtrToValuePtr(Node); // Check the generated property is a member of the constants struct check(Record->GeneratedProperty->GetOwner() && InStruct->IsChildOf(Record->GeneratedProperty->GetOwner())); void* TargetPtr = Record->GeneratedProperty->ContainerPtrToValuePtr(InData); // Extract underlying property for enums FProperty* PropertyToCopyWith = Record->GeneratedProperty; if(const FEnumProperty* EnumProperty = CastField(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 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(Constants); const uint8* SourcePtr = SourceExtensionProperty->ContainerPtrToValuePtr(Extension); FAnimBlueprintExtensionCopyTermDefaultsContext NodeContext(DefaultObject, TargetProperty, DestinationPtr, SourcePtr, LinkIndexCount); Extension->CopyTermDefaultsToSparseClassData(CompilerContext, NodeContext); } } } // Patch constants PatchDataArea(Constants, ConstantsStruct, ConstantPropertyRecords); } if(NewMutablesProperty) { check(NewMutablesProperty->GetOwner() && DefaultObject->GetClass()->IsChildOf(NewMutablesProperty->GetOwner())); void* Mutables = NewMutablesProperty->ContainerPtrToValuePtr(DefaultObject); UScriptStruct* MutablesStruct = NewMutablesProperty->Struct; check(MutablesStruct == NewAnimBlueprintMutables); // Patch mutables PatchDataArea(Mutables, MutablesStruct, MutablePropertyRecords); } // Initialize extensions from their templates for (TFieldIterator It(DefaultAnimInstance->GetClass(), EFieldIteratorFlags::ExcludeSuper); It; ++It) { FStructProperty* TargetProperty = *It; if (UAnimBlueprintExtension* Extension = InstancePropertyToExtensionMap.FindRef(TargetProperty)) { uint8* DestinationPtr = TargetProperty->ContainerPtrToValuePtr(DefaultAnimInstance); const uint8* SourcePtr = nullptr; const FStructProperty* SourceExtensionProperty = Extension->GetInstanceDataProperty(); if (SourceExtensionProperty) { check(TargetProperty->Struct == SourceExtensionProperty->Struct); SourcePtr = SourceExtensionProperty->ContainerPtrToValuePtr(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(NewClass); // copy threaded update flag to CDO DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = AnimBlueprint->bUseMultiThreadedAnimationUpdate; // Verify thread-safety if(GetDefault()->bAllowMultiThreadedAnimationUpdate && DefaultAnimInstance->bUseMultiThreadedAnimationUpdate) { // If we are a child anim BP, check parent classes & their CDOs if (UAnimBlueprintGeneratedClass* ParentClass = Cast(AnimBlueprintGeneratedClass->GetSuperClass())) { UAnimBlueprint* ParentAnimBlueprint = Cast(ParentClass->ClassGeneratedBy); if (ParentAnimBlueprint && !ParentAnimBlueprint->bUseMultiThreadedAnimationUpdate) { DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; } UAnimInstance* ParentDefaultObject = Cast(ParentClass->GetDefaultObject(false)); if (ParentDefaultObject && !ParentDefaultObject->bUseMultiThreadedAnimationUpdate) { DefaultAnimInstance->bUseMultiThreadedAnimationUpdate = false; } } // iterate all properties to determine validity for (FStructProperty* Property : TFieldRange(AnimBlueprintGeneratedClass, EFieldIteratorFlags::IncludeSuper)) { if(Property->Struct->IsChildOf(FAnimNode_Base::StaticStruct())) { FAnimNode_Base* AnimNode = Property->ContainerPtrToValuePtr(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>& StatementPair : UbergraphFunctionContext->StatementsPerNode) { if (UK2Node_StructMemberSet* StructMemberSetNode = Cast(StatementPair.Key)) { UK2Node* SourceNode = CastChecked(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(Statement->FunctionToCall->GetOuter()); const bool bFunctionLibraryCall = FunctionClass->IsChildOf(); const bool bAnimInstanceCall = FunctionClass->IsChildOf(); // 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(*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(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& 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(ParentAnimBP->GeneratedClass); check(ParentAnimClass); check(ParentAnimClass->GetSparseClassDataStruct()); // Derive sparse class data from parent class NewAnimBlueprintConstants = NewObject(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(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(NewAnimBlueprintClass, *UAnimBlueprintGeneratedClass::GetMutablesStructName().ToString()); // Set up our mutables struct NewAnimBlueprintMutables = NewObject(NewAnimBlueprintClass, UAnimBlueprintGeneratedClass::GetMutablesStructName(), RF_Public); NewAnimBlueprintMutables->SetSuperStruct(FAnimBlueprintMutableData::StaticStruct()); if(OldMutablesStruct) { FLinkerLoad::PRIVATE_PatchNewObjectIntoExport(OldMutablesStruct, NewAnimBlueprintMutables); } } void FAnimBlueprintCompilerContext::SpawnNewClass(const FString& NewClassName) { NewClass = FindObject(Blueprint->GetOutermost(), *NewClassName); if (NewClass == NULL) { NewClass = NewObject(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(ClassToUse); } void FAnimBlueprintCompilerContext::CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOldCDO) { UAnimBlueprintGeneratedClass* AnimBlueprintClassToClean = CastChecked(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(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(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()) { 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(NewClass); if(UAnimInstance* DefaultAnimInstance = Cast(AnimBlueprintGeneratedClass->GetDefaultObject())) { // iterate all anim node and call PostCompile if(const USkeleton* CurrentSkeleton = AnimBlueprint->TargetSkeleton) { for (FStructProperty* Property : TFieldRange(AnimBlueprintGeneratedClass, EFieldIteratorFlags::IncludeSuper)) { if (Property->Struct->IsChildOf(FAnimNode_Base::StaticStruct())) { FAnimNode_Base* AnimNode = Property->ContainerPtrToValuePtr(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(NewAnimBlueprintClass->GetDefaultObject())) { if (DefaultAnimInstance->PostCompileValidationClassName.IsValid()) { UClass* PostCompileValidationClass = LoadClass(nullptr, *DefaultAnimInstance->PostCompileValidationClassName.ToString()); if (PostCompileValidationClass) { UAnimBlueprintPostCompileValidation* PostCompileValidation = NewObject(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(NewAnimBlueprintClass->GetDefaultObject())) { DefaultAnimInstance->bUsingCopyPoseFromMesh = bUsingCopyPoseFromMesh; } } } void FAnimBlueprintCompilerContext::CreateAnimGraphStubFunctions() { TArray 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(InterfaceClass->ClassGeneratedBy)) { TArray 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 Roots; GraphToUseforSignature->GetNodesOfClass(Roots); TArray 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(Blueprint, NewGraphName); NewGraphs.Add(StubGraph); StubGraph->Schema = UEdGraphSchema_K2::StaticClass(); StubGraph->SetFlags(RF_Transient); // Add an entry node UK2Node_FunctionEntry* EntryNode = SpawnIntermediateNode(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 PosePinInfo = MakeShared(); 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 ParameterPinInfo = MakeShared(); 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(RootNode, StubGraph); ResultNode->NodePosX = 200; // Add root as the 'return value' TSharedPtr PinInfo = MakeShared(); 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 EntryPoints; InGraph->GetNodesOfClass(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 EntryPoints; InGraph->GetNodesOfClass(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 InAttributes) const { if(UAnimGraphNode_Base* OriginalNode = CastChecked(MessageLog.FindSourceObject(InNode))) { TArray& AttributeSet = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().NodeAttributes.FindOrAdd(OriginalNode); AttributeSet.Reserve(AttributeSet.Num() + InAttributes.Num()); for(const FName& Attribute : InAttributes) { AttributeSet.AddUnique(Attribute); } } } TArrayView FAnimBlueprintCompilerContext::GetAttributesFromNode(UAnimGraphNode_Base* InNode) const { if(UAnimGraphNode_Base* OriginalNode = CastChecked(MessageLog.FindSourceObject(InNode))) { if(const TArray* AttributeSetPtr = GetNewAnimBlueprintClass()->GetAnimBlueprintDebugData().NodeAttributes.Find(OriginalNode)) { return MakeArrayView(*AttributeSetPtr); } } return TArrayView(); } 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 Record = MakeShared(InAnimGraphNode, InAnimNodeProperty, InProperty, bConstant); TArray>& 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& InRecord) { FStructProperty* AnimGraphNodeProperty = InRecord->AnimGraphNode->GetFNodeProperty(); check(AnimGraphNodeProperty->GetOwner() && InRecord->AnimGraphNode->GetClass()->IsChildOf(AnimGraphNodeProperty->GetOwner())); const void* Node = AnimGraphNodeProperty->ContainerPtrToValuePtr(InRecord->AnimGraphNode); check(InRecord->Property->GetOwner() && AnimGraphNodeProperty->Struct->IsChildOf(InRecord->Property->GetOwner())); return InRecord->Property->ContainerPtrToValuePtr(Node); }; // Reduce any constant properties before patching for(int32 RecordIndex0 = 0; RecordIndex0 < ConstantPropertyRecords.Num(); ++RecordIndex0) { const TSharedRef& Record0 = ConstantPropertyRecords[RecordIndex0]; if(Record0->FoldIndex == INDEX_NONE) { const void* Value0 = GetRecordValue(Record0); for(int32 RecordIndex1 = RecordIndex0 + 1; RecordIndex1 < ConstantPropertyRecords.Num(); ++RecordIndex1) { const TSharedRef& 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>& InRecords, UScriptStruct* InStruct) { int32 PropertyIndex = 0; if(InStruct) { const UAnimationGraphSchema* AnimationGraphSchema = GetDefault(); FAnimBlueprintDebugData& AnimBlueprintDebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData(); for(const TSharedRef& 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(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(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(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& IndexPropertyPair : AllocatedPropertiesByIndex) { int32 NodeIndex = AllocatedPropertiesByIndex.Num() - 1 - IndexPropertyPair.Key; FStructProperty* NodeStructProperty = CastFieldChecked(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(Struct->GetSuperStruct()); while(SuperStruct) { NewAnimBlueprintClass->NodeTypeMap.Add(SuperStruct, FAnimNodeStructData(SuperStruct)); SuperStruct = Cast(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>& InRecords, int32 InTotalPropertyCount) { const int32 NumNodes = AllocatedAnimNodeIndices.Num(); for(const TSharedRef& 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& 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(ClassToUse)) { NodeData.SetNodeFlags(EAnimNodeDataFlags::HasInitialUpdateFunction); } if(AnimGraphNode->UpdateFunction.ResolveMember(ClassToUse)) { NodeData.SetNodeFlags(EAnimNodeDataFlags::HasUpdateFunction); } if(AnimGraphNode->BecomeRelevantFunction.ResolveMember(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>* FoundRecordsPtr = NodeToFoldedPropertyRecordMap.Find(InNode)) { for(const TSharedRef& Record : *FoundRecordsPtr) { if(Record->Property->GetFName() == InPropertyName) { return &Record.Get(); } } } return nullptr; } void FAnimBlueprintCompilerContext::PreCompileUpdateBlueprintOnLoad(UBlueprint* BP) { if (UAnimBlueprint* AnimBP = Cast(BP)) { if (AnimBP->GetLinkerCustomVersion(FFrameworkObjectVersion::GUID) < FFrameworkObjectVersion::AnimBlueprintSubgraphFix) { AnimationEditorUtils::RegenerateSubGraphArrays(AnimBP); } } } ////////////////////////////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE