// Copyright 2017 ~ 2022 Critical Failure Studio Ltd. All rights reserved. #include "Compilers/PaperZDAnimBPCompilerContext.h" #include "Compilers/Access/PaperZDAnimBPCompilerAccess.h" #include "Compilers/Access/PaperZDAnimBPGeneratedClassAccess.h" #include "Editors/Util/PaperZDEditorSettings.h" #include "AnimSequences/Sources/PaperZDAnimationSource.h" #include "PaperZDAnimBP.h" #include "PaperZDAnimInstance.h" #include "PaperZDAnimBPGeneratedClass.h" #include "Graphs/PaperZDAnimGraphSchema.h" #include "Graphs/PaperZDNotifyGraph.h" #include "Graphs/Nodes/PaperZDAnimGraphNode_Base.h" #include "Graphs/Nodes/PaperZDAnimGraphNode_Sink.h" #include "EdGraphUtilities.h" #include "Kismet2/KismetReinstanceUtilities.h" #include "Kismet2/BlueprintEditorUtils.h" #include "K2Node_FunctionEntry.h" #include "K2Node_Knot.h" #include "PaperZDCustomVersion.h" #define LOCTEXT_NAMESPACE "PaperZDAnimBlueprintCompiler" //static definitions TMap> FPaperZDAnimBPCompilerContext::RegisteredHandleFactories = TMap>(); //end static definitions FPaperZDAnimBPCompilerContext::FPaperZDAnimBPCompilerContext(UBlueprint* Blueprint, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompilerOptions) : FKismetCompilerContext(Blueprint, InMessageLog, InCompilerOptions) , AnimBP(CastChecked(Blueprint)) { //Initialize all the compiler handles FPaperZDAnimBPCompilerAccess CompilerAccess(this); for(auto FactoryIt = RegisteredHandleFactories.CreateConstIterator(); FactoryIt; ++FactoryIt) { zid_t HandleId = 0; IPaperZDCompilerHandleFactory* FactoryPtr = (*FactoryIt).Value.Get(); IPaperZDAnimBPCompilerHandle* Handle = FactoryPtr->CreateNew(CompilerAccess, HandleId); CompileHandles.Add(HandleId, TUniquePtr(Handle)); } //Add the AnimBP graph schema as a known schema KnownGraphSchemas.AddUnique(UPaperZDAnimGraphSchema::StaticClass()); } TUniquePtr FPaperZDAnimBPCompilerContext::RequestAccess(FKismetCompilerContext& CompilerContext) { FPaperZDAnimBPCompilerContext* ZDContext = static_cast(&CompilerContext); if (ZDContext) { FPaperZDAnimBPCompilerAccess* Access = new FPaperZDAnimBPCompilerAccess(ZDContext); return TUniquePtr(Access); } return TUniquePtr(); } void FPaperZDAnimBPCompilerContext::ForAllSubGraphs(UEdGraph* InGraph, TFunctionRef InPerGraphFunction) { TArray AllGraphs; AllGraphs.Add(InGraph); InGraph->GetAllChildrenGraphs(AllGraphs); for (UEdGraph* CurrGraph : AllGraphs) { InPerGraphFunction(CurrGraph); } }; void FPaperZDAnimBPCompilerContext::PruneIsolatedAnimationNodes(const TArray& RootSet, TArray& GraphNodes) { //Prune any animation node that has been orphaned struct FNodeVisitorDownAnimWires { TSet VisitedNodes; const UPaperZDAnimGraphSchema* Schema; FNodeVisitorDownAnimWires() { 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->IsAnimationDataPin(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 FNodeVisitorDownAnimWires Visitor; for (auto RootIt = RootSet.CreateConstIterator(); RootIt; ++RootIt) { UPaperZDAnimGraphNode_Base* RootNode = *RootIt; Visitor.TraverseNodes(RootNode); } for (int32 NodeIndex = 0; NodeIndex < GraphNodes.Num(); ++NodeIndex) { UPaperZDAnimGraphNode_Base* Node = GraphNodes[NodeIndex]; // Remove every node that hasn't been visited, as they are orphaned and won't be run in runtime if (!Visitor.VisitedNodes.Contains(Node) && !IsNodePure(Node)) { Node->BreakAllNodeLinks(); GraphNodes.RemoveAtSwap(NodeIndex); --NodeIndex; } } } UEdGraphSchema_K2* FPaperZDAnimBPCompilerContext::CreateSchema() { return NewObject(); } void FPaperZDAnimBPCompilerContext::CreateFunctionList() { //Before continuing, we need to make sure all the AnimNotifies have generated their function stubs and are visible on the editor //for this we add them to the AnimBP itself and configure them as generated graphs for the user to fill with logic UpdateAnimNotifyFunctions(); FKismetCompilerContext::CreateFunctionList(); } void FPaperZDAnimBPCompilerContext::UpdateAnimNotifyFunctions() { //If for some reason this AnimBP doesn't have an animation source connected to it, bail-out. if (AnimBP->GetSupportedAnimationSource() == nullptr) { return; } //We will map any notify function to its corresponding name on the class, so they can be found easily when running the notify events for (int32 i = 0; i < AnimBP->FunctionGraphs.Num(); i++) { UPaperZDNotifyGraph* AnimNotifyGraph = Cast(AnimBP->FunctionGraphs[i]); if (AnimNotifyGraph) { if (AnimBP->GetSupportedAnimationSource()->GetRegisteredNotifyNames().Find(AnimNotifyGraph->NotifyName) != INDEX_NONE) { //Notify still exists, pass the actual function name to the generated class TArray EntryPoints; AnimNotifyGraph->GetNodesOfClass(EntryPoints); check(EntryPoints.Num() == 1); NewAnimBlueprintClass->AnimNotifyFunctionMapping.Add(AnimNotifyGraph->NotifyName, EntryPoints[0]->CustomGeneratedFunctionName); } } } } void FPaperZDAnimBPCompilerContext::GetLinkedAnimNodes(UPaperZDAnimGraphNode_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(FPaperZDAnimDataLink::StaticStruct())) { GetLinkedAnimNodes_TraversePin(Pin, LinkedAnimNodes); } } } } } void FPaperZDAnimBPCompilerContext::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 (UPaperZDAnimGraphNode_Base* AnimNode = Cast(OwningNode)) { GetLinkedAnimNodes_ProcessAnimNode(AnimNode, LinkedAnimNodes); } } } void FPaperZDAnimBPCompilerContext::GetLinkedAnimNodes_ProcessAnimNode(UPaperZDAnimGraphNode_Base* AnimNode, TArray& LinkedAnimNodes) const { if (!AllocatedAnimNodes.Contains(AnimNode)) { UPaperZDAnimGraphNode_Base* TrueSourceNode = MessageLog.FindSourceObjectTypeChecked(AnimNode); if (UPaperZDAnimGraphNode_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 FPaperZDAnimBPCompilerContext::MergeUbergraphPagesIn(UEdGraph* Ubergraph) { FKismetCompilerContext::MergeUbergraphPagesIn(Ubergraph); //We don't have support for inheriting classes, but if we had, this is the point where we would avoid merging pages. { // Move all animation graph nodes and associated pure logic chains into the consolidated event graph auto MoveGraph = [this](UEdGraph* InGraph) { if (InGraph->Schema->IsChildOf(UPaperZDAnimGraphSchema::StaticClass())) { // Merge all the animation nodes, contents, etc... into the ubergraph UEdGraph* 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); } }; //Because we want to avoid any potential issues or interactions, we manually add the AnimGraph into the consolidated graph //The graph by this point has been removed as an "UbergraphPage" for this very reason (but will be added after compilation, for visual purposes) //Manually add the AnimationGraph onto the consolidated Ubergraph and process it // //Note: The Animation Graph could be not compatible with the current compiler due to it being loaded from an older version of the plugin, in this case we //avoid compiling the graph and wait for the version patch-up to fix the blueprint. if (AnimBP->IsVersionCompatibleForCompilation()) { MoveGraph(AnimBP->AnimationGraph); //Expand split pins on the consolidated graph before processing nodes ForAllSubGraphs(ConsolidatedEventGraph, [this](UEdGraph* InGraph) { ExpandSplitPins(InGraph); }); //Finally process the animation nodes ProcessAllAnimationNodes(); } } } void FPaperZDAnimBPCompilerContext::ProcessOneFunctionGraph(UEdGraph* SourceGraph, bool bInternalFunction /* = false */) { 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 FKismetCompilerContext::ProcessOneFunctionGraph(SourceGraph, bInternalFunction); } } void FPaperZDAnimBPCompilerContext::SpawnNewClass(const FString& NewClassName) { NewAnimBlueprintClass = FindObject(Blueprint->GetOutermost(), *NewClassName); if (NewAnimBlueprintClass == NULL) { NewAnimBlueprintClass = NewObject(Blueprint->GetOutermost(), FName(*NewClassName), RF_Public | RF_Transactional); } else { // Already existed, but wasn't linked in the Blueprint yet due to load ordering issues FBlueprintCompileReinstancer::Create(NewAnimBlueprintClass); } //Setup the class NewClass = NewAnimBlueprintClass; NewAnimBlueprintClass->SupportedAnimationSource = AnimBP->SupportedAnimationSource; //Call delegates FPaperZDAnimBPCompilerAccess CompilerAccess(this); FPaperZDAnimBPGeneratedClassAccess ClassAccess(NewAnimBlueprintClass); OnStartCompilingClass.Broadcast(NewAnimBlueprintClass, CompilerAccess, ClassAccess); } void FPaperZDAnimBPCompilerContext::OnNewClassSet(UBlueprintGeneratedClass* ClassToUse) { NewAnimBlueprintClass = CastChecked(ClassToUse); } void FPaperZDAnimBPCompilerContext::OnPostCDOCompiled(const UObject::FPostCDOCompiledContext& Context) { //We don't support blueprint inheritance for PaperZD, but adding extra measures if we ever need it is pretty easy for (UPaperZDAnimBPGeneratedClass* ClassWithInputHandlers = NewAnimBlueprintClass; ClassWithInputHandlers != nullptr; ClassWithInputHandlers = Cast(ClassWithInputHandlers->GetSuperClass())) { FPaperZDExposedValueHandler::InitClass(ClassWithInputHandlers->EvaluateGraphExposedInputs, NewAnimBlueprintClass->GetDefaultObject()); ClassWithInputHandlers->CacheRequiredNodes(NewAnimBlueprintClass->GetDefaultObject()); } } void FPaperZDAnimBPCompilerContext::ProcessAllAnimationNodes() { //Obtain all the graph nodes 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 (UPaperZDAnimGraphNode_Base* SourceNode : RootAnimNodeList) { //UPaperZDAnimGraphNode_Base* TrueNode = MessageLog.FindSourceObjectTypeChecked(SourceNode); //TrueNode->BlueprintUsage = EBlueprintUsage::NoProperties; //@NOTE: To fill if we have fast pathing 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); //Notify any delegate that might need to do something before the data has been processed FPaperZDAnimBPCompilerAccess CompilerAccess(this); FPaperZDAnimBPGeneratedClassAccess ClassAccess(NewAnimBlueprintClass); OnPreProcessAnimationNodes.Broadcast(AllSubGraphsAnimNodeList, CompilerAccess, ClassAccess); // Process the animation nodes ProcessAnimationNodes(RootAnimNodeList); //Notify delegates OnPostProcessAnimationNodes.Broadcast(AllSubGraphsAnimNodeList, CompilerAccess, ClassAccess); } else { MessageLog.Error(*LOCTEXT("ExpectedAFunctionEntry_Error", "Expected at least one animation node, but did not find any").ToString()); } } void FPaperZDAnimBPCompilerContext::ExpandSplitPins(UEdGraph* Graph) { //ExpandSplitPins can change the size of the array, so we're better of using an iterator instead for (auto NodeIt = Graph->Nodes.CreateIterator(); NodeIt; ++NodeIt) { UK2Node* K2Node = Cast(*NodeIt); if (K2Node != nullptr) { K2Node->ExpandSplitPins(*this, Graph); } } } void FPaperZDAnimBPCompilerContext::ProcessAnimationNodes(TArray& AnimNodeList) { for (UPaperZDAnimGraphNode_Base* AnimNode : AnimNodeList) { ProcessAnimationNode(AnimNode); } } void FPaperZDAnimBPCompilerContext::ProcessAnimationNode(UPaperZDAnimGraphNode_Base* VisualAnimNode) { // Early out if this node has already been processed if (AllocatedAnimNodes.Contains(VisualAnimNode)) { return; } // Make sure the visual node has a runtime node template (FNode) const UScriptStruct* NodeType = VisualAnimNode->GetFNodeType(); if (NodeType == NULL) { 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(AnimBP, MessageLog); VisualAnimNode->BakeDataDuringCompilation(MessageLog); if (MessageLog.NumErrors != PreValidationErrorCount) { return; } } // Create a property for the node const FString NodeVariableName = ClassScopeNetNameMap.MakeValidName(VisualAnimNode); const UPaperZDAnimGraphSchema* AnimGraphDefaultSchema = GetDefault(); //Create the variable type descriptor FEdGraphPinType NodeVariableType; NodeVariableType.PinCategory = UPaperZDAnimGraphSchema::PC_Struct; NodeVariableType.PinSubCategoryObject = MakeWeakObjectPtr(const_cast(NodeType)); //Create a variable to describe the current node (of type "struct" as the FNode is) FStructProperty* NewProperty = CastField(CreateVariable(FName(*NodeVariableName), NodeVariableType)); if (NewProperty == NULL) { MessageLog.Error(TEXT("Failed to create node property for @@"), VisualAnimNode); } // Register this node with the compile-time data structures const int32 AllocatedIndex = AllocateNodeIndexCounter++; AllocatedAnimNodes.Add(VisualAnimNode, NewProperty); AllocatedNodePropertiesToNodes.Add(NewProperty, VisualAnimNode); AllocatedAnimNodeIndices.Add(VisualAnimNode, AllocatedIndex); AllocatedPropertiesByIndex.Add(AllocatedIndex, NewProperty); UPaperZDAnimGraphNode_Base* TrueSourceObject = MessageLog.FindSourceObjectTypeChecked(VisualAnimNode); SourceNodeToProcessedNodeMap.Add(TrueSourceObject, VisualAnimNode); // Register the slightly more permanent debug information //@TODO: Add this //FPaperZDAnimBPDebugData& NewAnimBlueprintDebugData = NewAnimBlueprintClass->GetAnimBlueprintDebugData(); // NewAnimBlueprintDebugData.NodePropertyToIndexMap.Add(TrueSourceObject, AllocatedIndex); // NewAnimBlueprintDebugData.NodeGuidToIndexMap.Add(TrueSourceObject->NodeGuid, AllocatedIndex); // NewAnimBlueprintDebugData.NodePropertyIndexToNodeMap.Add(AllocatedIndex, TrueSourceObject); // NewAnimBlueprintClass->GetDebugData().RegisterClassPropertyAssociation(TrueSourceObject, NewProperty); FPaperZDAnimBPCompilerAccess CompilerAccess(this); FPaperZDAnimBPGeneratedClassAccess ClassAccess(NewAnimBlueprintClass); VisualAnimNode->ProcessDuringCompilation(CompilerAccess, ClassAccess); } void FPaperZDAnimBPCompilerContext::CopyTermDefaultsToDefaultObject(UObject* DefaultObject) { FKismetCompilerContext::CopyTermDefaultsToDefaultObject(DefaultObject); //Bake the data onto the AnimInstance UPaperZDAnimInstance* AnimInstance = Cast(DefaultObject); if (AnimInstance) { int32 LinkIndexCount = 0; TMap LinkIndexMap; TMap NodeBaseAddresses; // We iterate on the class variables, to make sure that every variable we're setting has been setup on the class // variables that didn't get to the class (nodes), failed for some reason and they could still live on the maps // we also add everything to the "LinkIndexMap" which double checks the node existence in the posterior linking process // // NOTE: The field iterator always returns the data in the same order, the IDs are ordered such as properties will get the same // order when being "linked" on the class at runtime for (TFieldIterator It(AnimInstance->GetClass(), EFieldIteratorFlags::ExcludeSuper); It; ++It) { FProperty* TargetProperty = *It; UPaperZDAnimGraphNode_Base* AnimGraphNode = AllocatedNodePropertiesToNodes.FindRef(TargetProperty); //We should never have null pointers here, but checking to be safe if (AnimGraphNode && TargetProperty) { const FStructProperty* SourceNodeProperty = AnimGraphNode->GetFNodeProperty(); check(SourceNodeProperty != NULL); check(CastFieldChecked(TargetProperty)->Struct == SourceNodeProperty->Struct); uint8* SourcePtr = SourceNodeProperty->ContainerPtrToValuePtr(AnimGraphNode); uint8* DestinationPtr = TargetProperty->ContainerPtrToValuePtr(AnimInstance); //Special handling if (UPaperZDAnimGraphNode_Sink* RootNode = ExactCast(AnimGraphNode)) { // patch graph name into root nodes FPaperZDAnimNode_Sink NewRoot = *reinterpret_cast(SourcePtr); NewRoot.Name = Cast(MessageLog.FindSourceObject(RootNode))->GetGraph()->GetFName(); TargetProperty->CopyCompleteValue(DestinationPtr, &NewRoot); } else { TargetProperty->CopyCompleteValue(DestinationPtr, SourcePtr); } LinkIndexMap.Add(AnimGraphNode, LinkIndexCount); NodeBaseAddresses.Add(AnimGraphNode, DestinationPtr); ++LinkIndexCount; } } //Finally wire up animation data links for (const FPaperZDAnimDataLinkRecord& LinkRecord : LinkRecords) { UPaperZDAnimGraphNode_Base* LinkingNode = LinkRecord.GetLinkingNode(); UPaperZDAnimGraphNode_Base* LinkedNode = LinkRecord.GetLinkedNode(); //This check if to be doubly certain that the nodes we're linking got past the compilation process into the class if (LinkIndexMap.Contains(LinkingNode) && LinkIndexMap.Contains(LinkedNode)) { const int32 SourceNodeIdx = LinkIndexMap.FindChecked(LinkingNode); const int32 LinkedNodeIdx = LinkIndexMap.FindChecked(LinkedNode); uint8* DestinationPtr = NodeBaseAddresses.FindChecked(LinkingNode); LinkRecord.PatchLinkIndex(DestinationPtr, LinkedNodeIdx, SourceNodeIdx); } } } //Call delegates FPaperZDAnimBPCompilerAccess CompilerAccess(this); FPaperZDAnimBPGeneratedClassAccess ClassAccess(NewAnimBlueprintClass); OnCopyTermDefaultsToDefaultObject.Broadcast(DefaultObject, CompilerAccess, ClassAccess); } void FPaperZDAnimBPCompilerContext::PreCompile() { //Let the AnimBP know that it's gonna be compiled, so it prepares its graphs AnimBP->OnPreCompile(); } void FPaperZDAnimBPCompilerContext::PostCompile() { //@TODO: SHould we purge? this has issues if the sequence hasn't been loaded yet //Make sure the AnimSequences are up-to-date and don't contain any invalid notify data // for (auto Seq : AnimBP->GetAnimSequences()) // { // Seq->PurgeInvalidCustomNotifies(AnimBP->RegisteredNotifyNames); // } //Let the AnimBP know that we finished compilation AnimBP->OnPostCompile(); } void FPaperZDAnimBPCompilerContext::EnsureProperGeneratedClass(UClass*& InTargetClass) { if (InTargetClass && !((UObject*)InTargetClass)->IsA(UPaperZDAnimBPGeneratedClass::StaticClass())) { FKismetCompilerUtilities::ConsignToOblivion(InTargetClass, Blueprint->bIsRegeneratingOnLoad); //Patch: This class is no longer valid and will be purged, but re-instancing code requires the class CDO which is nulled when the class is purged. //The re-instancer will then request the creation of a new CDO which triggers an ensure due to the class' layout //changing (the class flag never gets cleared out due to the class being dead). //Manually clearing the flag removes this behavior and has no consequences due to the class no longer being used anyway. InTargetClass->bLayoutChanging = false; InTargetClass = nullptr; } } void FPaperZDAnimBPCompilerContext::CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& InOldCDO) { FKismetCompilerContext::CleanAndSanitizeClass(ClassToClean, InOldCDO); //Should be using the same pointers check(ClassToClean == NewClass && NewAnimBlueprintClass == NewClass); //Setup the supported animation class, so any node can check against it NewAnimBlueprintClass->SupportedAnimationSource = AnimBP->SupportedAnimationSource; //Call delegates FPaperZDAnimBPCompilerAccess CompilerAccess(this); FPaperZDAnimBPGeneratedClassAccess ClassAccess(NewAnimBlueprintClass); OnStartCompilingClass.Broadcast(NewAnimBlueprintClass, CompilerAccess, ClassAccess); } void FPaperZDAnimBPCompilerContext::FinishCompilingClass(UClass* Class) { FKismetCompilerContext::FinishCompilingClass(Class); //Call delegates FPaperZDAnimBPCompilerAccess CompilerAccess(this); FPaperZDAnimBPGeneratedClassAccess ClassAccess(NewAnimBlueprintClass); OnFinishCompilingClass.Broadcast(NewAnimBlueprintClass, CompilerAccess, ClassAccess); } bool FPaperZDAnimBPCompilerContext::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 FPaperZDAnimBPCompilerContext::PostExpansionStep(const UEdGraph* Graph) { FKismetCompilerContext::PostExpansionStep(Graph); // Cull out all anim nodes as they dont contribute to execution at all for (int32 NodeIndex = 0; NodeIndex < ConsolidatedEventGraph->Nodes.Num(); ++NodeIndex) { if (UPaperZDAnimGraphNode_Base* Node = Cast(ConsolidatedEventGraph->Nodes[NodeIndex])) { Node->BreakAllNodeLinks(); ConsolidatedEventGraph->Nodes.RemoveAtSwap(NodeIndex); --NodeIndex; } } //Call delegates FPaperZDAnimBPCompilerAccess CompilerAccess(this); FPaperZDAnimBPGeneratedClassAccess ClassAccess(NewAnimBlueprintClass); OnPostExpansionStep.Broadcast(Graph, CompilerAccess, ClassAccess); } #undef LOCTEXT_NAMESPACE