// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimBlueprintExtension_CachedPose.h" #include "AnimGraphNode_SaveCachedPose.h" #include "AnimGraphNode_Root.h" #include "AnimGraphNode_UseCachedPose.h" #include "Kismet2/CompilerResultsLog.h" #include "AnimGraphNode_StateMachine.h" #include "AnimGraphNode_StateResult.h" #include "IAnimBlueprintGeneratedClassCompiledData.h" #include "IAnimBlueprintCompilerCreationContext.h" #include "IAnimBlueprintCompilationContext.h" #include "AnimationGraphSchema.h" void UAnimBlueprintExtension_CachedPose::HandleStartCompilingClass(const UClass* InClass, IAnimBlueprintCompilationBracketContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { SaveCachedPoseNodes.Empty(); } void UAnimBlueprintExtension_CachedPose::HandlePreProcessAnimationNodes(TArrayView InAnimNodes, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { for(UAnimGraphNode_Base* Node : InAnimNodes) { if(UAnimGraphNode_SaveCachedPose* SavePoseRoot = Cast(Node)) { //@TODO: Ideally we only add these if there is a UseCachedPose node referencing them, but those can be anywhere and are hard to grab SaveCachedPoseNodes.Add(SavePoseRoot->CacheName, SavePoseRoot); } } } void UAnimBlueprintExtension_CachedPose::HandlePostProcessAnimationNodes(TArrayView InAnimNodes, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { // Build cached pose map BuildCachedPoseNodeUpdateOrder(InCompilationContext, OutCompiledData); } TAutoConsoleVariable CVarAnimDebugCachePoseNodeUpdateOrder(TEXT("a.Compiler.CachePoseNodeUpdateOrderDebug.Enable"), 0, TEXT("Toggle debugging for CacheNodeUpdateOrder debug during AnimBP compilation")); void UAnimBlueprintExtension_CachedPose::BuildCachedPoseNodeUpdateOrder(IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { TArray RootNodes; InCompilationContext.GetConsolidatedEventGraph()->GetNodesOfClass(RootNodes); // State results are also "root" nodes, need to find the true roots RootNodes.RemoveAll([](UAnimGraphNode_Root* InPossibleRootNode) { return InPossibleRootNode->GetClass() != UAnimGraphNode_Root::StaticClass(); }); const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); for(UAnimGraphNode_Root* RootNode : RootNodes) { TArray OrderedSavePoseNodes; TArray VisitedRootNodes; UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("CachePoseNodeOrdering BEGIN")); CachePoseNodeOrdering_StartNewTraversal(InCompilationContext, RootNode, OrderedSavePoseNodes, VisitedRootNodes); UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("CachePoseNodeOrdering END")); if (bEnableDebug) { UE_LOG(LogAnimation, Display, TEXT("Ordered Save Pose Node List:")); for (UAnimGraphNode_SaveCachedPose* SavedPoseNode : OrderedSavePoseNodes) { UE_LOG(LogAnimation, Display, TEXT("\t%s"), *SavedPoseNode->Node.CachePoseName.ToString()) } UE_LOG(LogAnimation, Display, TEXT("End List")); } FCachedPoseIndices& OrderedSavedPoseIndices = OutCompiledData.GetOrderedSavedPoseIndicesMap().FindOrAdd(RootNode->Node.GetName()); for(UAnimGraphNode_SaveCachedPose* PoseNode : OrderedSavePoseNodes) { if(const int32* NodeIndex = InCompilationContext.GetAllocatedAnimNodeIndices().Find(PoseNode)) { OrderedSavedPoseIndices.OrderedSavedPoseNodeIndices.Add(*NodeIndex); } else { InCompilationContext.GetMessageLog().Error(TEXT("Failed to find index for a saved pose node while building ordered pose list.")); } } } } void UAnimBlueprintExtension_CachedPose::CachePoseNodeOrdering_StartNewTraversal(IAnimBlueprintCompilationContext& InCompilationContext, UAnimGraphNode_Base* InRootNode, TArray &OrderedSavePoseNodes, TArray VisitedRootNodes) { check(InRootNode); UAnimGraphNode_SaveCachedPose* RootCacheNode = Cast(InRootNode); FString RootName = RootCacheNode ? RootCacheNode->CacheName : InRootNode->GetName(); const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("StartNewTraversal %s"), *RootName); // Track which root nodes we've visited to prevent infinite recursion. VisitedRootNodes.Add(InRootNode); // Need a list of only what we find here to recurse, we can't do that with the total list TArray InternalOrderedNodes; // Traverse whole graph from root collecting SaveCachePose nodes we've touched. CachePoseNodeOrdering_TraverseInternal(InCompilationContext, InRootNode, InternalOrderedNodes); // Process nodes that we've touched UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("Process Queue for %s"), *RootName); for (UAnimGraphNode_SaveCachedPose* QueuedCacheNode : InternalOrderedNodes) { if (VisitedRootNodes.Contains(QueuedCacheNode)) { UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("Process Queue SaveCachePose %s. ALREADY VISITED, INFINITE RECURSION DETECTED! SKIPPING"), *QueuedCacheNode->CacheName); InCompilationContext.GetMessageLog().Error(*FString::Printf(TEXT("Infinite recursion detected with SaveCachePose %s and %s"), *RootName, *QueuedCacheNode->CacheName)); continue; } else { OrderedSavePoseNodes.Remove(QueuedCacheNode); OrderedSavePoseNodes.Add(QueuedCacheNode); CachePoseNodeOrdering_StartNewTraversal(InCompilationContext, QueuedCacheNode, OrderedSavePoseNodes, VisitedRootNodes); } } UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("EndNewTraversal %s"), *RootName); } void UAnimBlueprintExtension_CachedPose::CachePoseNodeOrdering_TraverseInternal_SubGraph(IAnimBlueprintCompilationContext& InCompilationContext, UEdGraph* InGraph, TArray &OrderedSavePoseNodes) { const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("ProcessGraph %s"), *InGraph->GetName()); if(InGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass())) { TArray PotentialRootNodes; InGraph->GetNodesOfClass(PotentialRootNodes); for (UAnimGraphNode_Base* PotentialRootNode : PotentialRootNodes) { if(PotentialRootNode->IsNodeRootSet()) { CachePoseNodeOrdering_TraverseInternal(InCompilationContext, PotentialRootNode, OrderedSavePoseNodes); } } } else { for(UEdGraph* SubGraph : InGraph->SubGraphs) { CachePoseNodeOrdering_TraverseInternal_SubGraph(InCompilationContext, SubGraph, OrderedSavePoseNodes); } } } void UAnimBlueprintExtension_CachedPose::CachePoseNodeOrdering_TraverseInternal(IAnimBlueprintCompilationContext& InCompilationContext, UAnimGraphNode_Base* InAnimGraphNode, TArray &OrderedSavePoseNodes) { TArray LinkedAnimNodes; InCompilationContext.GetLinkedAnimNodes(InAnimGraphNode, LinkedAnimNodes); const bool bEnableDebug = (CVarAnimDebugCachePoseNodeUpdateOrder.GetValueOnAnyThread() == 1); for (UAnimGraphNode_Base* LinkedNode : LinkedAnimNodes) { UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("\t Processing %s"), *LinkedNode->GetName()); if (UAnimGraphNode_UseCachedPose* UsePoseNode = Cast(LinkedNode)) { if (UAnimGraphNode_SaveCachedPose* SaveNode = UsePoseNode->SaveCachedPoseNode.Get()) { UE_CLOG(bEnableDebug, LogAnimation, Display, TEXT("\t Queueing SaveCachePose %s"), *SaveNode->CacheName); // Requeue the node we found OrderedSavePoseNodes.Remove(SaveNode); OrderedSavePoseNodes.Add(SaveNode); } } else { TArray SubGraphs = LinkedNode->GetSubGraphs(); if(SubGraphs.Num() > 0) { for (UEdGraph* SubGraph : SubGraphs) { CachePoseNodeOrdering_TraverseInternal_SubGraph(InCompilationContext, SubGraph, OrderedSavePoseNodes); } } else { CachePoseNodeOrdering_TraverseInternal(InCompilationContext, LinkedNode, OrderedSavePoseNodes); } } } } const TMap& UAnimBlueprintExtension_CachedPose::GetSaveCachedPoseNodes() const { return SaveCachedPoseNodes; }