// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimBlueprintExtension_StateMachine.h" #include "Animation/AnimBlueprintGeneratedClass.h" #include "K2Node_AnimGetter.h" #include "K2Node_TransitionRuleGetter.h" #include "Kismet2/CompilerResultsLog.h" #include "AnimGraphNode_Base.h" #include "K2Node_StructMemberGet.h" #include "AnimStateNode.h" #include "AnimStateTransitionNode.h" #include "AnimGraphNode_StateMachineBase.h" #include "EdGraphUtilities.h" #include "AnimationStateMachineSchema.h" #include "IAnimBlueprintGeneratedClassCompiledData.h" #include "IAnimBlueprintCompilerCreationContext.h" #include "IAnimBlueprintCompilationContext.h" #define LOCTEXT_NAMESPACE "AnimBlueprintExtension_StateMachine" void UAnimBlueprintExtension_StateMachine::HandleBeginCompilation(IAnimBlueprintCompilerCreationContext& InCreationContext) { InCreationContext.RegisterKnownGraphSchema(UAnimationStateMachineSchema::StaticClass()); } void UAnimBlueprintExtension_StateMachine::HandleStartCompilingClass(const UClass* InClass, IAnimBlueprintCompilationBracketContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { FoundGetterNodes.Empty(); RootTransitionGetters.Empty(); RootGraphAnimGetters.Empty(); } void UAnimBlueprintExtension_StateMachine::HandlePreProcessAnimationNodes(TArrayView InAnimNodes, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { InCompilationContext.GetConsolidatedEventGraph()->GetNodesOfClass(RootTransitionGetters); // Get anim getters from the root anim graph (processing the nodes below will collect them in nested graphs) InCompilationContext.GetConsolidatedEventGraph()->GetNodesOfClass(RootGraphAnimGetters); } void UAnimBlueprintExtension_StateMachine::HandlePostProcessAnimationNodes(TArrayView InAnimNodes, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { // Process the getter nodes in the graph if there were any for (auto GetterIt = RootTransitionGetters.CreateIterator(); GetterIt; ++GetterIt) { ProcessTransitionGetter(*GetterIt, nullptr, InCompilationContext, OutCompiledData); // transition nodes should not appear at top-level } // Wire root getters for(UK2Node_AnimGetter* RootGraphGetter : RootGraphAnimGetters) { AutoWireAnimGetter(RootGraphGetter, nullptr, InCompilationContext, OutCompiledData); } // Wire nested getters for(UK2Node_AnimGetter* Getter : FoundGetterNodes) { AutoWireAnimGetter(Getter, nullptr, InCompilationContext, OutCompiledData); } } UK2Node_CallFunction* UAnimBlueprintExtension_StateMachine::SpawnCallAnimInstanceFunction(IAnimBlueprintCompilationContext& InCompilationContext, UEdGraphNode* SourceNode, FName FunctionName) { //@TODO: SKELETON: This is a call on a parent function (UAnimInstance::StaticClass() specifically), should we treat it as self or not? UK2Node_CallFunction* FunctionCall = InCompilationContext.SpawnIntermediateNode(SourceNode); FunctionCall->FunctionReference.SetSelfMember(FunctionName); FunctionCall->AllocateDefaultPins(); return FunctionCall; } void UAnimBlueprintExtension_StateMachine::ProcessTransitionGetter(UK2Node_TransitionRuleGetter* Getter, UAnimStateTransitionNode* TransitionNode, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { // Get common elements for multiple getters UEdGraphPin* OutputPin = Getter->GetOutputPin(); UEdGraphPin* SourceTimePin = NULL; UAnimationAsset* AnimAsset= NULL; int32 PlayerNodeIndex = INDEX_NONE; if (UAnimGraphNode_Base* SourcePlayerNode = Getter->AssociatedAnimAssetPlayerNode) { // This check should never fail as the source state is always processed first before handling it's rules UAnimGraphNode_Base* TrueSourceNode = InCompilationContext.GetMessageLog().FindSourceObjectTypeChecked(SourcePlayerNode); UAnimGraphNode_Base* UndertypedPlayerNode = InCompilationContext.GetSourceNodeToProcessedNodeMap().FindRef(TrueSourceNode); if (UndertypedPlayerNode == NULL) { InCompilationContext.GetMessageLog().Error(TEXT("ICE: Player node @@ was not processed prior to handling a transition getter @@ that used it"), SourcePlayerNode, Getter); return; } // Make sure the node is still relevant UEdGraph* PlayerGraph = UndertypedPlayerNode->GetGraph(); if (!PlayerGraph->Nodes.Contains(UndertypedPlayerNode)) { InCompilationContext.GetMessageLog().Error(TEXT("@@ is not associated with a node in @@; please delete and recreate it"), Getter, PlayerGraph); } // Make sure the referenced AnimAsset player has been allocated PlayerNodeIndex = InCompilationContext.GetAllocationIndexOfNode(UndertypedPlayerNode); if (PlayerNodeIndex == INDEX_NONE) { InCompilationContext.GetMessageLog().Error(*LOCTEXT("BadAnimAssetNodeUsedInGetter", "@@ doesn't have a valid associated AnimAsset node. Delete and recreate it").ToString(), Getter); } // Grab the AnimAsset, and time pin if needed UScriptStruct* TimePropertyInStructType = NULL; const TCHAR* TimePropertyName = NULL; if (UndertypedPlayerNode->DoesSupportTimeForTransitionGetter()) { AnimAsset = UndertypedPlayerNode->GetAnimationAsset(); TimePropertyInStructType = UndertypedPlayerNode->GetTimePropertyStruct(); TimePropertyName = UndertypedPlayerNode->GetTimePropertyName(); } else { InCompilationContext.GetMessageLog().Error(TEXT("@@ is associated with @@, which is an unexpected type"), Getter, UndertypedPlayerNode); } bool bNeedTimePin = false; // Determine if we need to read the current time variable from the specified sequence player switch (Getter->GetterType) { case ETransitionGetter::AnimationAsset_GetCurrentTime: case ETransitionGetter::AnimationAsset_GetCurrentTimeFraction: case ETransitionGetter::AnimationAsset_GetTimeFromEnd: case ETransitionGetter::AnimationAsset_GetTimeFromEndFraction: bNeedTimePin = true; break; default: bNeedTimePin = false; break; } if (bNeedTimePin && (PlayerNodeIndex != INDEX_NONE) && (TimePropertyName != NULL) && (TimePropertyInStructType != NULL)) { const FProperty* NodeProperty = InCompilationContext.GetAllocatedPropertiesByIndex().FindChecked(PlayerNodeIndex); // Create a struct member read node to grab the current position of the sequence player node UK2Node_StructMemberGet* TimeReadNode = InCompilationContext.SpawnIntermediateNode(Getter, InCompilationContext.GetConsolidatedEventGraph()); TimeReadNode->VariableReference.SetSelfMember(NodeProperty->GetFName()); TimeReadNode->StructType = TimePropertyInStructType; TimeReadNode->AllocatePinsForSingleMemberGet(TimePropertyName); SourceTimePin = TimeReadNode->FindPinChecked(TimePropertyName); } } // Expand it out UK2Node_CallFunction* GetterHelper = NULL; switch (Getter->GetterType) { case ETransitionGetter::AnimationAsset_GetCurrentTime: if ((AnimAsset != NULL) && (SourceTimePin != NULL)) { GetterHelper = SpawnCallAnimInstanceFunction(InCompilationContext, Getter, TEXT("GetInstanceAssetPlayerTime")); GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); } else { if (Getter->AssociatedAnimAssetPlayerNode) { InCompilationContext.GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Time. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); } else { InCompilationContext.GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); } } break; case ETransitionGetter::AnimationAsset_GetLength: if (AnimAsset != NULL) { GetterHelper = SpawnCallAnimInstanceFunction(InCompilationContext, Getter, TEXT("GetInstanceAssetPlayerLength")); GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); } else { if (Getter->AssociatedAnimAssetPlayerNode) { InCompilationContext.GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Length. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); } else { InCompilationContext.GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); } } break; case ETransitionGetter::AnimationAsset_GetCurrentTimeFraction: if ((AnimAsset != NULL) && (SourceTimePin != NULL)) { GetterHelper = SpawnCallAnimInstanceFunction(InCompilationContext, Getter, TEXT("GetInstanceAssetPlayerTimeFraction")); GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); } else { if (Getter->AssociatedAnimAssetPlayerNode) { InCompilationContext.GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Time Fraction. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); } else { InCompilationContext.GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); } } break; case ETransitionGetter::AnimationAsset_GetTimeFromEnd: if ((AnimAsset != NULL) && (SourceTimePin != NULL)) { GetterHelper = SpawnCallAnimInstanceFunction(InCompilationContext, Getter, TEXT("GetInstanceAssetPlayerTimeFromEnd")); GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); } else { if (Getter->AssociatedAnimAssetPlayerNode) { InCompilationContext.GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Time Remaining. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); } else { InCompilationContext.GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); } } break; case ETransitionGetter::AnimationAsset_GetTimeFromEndFraction: if ((AnimAsset != NULL) && (SourceTimePin != NULL)) { GetterHelper = SpawnCallAnimInstanceFunction(InCompilationContext, Getter, TEXT("GetInstanceAssetPlayerTimeFromEndFraction")); GetterHelper->FindPinChecked(TEXT("AssetPlayerIndex"))->DefaultValue = FString::FromInt(PlayerNodeIndex); } else { if (Getter->AssociatedAnimAssetPlayerNode) { InCompilationContext.GetMessageLog().Error(TEXT("Please replace @@ with Get Relevant Anim Time Remaining Fraction. @@ has no animation asset"), Getter, Getter->AssociatedAnimAssetPlayerNode); } else { InCompilationContext.GetMessageLog().Error(TEXT("@@ is not asscociated with an asset player"), Getter); } } break; case ETransitionGetter::CurrentTransitionDuration: { check(TransitionNode); if(UAnimStateNode* SourceStateNode = InCompilationContext.GetMessageLog().FindSourceObjectTypeChecked(TransitionNode->GetPreviousState())) { if(UObject* SourceTransitionNode = InCompilationContext.GetMessageLog().FindSourceObject(TransitionNode)) { if(FStateMachineDebugData* DebugData = OutCompiledData.GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) { if(int32* pStateIndex = DebugData->NodeToStateIndex.Find(SourceStateNode)) { const int32 StateIndex = *pStateIndex; // This check should never fail as all animation nodes should be processed before getters are UAnimGraphNode_Base* CompiledMachineInstanceNode = InCompilationContext.GetSourceNodeToProcessedNodeMap().FindChecked(DebugData->MachineInstanceNode.Get()); const int32 MachinePropertyIndex = InCompilationContext.GetAllocatedAnimNodeIndices().FindChecked(CompiledMachineInstanceNode); int32 TransitionPropertyIndex = INDEX_NONE; for(auto TransIt = DebugData->NodeToTransitionIndex.CreateConstIterator(); TransIt; ++TransIt) { const UEdGraphNode* CurrTransNode = TransIt.Key().Get(); if(CurrTransNode == SourceTransitionNode) { TransitionPropertyIndex = TransIt.Value(); break; } } if(TransitionPropertyIndex != INDEX_NONE) { GetterHelper = SpawnCallAnimInstanceFunction(InCompilationContext, Getter, TEXT("GetInstanceTransitionCrossfadeDuration")); GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); GetterHelper->FindPinChecked(TEXT("TransitionIndex"))->DefaultValue = FString::FromInt(TransitionPropertyIndex); } } } } } } break; case ETransitionGetter::ArbitraryState_GetBlendWeight: { if (Getter->AssociatedStateNode) { if (UAnimStateNode* SourceStateNode = InCompilationContext.GetMessageLog().FindSourceObjectTypeChecked(Getter->AssociatedStateNode)) { if (FStateMachineDebugData* DebugData = OutCompiledData.GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) { if (int32* pStateIndex = DebugData->NodeToStateIndex.Find(SourceStateNode)) { const int32 StateIndex = *pStateIndex; //const int32 MachineIndex = DebugData->MachineIndex; // This check should never fail as all animation nodes should be processed before getters are UAnimGraphNode_Base* CompiledMachineInstanceNode = InCompilationContext.GetSourceNodeToProcessedNodeMap().FindChecked(DebugData->MachineInstanceNode.Get()); const int32 MachinePropertyIndex = InCompilationContext.GetAllocatedAnimNodeIndices().FindChecked(CompiledMachineInstanceNode); GetterHelper = SpawnCallAnimInstanceFunction(InCompilationContext, Getter, TEXT("GetInstanceStateWeight")); GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); GetterHelper->FindPinChecked(TEXT("StateIndex"))->DefaultValue = FString::FromInt(StateIndex); } } } } if (GetterHelper == NULL) { InCompilationContext.GetMessageLog().Error(TEXT("@@ is not associated with a valid state"), Getter); } } break; case ETransitionGetter::CurrentState_ElapsedTime: { check(TransitionNode); if (UAnimStateNode* SourceStateNode = InCompilationContext.GetMessageLog().FindSourceObjectTypeChecked(TransitionNode->GetPreviousState())) { if (FStateMachineDebugData* DebugData = OutCompiledData.GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) { // This check should never fail as all animation nodes should be processed before getters are UAnimGraphNode_Base* CompiledMachineInstanceNode = InCompilationContext.GetSourceNodeToProcessedNodeMap().FindChecked(DebugData->MachineInstanceNode.Get()); const int32 MachinePropertyIndex = InCompilationContext.GetAllocatedAnimNodeIndices().FindChecked(CompiledMachineInstanceNode); GetterHelper = SpawnCallAnimInstanceFunction(InCompilationContext, Getter, TEXT("GetInstanceCurrentStateElapsedTime")); GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); } } if (GetterHelper == NULL) { InCompilationContext.GetMessageLog().Error(TEXT("@@ is not associated with a valid state"), Getter); } } break; case ETransitionGetter::CurrentState_GetBlendWeight: { check(TransitionNode); if (UAnimStateNode* SourceStateNode = InCompilationContext.GetMessageLog().FindSourceObjectTypeChecked(TransitionNode->GetPreviousState())) { { if (FStateMachineDebugData* DebugData = OutCompiledData.GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) { if (int32* pStateIndex = DebugData->NodeToStateIndex.Find(SourceStateNode)) { const int32 StateIndex = *pStateIndex; //const int32 MachineIndex = DebugData->MachineIndex; // This check should never fail as all animation nodes should be processed before getters are UAnimGraphNode_Base* CompiledMachineInstanceNode = InCompilationContext.GetSourceNodeToProcessedNodeMap().FindChecked(DebugData->MachineInstanceNode.Get()); const int32 MachinePropertyIndex = InCompilationContext.GetAllocatedAnimNodeIndices().FindChecked(CompiledMachineInstanceNode); GetterHelper = SpawnCallAnimInstanceFunction(InCompilationContext, Getter, TEXT("GetInstanceStateWeight")); GetterHelper->FindPinChecked(TEXT("MachineIndex"))->DefaultValue = FString::FromInt(MachinePropertyIndex); GetterHelper->FindPinChecked(TEXT("StateIndex"))->DefaultValue = FString::FromInt(StateIndex); } } } } if (GetterHelper == NULL) { InCompilationContext.GetMessageLog().Error(TEXT("@@ is not associated with a valid state"), Getter); } } break; default: InCompilationContext.GetMessageLog().Error(TEXT("Unrecognized getter type on @@"), Getter); break; } // Finish wiring up a call function if needed if (GetterHelper != NULL) { check(GetterHelper->IsNodePure()); UEdGraphPin* NewReturnPin = GetterHelper->FindPinChecked(TEXT("ReturnValue")); InCompilationContext.GetMessageLog().NotifyIntermediatePinCreation(NewReturnPin, OutputPin); NewReturnPin->CopyPersistentDataFromOldPin(*OutputPin); } // Remove the getter from the equation Getter->BreakAllNodeLinks(); } void UAnimBlueprintExtension_StateMachine::AutoWireAnimGetter(class UK2Node_AnimGetter* Getter, UAnimStateTransitionNode* InTransitionNode, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { UEdGraphPin* ReferencedNodeTimePin = nullptr; int32 ReferencedNodeIndex = INDEX_NONE; int32 SubNodeIndex = INDEX_NONE; UAnimGraphNode_Base* ProcessedNodeCheck = NULL; if(UAnimGraphNode_Base* SourceNode = Getter->SourceNode) { UAnimGraphNode_Base* ActualSourceNode = InCompilationContext.GetMessageLog().FindSourceObjectTypeChecked(SourceNode); if(UAnimGraphNode_Base* ProcessedSourceNode = InCompilationContext.GetSourceNodeToProcessedNodeMap().FindRef(ActualSourceNode)) { ProcessedNodeCheck = ProcessedSourceNode; ReferencedNodeIndex = InCompilationContext.GetAllocationIndexOfNode(ProcessedSourceNode); if(ProcessedSourceNode->DoesSupportTimeForTransitionGetter()) { UScriptStruct* TimePropertyInStructType = ProcessedSourceNode->GetTimePropertyStruct(); const TCHAR* TimePropertyName = ProcessedSourceNode->GetTimePropertyName(); if(ReferencedNodeIndex != INDEX_NONE && TimePropertyName && TimePropertyInStructType) { FProperty* NodeProperty = InCompilationContext.GetAllocatedPropertiesByIndex().FindChecked(ReferencedNodeIndex); UK2Node_StructMemberGet* ReaderNode = InCompilationContext.SpawnIntermediateNode(Getter, InCompilationContext.GetConsolidatedEventGraph()); ReaderNode->VariableReference.SetSelfMember(NodeProperty->GetFName()); ReaderNode->StructType = TimePropertyInStructType; ReaderNode->AllocatePinsForSingleMemberGet(TimePropertyName); ReferencedNodeTimePin = ReaderNode->FindPinChecked(TimePropertyName); } } } } if(Getter->SourceStateNode) { UObject* SourceObject = InCompilationContext.GetMessageLog().FindSourceObject(Getter->SourceStateNode); if(UAnimStateNode* SourceStateNode = Cast(SourceObject)) { if(FStateMachineDebugData* DebugData = OutCompiledData.GetAnimBlueprintDebugData().StateMachineDebugData.Find(SourceStateNode->GetGraph())) { if(int32* StateIndexPtr = DebugData->NodeToStateIndex.Find(SourceStateNode)) { SubNodeIndex = *StateIndexPtr; } } } else if(UAnimStateTransitionNode* TransitionNode = Cast(SourceObject)) { if(FStateMachineDebugData* DebugData = OutCompiledData.GetAnimBlueprintDebugData().StateMachineDebugData.Find(TransitionNode->GetGraph())) { TArray TransitionIndices; DebugData->NodeToTransitionIndex.MultiFind(TransitionNode, TransitionIndices); const int32 TransNum = TransitionIndices.Num(); if (TransNum > 0) { SubNodeIndex = TransitionIndices[0]; } } } } check(Getter->IsNodePure()); for(UEdGraphPin* Pin : Getter->Pins) { // Hook up autowired parameters / pins if(Pin->PinName == TEXT("CurrentTime")) { Pin->MakeLinkTo(ReferencedNodeTimePin); } else if(Pin->PinName == TEXT("AssetPlayerIndex") || Pin->PinName == TEXT("MachineIndex")) { Pin->DefaultValue = FString::FromInt(ReferencedNodeIndex); } else if(Pin->PinName == TEXT("StateIndex") || Pin->PinName == TEXT("TransitionIndex")) { Pin->DefaultValue = FString::FromInt(SubNodeIndex); } } } int32 UAnimBlueprintExtension_StateMachine::ExpandGraphAndProcessNodes(UEdGraph* SourceGraph, UAnimGraphNode_Base* SourceRootNode, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData, UAnimStateTransitionNode* TransitionNode, TArray* ClonedNodes) { // Clone the nodes from the source graph // Note that we outer this graph to the ConsolidatedEventGraph to allow ExpansionStep to // correctly retrieve the context for any expanded function calls (custom make/break structs etc.) UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(SourceGraph, InCompilationContext.GetConsolidatedEventGraph(), &InCompilationContext.GetMessageLog(), true); // Grab all the animation nodes and find the corresponding root node in the cloned set UAnimGraphNode_Base* TargetRootNode = nullptr; TArray AnimNodeList; TArray Getters; TArray AnimGetterNodes; for (auto NodeIt = ClonedGraph->Nodes.CreateIterator(); NodeIt; ++NodeIt) { UEdGraphNode* Node = *NodeIt; if (UK2Node_TransitionRuleGetter* GetterNode = Cast(Node)) { Getters.Add(GetterNode); } else if(UK2Node_AnimGetter* NewGetterNode = Cast(Node)) { AnimGetterNodes.Add(NewGetterNode); } else if (UAnimGraphNode_Base* TestNode = Cast(Node)) { AnimNodeList.Add(TestNode); //@TODO: There ought to be a better way to determine this if (InCompilationContext.GetMessageLog().FindSourceObject(TestNode) == InCompilationContext.GetMessageLog().FindSourceObject(SourceRootNode)) { TargetRootNode = TestNode; } } if (ClonedNodes != NULL) { ClonedNodes->Add(Node); } } check(TargetRootNode); // Run another expansion pass to catch the graph we just added (this is slightly wasteful InCompilationContext.ExpansionStep(ClonedGraph, false); // Validate graph now we have expanded/pruned InCompilationContext.ValidateGraphIsWellFormed(ClonedGraph); // Move the cloned nodes into the consolidated event graph const bool bIsLoading = InCompilationContext.GetBlueprint()->bIsRegeneratingOnLoad || IsAsyncLoading(); const bool bIsCompiling = InCompilationContext.GetBlueprint()->bBeingCompiled; ClonedGraph->MoveNodesToAnotherGraph(InCompilationContext.GetConsolidatedEventGraph(), bIsLoading, bIsCompiling); // Process any animation nodes { TArray RootSet; RootSet.Add(TargetRootNode); InCompilationContext.PruneIsolatedAnimationNodes(RootSet, AnimNodeList); InCompilationContext.ProcessAnimationNodes(AnimNodeList); } // Process the getter nodes in the graph if there were any for (auto GetterIt = Getters.CreateIterator(); GetterIt; ++GetterIt) { ProcessTransitionGetter(*GetterIt, TransitionNode, InCompilationContext, OutCompiledData); } // Wire anim getter nodes for(UK2Node_AnimGetter* GetterNode : AnimGetterNodes) { FoundGetterNodes.Add(GetterNode); } // Returns the index of the processed cloned version of SourceRootNode return InCompilationContext.GetAllocationIndexOfNode(TargetRootNode); } #undef LOCTEXT_NAMESPACE