// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimBlueprintExtension_Base.h" #include "AnimBlueprintExtension.h" #include "AnimBlueprintExtension_PropertyAccess.h" #include "AnimGraphNode_Base.h" #include "AnimationGraphSchema.h" #include "AnimGraphNode_CustomProperty.h" #include "K2Node_CustomEvent.h" #include "K2Node_VariableSet.h" #include "K2Node_StructMemberSet.h" #include "K2Node_StructMemberGet.h" #include "K2Node_CallArrayFunction.h" #include "Kismet/KismetArrayLibrary.h" #include "K2Node_Knot.h" #include "String/ParseTokens.h" #include "K2Node_VariableGet.h" #include "K2Node_BreakStruct.h" #include "K2Node_MakeStruct.h" #include "Kismet/KismetMathLibrary.h" #include "K2Node_TransitionRuleGetter.h" #include "Animation/AnimNode_LinkedAnimGraph.h" #include "Kismet2/BlueprintEditorUtils.h" #include "PropertyAccessCompilerHandler.h" #include "IPropertyAccessEditor.h" #include "IPropertyAccessCompiler.h" #include "IAnimBlueprintGeneratedClassCompiledData.h" #include "IAnimBlueprintCompilerCreationContext.h" #include "IAnimBlueprintCompilationContext.h" #include "IAnimBlueprintCopyTermDefaultsContext.h" #include "IAnimBlueprintPostExpansionStepContext.h" #include "IAnimBlueprintCompilationBracketContext.h" #include "Features/IModularFeatures.h" #include "Animation/ExposedValueHandler.h" #include "AnimGraphNodeBinding_Base.h" #define LOCTEXT_NAMESPACE "AnimBlueprintExtension_Base" DECLARE_CYCLE_STAT(TEXT("Create Evaluation Handler"), EAnimBlueprintCompilerStats_CreateEvaluationHandler, STATGROUP_KismetCompiler ) DECLARE_CYCLE_STAT(TEXT("Create Evaluation Handler - Node Properties"), EAnimBlueprintCompilerStats_CreateEvaluationHandler_NodeProperties, STATGROUP_KismetCompiler ) DECLARE_CYCLE_STAT(TEXT("Create Evaluation Handler - Create Assignment Node"), EAnimBlueprintCompilerStats_CreateEvaluationHandler_CreateAssignmentNode, STATGROUP_KismetCompiler ) DECLARE_CYCLE_STAT(TEXT("Create Evaluation Handler - Create Instance Assignment Node"), EAnimBlueprintCompilerStats_CreateEvaluationHandler_CreateInstanceAssignmentNode, STATGROUP_KismetCompiler ) DECLARE_CYCLE_STAT(TEXT("Create Evaluation Handler - Create Instance Assignment Node - Build Property List"), EAnimBlueprintCompilerStats_CreateEvaluationHandler_CreateInstanceAssignmentNode_BuildPropertyList, STATGROUP_KismetCompiler ) DECLARE_CYCLE_STAT(TEXT("Create Evaluation Handler - Create Instance Assignment Node - Create Visible Pins"), EAnimBlueprintCompilerStats_CreateEvaluationHandler_CreateInstanceAssignmentNode_CreateVisiblePins, STATGROUP_KismetCompiler ) void UAnimBlueprintExtension_Base::HandleCopyTermDefaultsToDefaultObject(UObject* InDefaultObject, IAnimBlueprintCopyTermDefaultsContext& InCompilationContext, IAnimBlueprintExtensionCopyTermDefaultsContext& InPerExtensionContext) { UAnimInstance* DefaultAnimInstance = Cast(InDefaultObject); if(DefaultAnimInstance) { Subsystem.PatchValueHandlers(DefaultAnimInstance->GetClass()); // Update blueprint usage of all graph nodes that have properties exposed for (const TPair& EvaluationHandlerPair : PerNodeStructEvalHandlers) { if (EvaluationHandlerPair.Value.bHasProperties && EvaluationHandlerPair.Value.ServicedProperties.Num() == 0) { UAnimGraphNode_Base* Node = CastChecked(EvaluationHandlerPair.Key); UAnimGraphNode_Base* TrueNode = InCompilationContext.GetMessageLog().FindSourceObjectTypeChecked(Node); TrueNode->BlueprintUsage = EBlueprintUsage::DoesNotUseBlueprint; } } for(const FEvaluationHandlerRecord& EvaluationHandler : ValidEvaluationHandlerList) { if(EvaluationHandler.AnimGraphNode) { UAnimGraphNode_Base* Node = CastChecked(EvaluationHandler.AnimGraphNode); UAnimGraphNode_Base* TrueNode = InCompilationContext.GetMessageLog().FindSourceObjectTypeChecked(Node); if(EvaluationHandler.EvaluationHandlerIdx != INDEX_NONE && EvaluationHandler.ServicedProperties.Num() > 0) { const FAnimNodeSinglePropertyHandler& Handler = EvaluationHandler.ServicedProperties.CreateConstIterator()->Value; check(Handler.CopyRecords.Num() > 0); bool bHandlerUsesBlueprint = false; if(Subsystem.ExposedValueHandlers[EvaluationHandler.EvaluationHandlerIdx].GetHandlerStruct()->IsChildOf(FAnimNodeExposedValueHandler_Base::StaticStruct())) { const FAnimNodeExposedValueHandler_Base* BaseHandler = static_cast(Subsystem.ExposedValueHandlers[EvaluationHandler.EvaluationHandlerIdx].GetHandler()); bHandlerUsesBlueprint = BaseHandler->BoundFunction != NAME_None; } TrueNode->BlueprintUsage = bHandlerUsesBlueprint ? EBlueprintUsage::UsesBlueprint : EBlueprintUsage::DoesNotUseBlueprint; #if WITH_EDITORONLY_DATA // ANIMINST_PostCompileValidation const bool bWarnAboutBlueprintUsage = InCompilationContext.GetAnimBlueprint()->bWarnAboutBlueprintUsage || DefaultAnimInstance->PCV_ShouldWarnAboutNodesNotUsingFastPath(); const bool bNotifyAboutBlueprintUsage = DefaultAnimInstance->PCV_ShouldNotifyAboutNodesNotUsingFastPath(); #else const bool bWarnAboutBlueprintUsage = InCompilationContext.GetAnimBlueprint()->bWarnAboutBlueprintUsage; const bool bNotifyAboutBlueprintUsage = false; #endif if ((TrueNode->BlueprintUsage == EBlueprintUsage::UsesBlueprint) && (bWarnAboutBlueprintUsage || bNotifyAboutBlueprintUsage)) { const FString MessageString = LOCTEXT("BlueprintUsageWarning", "Node @@ uses Blueprint to update its values, access member variables directly or use a constant value for better performance.").ToString(); if (bWarnAboutBlueprintUsage) { InCompilationContext.GetMessageLog().Warning(*MessageString, Node); } else { InCompilationContext.GetMessageLog().Note(*MessageString, Node); } } } } } } } void UAnimBlueprintExtension_Base::HandlePostExpansionStep(const UEdGraph* InGraph, IAnimBlueprintPostExpansionStepContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { UEdGraph* ConsolidatedEventGraph = InCompilationContext.GetConsolidatedEventGraph(); if(InGraph == ConsolidatedEventGraph) { // Skip fast-path generation if the property access system is unavailable. // Note that this wont prevent property access 'binding' copy records from running, only // old-style 'fast-path' records that are derived from BP pure chains if(IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) { for(FEvaluationHandlerRecord& HandlerRecord : ValidEvaluationHandlerList) { HandlerRecord.BuildFastPathCopyRecords(InCompilationContext); if(HandlerRecord.IsFastPath()) { for(UEdGraphNode* CustomEventNode : HandlerRecord.CustomEventNodes) { // Remove custom event nodes as we dont need it any more ConsolidatedEventGraph->RemoveNode(CustomEventNode); } } } } // Cull out all anim nodes as they dont contribute to execution at all for (int32 NodeIndex = 0; NodeIndex < ConsolidatedEventGraph->Nodes.Num(); ++NodeIndex) { if(UAnimGraphNode_Base* Node = Cast(ConsolidatedEventGraph->Nodes[NodeIndex])) { Node->BreakAllNodeLinks(); ConsolidatedEventGraph->Nodes.RemoveAtSwap(NodeIndex); --NodeIndex; } } } } void UAnimBlueprintExtension_Base::PatchEvaluationHandlers(const UClass* InClass, IAnimBlueprintCompilationBracketContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { // Exposed value handlers indices must match the index of anim node properties, // so we iterate over anim node properties here when patching up const int32 NumAllocatedNodes = InCompilationContext.GetAllocatedAnimNodeIndices().Num(); for(const TPair& GraphNodePair : InCompilationContext.GetAllocatedAnimNodeIndices()) { if(int32* EvaluationHandlerIndexPtr = ValidEvaluationHandlerMap.Find(GraphNodePair.Key)) { // Indices here are in reverse order with respect to iterated properties as properties are prepended to the linked list when they are added const int32 NodePropertyIndex = NumAllocatedNodes - 1 - GraphNodePair.Value; FEvaluationHandlerRecord& EvaluationHandlerRecord = ValidEvaluationHandlerList[*EvaluationHandlerIndexPtr]; EvaluationHandlerRecord.EvaluationHandlerIdx = NodePropertyIndex; EvaluationHandlerRecord.PatchAnimNodeExposedValueHandler(InClass, InCompilationContext); } } } void UAnimBlueprintExtension_Base::HandleStartCompilingClass(const UClass* InClass, IAnimBlueprintCompilationBracketContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { PerNodeStructEvalHandlers.Empty(); ValidEvaluationHandlerList.Empty(); ValidEvaluationHandlerMap.Empty(); HandlerFunctionNames.Empty(); PreLibraryCompiledDelegateHandle.Reset(); PostLibraryCompiledDelegateHandle.Reset(); UAnimBlueprintExtension_PropertyAccess* PropertyAccessExtension = UAnimBlueprintExtension::GetExtension(GetAnimBlueprint()); if(PropertyAccessExtension) { PreLibraryCompiledDelegateHandle = PropertyAccessExtension->OnPreLibraryCompiled().AddLambda([this, PropertyAccessExtension, InClass]() { if(IModularFeatures::Get().IsModularFeatureAvailable("PropertyAccessEditor")) { IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); // Build the classes property access library before the library is compiled for(FEvaluationHandlerRecord& HandlerRecord : ValidEvaluationHandlerList) { for(TPair& PropertyHandler : HandlerRecord.ServicedProperties) { for(FPropertyCopyRecord& Record : PropertyHandler.Value.CopyRecords) { if(Record.IsFastPath()) { Record.LibraryHandle = PropertyAccessExtension->AddCopy(Record.SourcePropertyPath, Record.DestPropertyPath, Record.BindingContextId, HandlerRecord.AnimGraphNode); } } } } } PropertyAccessExtension->OnPreLibraryCompiled().Remove(PreLibraryCompiledDelegateHandle); }); PostLibraryCompiledDelegateHandle = PropertyAccessExtension->OnPostLibraryCompiled().AddLambda([this, PropertyAccessExtension, InClass](IAnimBlueprintCompilationBracketContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { for(FEvaluationHandlerRecord& HandlerRecord : ValidEvaluationHandlerList) { UAnimGraphNode_Base* OriginalNode = Cast(InCompilationContext.GetMessageLog().FindSourceObject(HandlerRecord.AnimGraphNode)); // Map global copy index to batched indices for(TPair& PropertyHandler : HandlerRecord.ServicedProperties) { for(FPropertyCopyRecord& CopyRecord : PropertyHandler.Value.CopyRecords) { if(CopyRecord.IsFastPath()) { CopyRecord.LibraryCompiledHandle = PropertyAccessExtension->GetCompiledHandle(CopyRecord.LibraryHandle); // Push compiled desc back to original node for feedback FName BindingName = CopyRecord.DestProperty->GetFName(); if(CopyRecord.DestArrayIndex != INDEX_NONE) { BindingName.SetNumber(CopyRecord.DestArrayIndex + 1); } if(UAnimGraphNodeBinding_Base* Binding = Cast(OriginalNode->GetMutableBinding())) { if(FAnimGraphNodePropertyBinding* PropertyBinding = Binding->PropertyBindings.Find(BindingName)) { if(CopyRecord.LibraryCompiledHandle.IsValid()) { PropertyBinding->CompiledContext = UAnimBlueprintExtension_PropertyAccess::GetCompiledHandleContext(CopyRecord.LibraryCompiledHandle); PropertyBinding->CompiledContextDesc = UAnimBlueprintExtension_PropertyAccess::GetCompiledHandleContextDesc(CopyRecord.LibraryCompiledHandle); } else { PropertyBinding->CompiledContext = FText::GetEmpty(); PropertyBinding->CompiledContextDesc = FText::GetEmpty(); } } } } } } } PatchEvaluationHandlers(InClass, InCompilationContext, OutCompiledData); PropertyAccessExtension->OnPostLibraryCompiled().Remove(PostLibraryCompiledDelegateHandle); }); } } void UAnimBlueprintExtension_Base::HandleFinishCompilingClass(const UClass* InClass, IAnimBlueprintCompilationBracketContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { UAnimBlueprintExtension_PropertyAccess* PropertyAccessExtension = UAnimBlueprintExtension::GetExtension(GetAnimBlueprint()); if(PropertyAccessExtension == nullptr) { // Without the property access system we need to patch generated function names here PatchEvaluationHandlers(InClass, InCompilationContext, OutCompiledData); } } void UAnimBlueprintExtension_Base::ProcessPosePins(UAnimGraphNode_Base* InNode, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); FStructProperty* NodeProperty = CastFieldChecked(InCompilationContext.GetAllocatedPropertiesByNode().FindChecked(InNode)); for (auto SourcePinIt = InNode->Pins.CreateIterator(); SourcePinIt; ++SourcePinIt) { UEdGraphPin* SourcePin = *SourcePinIt; // Register pose links for future use if ((SourcePin->Direction == EGPD_Input) && (AnimGraphDefaultSchema->IsPosePin(SourcePin->PinType))) { // Input pose pin, going to need to be linked up FPoseLinkMappingRecord LinkRecord = InNode->GetLinkIDLocation(NodeProperty->Struct, SourcePin); if (LinkRecord.IsValid()) { InCompilationContext.AddPoseLinkMappingRecord(LinkRecord); } else { //@TODO: ANIMREFACTOR: It's probably OK to have certain pins ignored eventually, but this is very helpful during development InCompilationContext.GetMessageLog().Note(TEXT("@@ was visible but ignored"), SourcePin); } } } } void UAnimBlueprintExtension_Base::ProcessNonPosePins(UAnimGraphNode_Base* InNode, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData, EPinProcessingFlags InFlags) { const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); FStructProperty* NodeProperty = CastFieldChecked(InCompilationContext.GetAllocatedPropertiesByNode().FindChecked(InNode)); for (auto SourcePinIt = InNode->Pins.CreateIterator(); SourcePinIt; ++SourcePinIt) { UEdGraphPin* SourcePin = *SourcePinIt; bool bConsumed = false; if ((SourcePin->Direction == EGPD_Input) && (AnimGraphDefaultSchema->IsPosePin(SourcePin->PinType))) { bConsumed = true; } else if(!InNode->ShouldCreateStructEvalHandlers() || !EnumHasAnyFlags(InFlags, EPinProcessingFlags::All)) { bConsumed = true; } else { FEvaluationHandlerRecord& EvalHandler = PerNodeStructEvalHandlers.FindOrAdd(InNode); // The property source for our data, either the struct property for an anim node, or the // owning anim instance if using a linked instance node. FProperty* SourcePinProperty = nullptr; int32 SourceArrayIndex = INDEX_NONE; bool bInstancePropertyExists = false; // We have special handling below if we're targeting a linked instance instead of our own instance properties UAnimGraphNode_CustomProperty* CustomPropertyNode = Cast(InNode); InNode->GetPinAssociatedProperty(NodeProperty->Struct, SourcePin, /*out*/ SourcePinProperty, /*out*/ SourceArrayIndex); // Does this pin have an associated evaluation handler? if(!SourcePinProperty && CustomPropertyNode) { // Custom property nodes use instance properties not node properties as they aren't UObjects // and we can't store non-native properties there CustomPropertyNode->GetInstancePinProperty(InCompilationContext, SourcePin, SourcePinProperty); bInstancePropertyExists = true; } if (SourcePinProperty != NULL) { EvalHandler.bHasProperties = true; if (SourcePin->LinkedTo.Num() == 0) { if(EnumHasAnyFlags(InFlags, EPinProcessingFlags::Constants)) { // Literal that can be pushed into the CDO instead of re-evaluated every frame bConsumed = true; } } else { if (EnumHasAnyFlags(InFlags, EPinProcessingFlags::BlueprintHandlers)) { // Dynamic value that needs to be wired up and evaluated each frame const FString& EvaluationHandlerStr = SourcePinProperty->GetMetaData(AnimGraphDefaultSchema->NAME_OnEvaluate); FName EvaluationHandlerName(*EvaluationHandlerStr); if (EvaluationHandlerName != NAME_None) { // warn that NAME_OnEvaluate is deprecated: InCompilationContext.GetMessageLog().Warning(*LOCTEXT("OnEvaluateDeprecated", "OnEvaluate meta data is deprecated, found on @@").ToString(), SourcePinProperty); } ensure(EvalHandler.NodeVariableProperty == nullptr || EvalHandler.NodeVariableProperty == NodeProperty); EvalHandler.AnimGraphNode = InNode; EvalHandler.NodeVariableProperty = NodeProperty; EvalHandler.RegisterPin(SourcePin, SourcePinProperty, SourceArrayIndex, EnumHasAnyFlags(InFlags, EPinProcessingFlags::PropertyAccessFastPath)); // if it's not instance property, ensure we mark it EvalHandler.bServicesNodeProperties = EvalHandler.bServicesNodeProperties || !bInstancePropertyExists; if (CustomPropertyNode) { EvalHandler.bServicesInstanceProperties = EvalHandler.bServicesInstanceProperties || bInstancePropertyExists; FAnimNodeSinglePropertyHandler* SinglePropHandler = EvalHandler.ServicedProperties.Find(SourcePinProperty->GetFName()); check(SinglePropHandler); // Should have been added in RegisterPin // Flag that the target property is actually on the instance class and not the node SinglePropHandler->bInstanceIsTarget = bInstancePropertyExists; } bConsumed = true; } } UEdGraphPin* TrueSourcePin = InCompilationContext.GetMessageLog().FindSourcePin(SourcePin); if (TrueSourcePin) { OutCompiledData.GetBlueprintDebugData().RegisterClassPropertyAssociation(TrueSourcePin, SourcePinProperty); } } } if (!bConsumed && (SourcePin->Direction == EGPD_Input) && !AnimGraphDefaultSchema->IsPosePin(SourcePin->PinType)) { //@TODO: ANIMREFACTOR: It's probably OK to have certain pins ignored eventually, but this is very helpful during development InCompilationContext.GetMessageLog().Note(TEXT("@@ was visible but ignored"), SourcePin); } } if (EnumHasAnyFlags(InFlags, EPinProcessingFlags::PropertyAccessBindings)) { // Add any property bindings if (UAnimGraphNodeBinding_Base* Binding = Cast(InNode->GetMutableBinding())) { for(auto Iter = Binding->PropertyBindings.CreateIterator(); Iter; ++Iter) { if(Iter.Value().bIsBound) { FEvaluationHandlerRecord& EvalHandler = PerNodeStructEvalHandlers.FindOrAdd(InNode); EvalHandler.AnimGraphNode = InNode; // for array properties we need to account for the extra FName number FName ComparisonName = Iter.Key(); ComparisonName.SetNumber(0); if (FProperty* Property = FindFProperty(NodeProperty->Struct, ComparisonName)) { EvalHandler.NodeVariableProperty = NodeProperty; EvalHandler.bServicesNodeProperties = true; EvalHandler.RegisterPropertyBinding(Property, Iter.Value()); } else if(FProperty* ClassProperty = FindFProperty(InCompilationContext.GetBlueprint()->SkeletonGeneratedClass, Iter.Value().PropertyName)) { EvalHandler.NodeVariableProperty = NodeProperty; EvalHandler.bServicesInstanceProperties = true; EvalHandler.RegisterPropertyBinding(ClassProperty, Iter.Value()); } else { // Binding is no longer valid, remove it Iter.RemoveCurrent(); } } } } } } void UAnimBlueprintExtension_Base::CreateEvaluationHandlerForNode(IAnimBlueprintCompilationContext& InCompilationContext, UAnimGraphNode_Base* InNode) { if(FEvaluationHandlerRecord* RecordPtr = PerNodeStructEvalHandlers.Find(InNode)) { // Generate a new event to update the value of these properties FEvaluationHandlerRecord& Record = *RecordPtr; if (Record.NodeVariableProperty) { CreateEvaluationHandler(InCompilationContext, InNode, Record); RedirectPropertyAccesses(InCompilationContext, InNode, Record); int32 NewIndex = ValidEvaluationHandlerList.Add(Record); ValidEvaluationHandlerMap.Add(InNode, NewIndex); } } } // Optional pin manager used to optimize the creation of internal struct member set nodes struct FInternalOptionalPinManager : public FOptionalPinManager { FInternalOptionalPinManager(UAnimGraphNode_Base* InNode, FStructProperty* InNodeProperty, IAnimBlueprintCompilationContext& InCompilationContext) : Node(InNode) , NodeProperty(InNodeProperty) , CompilationContext(InCompilationContext) {} void BuildPropertyList(TArray& Properties, UStruct* SourceStruct) { // Build optional pins for all properties for(TFieldIterator It(SourceStruct); It; ++It) { FOptionalPinFromProperty& OptionalPin = Properties.AddDefaulted_GetRef(); OptionalPin.PropertyName = It->GetFName(); } // Then expose only those that have records for this node for(TFieldIterator It(NodeProperty->Struct); It; ++It) { if(const IAnimBlueprintCompilationContext::FFoldedPropertyRecord* FoldedPropertyRecord = CompilationContext.GetFoldedPropertyRecord(Node, It->GetFName())) { // Dont expose array properties here - they are handled by a struct member get-by-ref if(!FoldedPropertyRecord->bIsOnClass && !FoldedPropertyRecord->GeneratedProperty->IsA()) { FOptionalPinFromProperty& OptionalPin = Properties[Properties.Num() - 1 - FoldedPropertyRecord->PropertyIndex]; check(OptionalPin.PropertyName == FoldedPropertyRecord->GeneratedProperty->GetFName()); OptionalPin.bShowPin = true; } } } } // Duplicated & re-worked from base class (because we are never re-creating) to optimize our case void CreateVisiblePinsEx(TArray& Properties, UStruct* SourceStruct, EEdGraphPinDirection Direction, UK2Node* TargetNode) { const UEdGraphSchema_K2* Schema = GetDefault(); for (FOptionalPinFromProperty& PropertyEntry : Properties) { if (PropertyEntry.bShowPin) { if (FProperty* OuterProperty = FindFieldChecked(SourceStruct, PropertyEntry.PropertyName)) { // Not an array property FEdGraphPinType PinType; if (Schema->ConvertPropertyToPinType(OuterProperty, /*out*/ PinType)) { // Create the pin const FName PinName = PropertyEntry.PropertyName; UEdGraphPin* NewPin = TargetNode->CreatePin(Direction, PinType, PinName); NewPin->PinFriendlyName = FText::FromString(PropertyEntry.PropertyFriendlyName.IsEmpty() ? PinName.ToString() : PropertyEntry.PropertyFriendlyName); NewPin->bNotConnectable = !PropertyEntry.bIsSetValuePinVisible; NewPin->bDefaultValueIsIgnored = !PropertyEntry.bIsSetValuePinVisible; if (FEnumProperty* EnumProperty = CastField(OuterProperty)) { NewPin->DefaultValue = EnumProperty->GetEnum()->GetNameStringByIndex(0); } else if (FByteProperty* ByteProperty = CastField(OuterProperty)) { if (ByteProperty->IsEnum()) { NewPin->DefaultValue = ByteProperty->GetIntPropertyEnum()->GetNameStringByIndex(0); } } Schema->ConstructBasicPinTooltip(*NewPin, PropertyEntry.PropertyTooltip, NewPin->PinToolTip); } } } } } UAnimGraphNode_Base* Node; FStructProperty* NodeProperty; IAnimBlueprintCompilationContext& CompilationContext; }; void UAnimBlueprintExtension_Base::CreateEvaluationHandler(IAnimBlueprintCompilationContext& InCompilationContext, UAnimGraphNode_Base* InNode, FEvaluationHandlerRecord& Record) { BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_CreateEvaluationHandler); // Shouldn't create a handler if there is nothing to work with check(Record.ServicedProperties.Num() > 0); check(Record.NodeVariableProperty != NULL); const UEdGraphSchema_K2* K2Schema = GetDefault(); const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); // Use the node GUID for a stable name across compiles FString FunctionName = FString::Printf(TEXT("%s_%s_%s_%s"), *AnimGraphDefaultSchema->DefaultEvaluationHandlerName.ToString(), *InNode->GetOuter()->GetName(), *InNode->GetClass()->GetName(), *InNode->NodeGuid.ToString()); Record.HandlerFunctionName = FName(*FunctionName); // check function name isnt already used (data exists that can contain duplicate GUIDs) and apply a numeric extension until it is unique int32 ExtensionIndex = 0; FName* ExistingName = HandlerFunctionNames.Find(Record.HandlerFunctionName); while (ExistingName != nullptr) { FunctionName = FString::Printf(TEXT("%s_%s_%s_%s_%d"), *AnimGraphDefaultSchema->DefaultEvaluationHandlerName.ToString(), *InNode->GetOuter()->GetName(), *InNode->GetClass()->GetName(), *InNode->NodeGuid.ToString(), ExtensionIndex); Record.HandlerFunctionName = FName(*FunctionName); ExistingName = HandlerFunctionNames.Find(Record.HandlerFunctionName); ExtensionIndex++; } HandlerFunctionNames.Add(Record.HandlerFunctionName); // Add a custom event in the graph UK2Node_CustomEvent* CustomEventNode = InCompilationContext.SpawnIntermediateNode(InNode, InCompilationContext.GetConsolidatedEventGraph()); CustomEventNode->bInternalEvent = true; CustomEventNode->CustomFunctionName = Record.HandlerFunctionName; CustomEventNode->AllocateDefaultPins(); Record.CustomEventNodes.Add(CustomEventNode); // The ExecChain is the current exec output pin in the linear chain UEdGraphPin* ExecChain = K2Schema->FindExecutionPin(*CustomEventNode, EGPD_Output); if (Record.bServicesInstanceProperties) { // Need to create a variable set call for each serviced property in the handler for (TPair& PropHandlerPair : Record.ServicedProperties) { FAnimNodeSinglePropertyHandler& PropHandler = PropHandlerPair.Value; FName PropertyName = PropHandlerPair.Key; // We only want to deal with instance targets in here if (PropHandler.bInstanceIsTarget) { for (FPropertyCopyRecord& CopyRecord : PropHandler.CopyRecords) { if(CopyRecord.DestPin) { // New set node for the property UK2Node_VariableSet* VarAssignNode = InCompilationContext.SpawnIntermediateNode(InNode, InCompilationContext.GetConsolidatedEventGraph()); VarAssignNode->VariableReference.SetSelfMember(CopyRecord.DestProperty->GetFName()); VarAssignNode->AllocateDefaultPins(); Record.CustomEventNodes.Add(VarAssignNode); // Wire up the exec line, and update the end of the chain UEdGraphPin* ExecVariablesIn = K2Schema->FindExecutionPin(*VarAssignNode, EGPD_Input); ExecChain->MakeLinkTo(ExecVariablesIn); ExecChain = K2Schema->FindExecutionPin(*VarAssignNode, EGPD_Output); // Find the property pin on the set node and configure for (UEdGraphPin* TargetPin : VarAssignNode->Pins) { FName PinPropertyName(TargetPin->PinName); if (PinPropertyName == PropertyName) { // This is us, wire up the variable UEdGraphPin* DestPin = CopyRecord.DestPin; // Copy the data (link up to the source nodes) TargetPin->CopyPersistentDataFromOldPin(*DestPin); InCompilationContext.GetMessageLog().NotifyIntermediatePinCreation(TargetPin, DestPin); break; } } } } } } } if (Record.bServicesNodeProperties) { BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_CreateEvaluationHandler_NodeProperties); UK2Node_StructMemberSet* AssignmentNode; { BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_CreateEvaluationHandler_CreateAssignmentNode); // Create a struct member write node to store the parameters into the animation node AssignmentNode = InCompilationContext.SpawnIntermediateNode(InNode, InCompilationContext.GetConsolidatedEventGraph()); AssignmentNode->VariableReference.SetSelfMember(Record.NodeVariableProperty->GetFName()); AssignmentNode->StructType = Record.NodeVariableProperty->Struct; AssignmentNode->AllocateExecPins(); // Simple FOptionalPinManager that exposes all pins. The default used in UK2Node_StructMemberSet::AllocateDefaultPins will hide any that // are PinHiddenByDefault, so for this instance we dont want that as users may have exposed some pins struct FAssignmentNodeOptionalPinManager : public FOptionalPinManager { // FOptionalPinManager Interface virtual void GetRecordDefaults(FProperty* TestProperty, FOptionalPinFromProperty& Record) const override { // Pins are always visible Record.bCanToggleVisibility = true; Record.bShowPin = true; } }; FAssignmentNodeOptionalPinManager OptionalPinManager; OptionalPinManager.RebuildPropertyList(AssignmentNode->ShowPinForProperties, AssignmentNode->StructType); OptionalPinManager.CreateVisiblePins(AssignmentNode->ShowPinForProperties, AssignmentNode->StructType, EGPD_Input, AssignmentNode); Record.CustomEventNodes.Add(AssignmentNode); } // If we have folded properties we will need to set members on the classes generated mutable data block const FStructProperty* MutableDataProperty = InCompilationContext.GetMutableDataProperty(); UK2Node_StructMemberSet* InstanceAssignmentNode = nullptr; if(InCompilationContext.IsAnimGraphNodeFolded(InNode) && MutableDataProperty != nullptr) { BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_CreateEvaluationHandler_CreateInstanceAssignmentNode); InstanceAssignmentNode = InCompilationContext.SpawnIntermediateNode(InNode, InCompilationContext.GetConsolidatedEventGraph()); InstanceAssignmentNode->VariableReference.SetSelfMember(MutableDataProperty->GetFName()); InstanceAssignmentNode->StructType = MutableDataProperty->Struct; // We build this struct member set node using specialized logic to optimize its creation // as it can have 1000's of properties harvested from animation nodes InstanceAssignmentNode->AllocateExecPins(); { FInternalOptionalPinManager OptionalPinManager(InNode, InNode->GetFNodeProperty(), InCompilationContext); { BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_CreateEvaluationHandler_CreateInstanceAssignmentNode_BuildPropertyList); OptionalPinManager.BuildPropertyList(InstanceAssignmentNode->ShowPinForProperties, MutableDataProperty->Struct); } { BP_SCOPED_COMPILER_EVENT_STAT(EAnimBlueprintCompilerStats_CreateEvaluationHandler_CreateInstanceAssignmentNode_CreateVisiblePins); OptionalPinManager.CreateVisiblePinsEx(InstanceAssignmentNode->ShowPinForProperties, MutableDataProperty->Struct, EGPD_Input, InstanceAssignmentNode); } } Record.CustomEventNodes.Add(InstanceAssignmentNode); } // Run thru each property TSet PropertiesBeingSet; for (auto TargetPinIt = AssignmentNode->Pins.CreateIterator(); TargetPinIt; ++TargetPinIt) { UEdGraphPin* TargetPin = *TargetPinIt; FName PropertyName(TargetPin->PinName); // Does it get serviced by this handler? if (FAnimNodeSinglePropertyHandler* SourceInfo = Record.ServicedProperties.Find(PropertyName)) { // Skip if the property is folded, we should have handled it above const IAnimBlueprintCompilationContext::FFoldedPropertyRecord* FoldedPropertyRecord = InCompilationContext.GetFoldedPropertyRecord(InNode, PropertyName); if(FoldedPropertyRecord != nullptr) { // We only support per-instance members here. if(!FoldedPropertyRecord->bIsOnClass) { // We must have created an assignment node for the mutable data block by now check(InstanceAssignmentNode != nullptr); // Redirect to the instance's mutable data area assignment node PropertyName = FoldedPropertyRecord->GeneratedProperty->GetFName(); // We dont need to handle arrays with a member set, as they use a member get-by-ref if(!TargetPin->PinType.IsArray()) { TargetPin = InstanceAssignmentNode->FindPinChecked(FoldedPropertyRecord->GeneratedProperty->GetFName()); } } } if (TargetPin->PinType.IsArray()) { // Grab the array that we need to set members for UK2Node_StructMemberGet* FetchArrayNode = InCompilationContext.SpawnIntermediateNode(InNode, InCompilationContext.GetConsolidatedEventGraph()); FetchArrayNode->VariableReference.SetSelfMember(FoldedPropertyRecord ? MutableDataProperty->GetFName() : Record.NodeVariableProperty->GetFName()); FetchArrayNode->StructType = FoldedPropertyRecord ? MutableDataProperty->Struct : Record.NodeVariableProperty->Struct; FetchArrayNode->AllocatePinsForSingleMemberGet(PropertyName); Record.CustomEventNodes.Add(FetchArrayNode); UEdGraphPin* ArrayVariableNode = FetchArrayNode->FindPin(PropertyName); if (SourceInfo->CopyRecords.Num() > 0) { // Set each element in the array for (FPropertyCopyRecord& CopyRecord : SourceInfo->CopyRecords) { int32 ArrayIndex = CopyRecord.DestArrayIndex; if(UEdGraphPin* DestPin = CopyRecord.DestPin) { // Create an array element set node UK2Node_CallArrayFunction* ArrayNode = InCompilationContext.SpawnIntermediateNode(InNode, InCompilationContext.GetConsolidatedEventGraph()); ArrayNode->FunctionReference.SetExternalMember(GET_FUNCTION_NAME_CHECKED(UKismetArrayLibrary, Array_Set), UKismetArrayLibrary::StaticClass()); ArrayNode->AllocateDefaultPins(); Record.CustomEventNodes.Add(ArrayNode); // Connect the execution chain ExecChain->MakeLinkTo(ArrayNode->GetExecPin()); ExecChain = ArrayNode->GetThenPin(); // Connect the input array UEdGraphPin* TargetArrayPin = ArrayNode->FindPinChecked(TEXT("TargetArray")); TargetArrayPin->MakeLinkTo(ArrayVariableNode); ArrayNode->PinConnectionListChanged(TargetArrayPin); // Set the array index UEdGraphPin* TargetIndexPin = ArrayNode->FindPinChecked(TEXT("Index")); TargetIndexPin->DefaultValue = FString::FromInt(ArrayIndex); // Wire up the data input UEdGraphPin* TargetItemPin = ArrayNode->FindPinChecked(TEXT("Item")); TargetItemPin->CopyPersistentDataFromOldPin(*DestPin); InCompilationContext.GetMessageLog().NotifyIntermediatePinCreation(TargetItemPin, DestPin); } } } } else { // Single property if (SourceInfo->CopyRecords.Num() > 0 && SourceInfo->CopyRecords[0].DestPin != nullptr) { UEdGraphPin* DestPin = SourceInfo->CopyRecords[0].DestPin; PropertiesBeingSet.Add(PropertyName); TargetPin->CopyPersistentDataFromOldPin(*DestPin); InCompilationContext.GetMessageLog().NotifyIntermediatePinCreation(TargetPin, DestPin); } } } } // Remove any unused pins from the assignment nodes to avoid smashing constant values bool bAnyNodePropertiesSet = false; for (int32 PinIndex = 0; PinIndex < AssignmentNode->ShowPinForProperties.Num(); ++PinIndex) { FOptionalPinFromProperty& TestProperty = AssignmentNode->ShowPinForProperties[PinIndex]; TestProperty.bShowPin = PropertiesBeingSet.Contains(TestProperty.PropertyName); bAnyNodePropertiesSet |= TestProperty.bShowPin; } if(bAnyNodePropertiesSet) { AssignmentNode->ReconstructNode(); UEdGraphPin* ExecVariablesIn = K2Schema->FindExecutionPin(*AssignmentNode, EGPD_Input); ExecChain->MakeLinkTo(ExecVariablesIn); ExecChain = K2Schema->FindExecutionPin(*AssignmentNode, EGPD_Output); } if (InstanceAssignmentNode != nullptr) { for (int32 PinIndex = 0; PinIndex < InstanceAssignmentNode->ShowPinForProperties.Num(); ++PinIndex) { FOptionalPinFromProperty& TestProperty = InstanceAssignmentNode->ShowPinForProperties[PinIndex]; if (TestProperty.bShowPin) { UEdGraphPin* ExecVariablesIn = K2Schema->FindExecutionPin(*InstanceAssignmentNode, EGPD_Input); ExecChain->MakeLinkTo(ExecVariablesIn); ExecChain = K2Schema->FindExecutionPin(*InstanceAssignmentNode, EGPD_Output); break; } } } } } void UAnimBlueprintExtension_Base::RedirectPropertyAccesses(IAnimBlueprintCompilationContext& InCompilationContext, UAnimGraphNode_Base* InNode, FEvaluationHandlerRecord& InRecord) { const FStructProperty* MutableDataProperty = InCompilationContext.GetMutableDataProperty(); if (InCompilationContext.IsAnimGraphNodeFolded(InNode) && MutableDataProperty != nullptr) { for (TPair& NamePropertyPair : InRecord.ServicedProperties) { if (const IAnimBlueprintCompilationContext::FFoldedPropertyRecord* FoldedPropertyRecord = InCompilationContext.GetFoldedPropertyRecord(InNode, NamePropertyPair.Key)) { for (FPropertyCopyRecord& CopyRecord : NamePropertyPair.Value.CopyRecords) { if (CopyRecord.DestPropertyPath.Num() > 1) { // If this record writes to the node, switch it to the mutable data's property instead if (CopyRecord.DestPropertyPath[0] == InRecord.NodeVariableProperty->GetName()) { CopyRecord.DestPropertyPath[0] = MutableDataProperty->GetName(); FString DestPropertyPathTail = CopyRecord.DestPropertyPath[1]; FString DestPropertyPathWithoutArray = DestPropertyPathTail; FString ArrayIndex; int32 ArrayDelim = INDEX_NONE; if (DestPropertyPathTail.FindChar(TEXT('['), ArrayDelim)) { DestPropertyPathWithoutArray = DestPropertyPathTail.Left(ArrayDelim); ArrayIndex = DestPropertyPathTail.RightChop(ArrayDelim); } // Switch the destination property from the node's property to the generated one if (DestPropertyPathWithoutArray == FoldedPropertyRecord->Property->GetName()) { CopyRecord.DestPropertyPath[1] = FoldedPropertyRecord->GeneratedProperty->GetName() + ArrayIndex; } } } } } } } } void UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::PatchAnimNodeExposedValueHandler(const UClass* InClass, IAnimBlueprintCompilationBracketContext& InCompilationContext) const { UAnimBlueprintGeneratedClass* AnimClass = Cast(const_cast(InClass)); FStructProperty* HandlerProperty = CastFieldChecked(InCompilationContext.GetAllocatedHandlerPropertiesByNode().FindChecked(AnimGraphNode)); check(HandlerProperty->GetOwner() == AnimClass->GetSparseClassDataStruct()); void* ConstantNodeData = const_cast(AnimClass->GetSparseClassData(EGetSparseClassDataMethod::ReturnIfNull)); check(ConstantNodeData); if (HandlerProperty->Struct == FAnimNodeExposedValueHandler_PropertyAccess::StaticStruct()) { FAnimNodeExposedValueHandler_PropertyAccess* Handler = HandlerProperty->ContainerPtrToValuePtr(ConstantNodeData); Handler->CopyRecords.Empty(); for (const TPair& ServicedPropPair : ServicedProperties) { const FName& PropertyName = ServicedPropPair.Key; const FAnimNodeSinglePropertyHandler& PropertyHandler = ServicedPropPair.Value; for (const FPropertyCopyRecord& PropertyCopyRecord : PropertyHandler.CopyRecords) { // Only unbatched copies can be processed on a per-node basis // Skip invalid copy indices as these are usually the result of BP errors/warnings if (PropertyCopyRecord.LibraryCompiledHandle.IsValid() && PropertyCopyRecord.LibraryCompiledHandle.GetBatchId() == (int32)EAnimPropertyAccessCallSite::WorkerThread_Unbatched) { Handler->CopyRecords.Emplace(PropertyCopyRecord.LibraryCompiledHandle.GetId(), PropertyCopyRecord.Operation, PropertyCopyRecord.bOnlyUpdateWhenActive); } } } } if (HandlerProperty->Struct->IsChildOf(FAnimNodeExposedValueHandler_Base::StaticStruct())) { FAnimNodeExposedValueHandler_Base* Handler = HandlerProperty->ContainerPtrToValuePtr(ConstantNodeData); if (!IsFastPath()) { // not all of our pins use copy records so we will need to call our exposed value handler Handler->BoundFunction = HandlerFunctionName; } } } static UEdGraphPin* FindFirstInputPin(UEdGraphNode* InNode) { const UAnimationGraphSchema* Schema = GetDefault(); for(UEdGraphPin* Pin : InNode->Pins) { if(Pin && Pin->Direction == EGPD_Input && !Schema->IsExecPin(*Pin) && !Schema->IsSelfPin(*Pin)) { return Pin; } } return nullptr; } static bool ForEachInputPin(UEdGraphNode* InNode, TFunctionRef InFunction) { const UAnimationGraphSchema* Schema = GetDefault(); bool bResult = false; for(UEdGraphPin* Pin : InNode->Pins) { if(Pin && Pin->Direction == EGPD_Input && !Schema->IsExecPin(*Pin) && !Schema->IsSelfPin(*Pin)) { bResult |= InFunction(Pin); } } return bResult; } static UEdGraphNode* FollowKnots(UEdGraphPin* FromPin, UEdGraphPin*& ToPin) { if (FromPin->LinkedTo.Num() == 0) { return nullptr; } UEdGraphPin* LinkedPin = FromPin->LinkedTo[0]; ToPin = LinkedPin; if(LinkedPin) { UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode(); UK2Node_Knot* KnotNode = Cast(LinkedNode); while(KnotNode) { if(UEdGraphPin* InputPin = FindFirstInputPin(KnotNode)) { if (InputPin->LinkedTo.Num() > 0 && InputPin->LinkedTo[0]) { ToPin = InputPin->LinkedTo[0]; LinkedNode = InputPin->LinkedTo[0]->GetOwningNode(); KnotNode = Cast(LinkedNode); } else { KnotNode = nullptr; } } } return LinkedNode; } return nullptr; } void UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::RegisterPin(UEdGraphPin* DestPin, FProperty* AssociatedProperty, int32 AssociatedPropertyArrayIndex, bool bAllowFastPath) { FAnimNodeSinglePropertyHandler& Handler = ServicedProperties.FindOrAdd(AssociatedProperty->GetFName()); TArray DestPropertyPath; // Prepend the destination property with the node's member property if the property is not on a UClass if(Cast(AssociatedProperty->Owner.ToUObject()) == nullptr) { DestPropertyPath.Add(NodeVariableProperty->GetName()); } if(AssociatedPropertyArrayIndex != INDEX_NONE) { DestPropertyPath.Add(FString::Printf(TEXT("%s[%d]"), *AssociatedProperty->GetName(), AssociatedPropertyArrayIndex)); } else { DestPropertyPath.Add(AssociatedProperty->GetName()); } Handler.CopyRecords.Emplace(DestPin, AssociatedProperty, AssociatedPropertyArrayIndex, MoveTemp(DestPropertyPath)); Handler.CopyRecords.Last().bIsFastPath = bAllowFastPath; } void UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::RegisterPropertyBinding(FProperty* InProperty, const FAnimGraphNodePropertyBinding& InBinding) { FAnimNodeSinglePropertyHandler& Handler = ServicedProperties.FindOrAdd(InProperty->GetFName()); TArray DestPropertyPath; // Prepend the destination property with the node's member property if the property is not on a UClass if(Cast(InProperty->Owner.ToUObject()) == nullptr) { Handler.bInstanceIsTarget = false; DestPropertyPath.Add(NodeVariableProperty->GetName()); } else { Handler.bInstanceIsTarget = true; } if(InBinding.ArrayIndex != INDEX_NONE) { DestPropertyPath.Add(FString::Printf(TEXT("%s[%d]"), *InProperty->GetName(), InBinding.ArrayIndex)); } else { DestPropertyPath.Add(InProperty->GetName()); } FPropertyCopyRecord& CopyRecord = Handler.CopyRecords.Emplace_GetRef(InBinding.PropertyPath, DestPropertyPath); CopyRecord.DestProperty = InProperty; CopyRecord.DestArrayIndex = InBinding.ArrayIndex; CopyRecord.BindingContextId = InBinding.ContextId; CopyRecord.bOnlyUpdateWhenActive = InBinding.bOnlyUpdateWhenActive; } void UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::BuildFastPathCopyRecords(IAnimBlueprintPostExpansionStepContext& InCompilationContext) { typedef bool (UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::*GraphCheckerFunc)(FCopyRecordGraphCheckContext&, UEdGraphPin*); GraphCheckerFunc GraphCheckerFuncs[] = { &UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForSplitPinAccess, &UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForVariableGet, &UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForLogicalNot, &UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForStructMemberAccess, &UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForArrayAccess, }; if (GetDefault()->bOptimizeAnimBlueprintMemberVariableAccess) { for (TPair& ServicedPropPair : ServicedProperties) { TArray AllAdditionalCopyRecords; for (FPropertyCopyRecord& CopyRecord : ServicedPropPair.Value.CopyRecords) { if(CopyRecord.SourcePropertyPath.Num() == 0) { TArray AdditionalCopyRecords; FCopyRecordGraphCheckContext Context(CopyRecord, AdditionalCopyRecords, InCompilationContext.GetMessageLog()); for (GraphCheckerFunc& CheckFunc : GraphCheckerFuncs) { if ((this->*CheckFunc)(Context, CopyRecord.DestPin)) { break; } } if(AdditionalCopyRecords.Num() > 0) { for(FPropertyCopyRecord& AdditionalCopyRecord : AdditionalCopyRecords) { CheckForMemberOnlyAccess(AdditionalCopyRecord, AdditionalCopyRecord.DestPin); } CopyRecord = AdditionalCopyRecords[0]; for(int32 AdditionalRecordIndex = 1; AdditionalRecordIndex < AdditionalCopyRecords.Num(); ++AdditionalRecordIndex) { AllAdditionalCopyRecords.Add(AdditionalCopyRecords[AdditionalRecordIndex]); } } else { CheckForMemberOnlyAccess(CopyRecord, CopyRecord.DestPin); } } } // Append any additional copy records ServicedPropPair.Value.CopyRecords.Append(AllAdditionalCopyRecords); } } } static void GetFullyQualifiedPathFromPin(const UEdGraphPin* Pin, TArray& OutPath) { FString PinName = Pin->PinName.ToString(); while (Pin->ParentPin != nullptr) { PinName[Pin->ParentPin->PinName.GetStringLength()] = TEXT('.'); Pin = Pin->ParentPin; } UE::String::ParseTokens(PinName, TEXT('.'), [&OutPath](FStringView InStringView) { OutPath.Add(FString(InStringView)); }); } bool UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForVariableGet(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) { if(DestPin) { UEdGraphPin* SourcePin = nullptr; if(UK2Node_VariableGet* VariableGetNode = Cast(FollowKnots(DestPin, SourcePin))) { if(VariableGetNode && VariableGetNode->IsNodePure() && VariableGetNode->VariableReference.IsSelfContext()) { if(SourcePin) { GetFullyQualifiedPathFromPin(SourcePin, Context.CopyRecord->SourcePropertyPath); return true; } } } } return false; } bool UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForLogicalNot(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) { if(DestPin) { UEdGraphPin* SourcePin = nullptr; UK2Node_CallFunction* CallFunctionNode = Cast(FollowKnots(DestPin, SourcePin)); if(CallFunctionNode && CallFunctionNode->FunctionReference.GetMemberName() == FName(TEXT("Not_PreBool"))) { // find and follow input pin if(UEdGraphPin* InputPin = FindFirstInputPin(CallFunctionNode)) { check(InputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean); if(CheckForVariableGet(Context, InputPin) || CheckForStructMemberAccess(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) { check(Context.CopyRecord->SourcePropertyPath.Num() > 0); // this should have been filled in by CheckForVariableGet() or CheckForStructMemberAccess() above Context.CopyRecord->Operation = EPostCopyOperation::LogicalNegateBool; return true; } } } } return false; } /** The functions that we can safely native-break */ static const FName NativeBreakFunctionNameAllowList[] = { FName(TEXT("BreakVector")), FName(TEXT("BreakVector2D")), FName(TEXT("BreakRotator")), }; /** Check whether a native break function can be safely used in the fast-path copy system (ie. source and dest data will be the same) */ static bool IsNativeBreakAllowed(const FName& InFunctionName) { for(const FName& FunctionName : NativeBreakFunctionNameAllowList) { if(InFunctionName == FunctionName) { return true; } } return false; } /** The functions that we can safely native-make */ static const FName NativeMakeFunctionNameAllowList[] = { FName(TEXT("MakeVector")), FName(TEXT("MakeVector2D")), FName(TEXT("MakeRotator")), }; /** Check whether a native break function can be safely used in the fast-path copy system (ie. source and dest data will be the same) */ static bool IsNativeMakeAllowed(const FName& InFunctionName) { for(const FName& FunctionName : NativeMakeFunctionNameAllowList) { if(InFunctionName == FunctionName) { return true; } } return false; } bool UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForStructMemberAccess(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) { if(DestPin) { UEdGraphPin* SourcePin = nullptr; if(UK2Node_BreakStruct* BreakStructNode = Cast(FollowKnots(DestPin, SourcePin))) { if(UEdGraphPin* InputPin = FindFirstInputPin(BreakStructNode)) { if(CheckForStructMemberAccess(Context, InputPin) || CheckForVariableGet(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) { check(Context.CopyRecord->SourcePropertyPath.Num() > 0); // this should have been filled in by CheckForVariableGet() above Context.CopyRecord->SourcePropertyPath.Add(SourcePin->PinName.ToString()); return true; } } } // could be a native break else if(UK2Node_CallFunction* NativeBreakNode = Cast(FollowKnots(DestPin, SourcePin))) { UFunction* Function = NativeBreakNode->FunctionReference.ResolveMember(UKismetMathLibrary::StaticClass()); if(Function && Function->HasMetaData(TEXT("NativeBreakFunc")) && IsNativeBreakAllowed(Function->GetFName())) { if(UEdGraphPin* InputPin = FindFirstInputPin(NativeBreakNode)) { if(CheckForStructMemberAccess(Context, InputPin) || CheckForVariableGet(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) { check(Context.CopyRecord->SourcePropertyPath.Num() > 0); // this should have been filled in by CheckForVariableGet() above Context.CopyRecord->SourcePropertyPath.Add(SourcePin->PinName.ToString()); return true; } } } } } return false; } bool UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForSplitPinAccess(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) { if(DestPin) { FPropertyCopyRecord OriginalRecord = *Context.CopyRecord; UEdGraphPin* SourcePin = nullptr; if(UK2Node_MakeStruct* MakeStructNode = Cast(FollowKnots(DestPin, SourcePin))) { // Idea here is to account for split pins, so we want to narrow the scope to not also include user-placed makes UObject* SourceObject = Context.MessageLog.FindSourceObject(MakeStructNode); if(SourceObject && SourceObject->IsA()) { return ForEachInputPin(MakeStructNode, [this, &Context, &OriginalRecord](UEdGraphPin* InputPin) { Context.CopyRecord->SourcePropertyPath = OriginalRecord.SourcePropertyPath; if(CheckForStructMemberAccess(Context, InputPin) || CheckForVariableGet(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) { check(Context.CopyRecord->DestPropertyPath.Num() > 0); FPropertyCopyRecord RecordCopy = *Context.CopyRecord; FPropertyCopyRecord& NewRecord = Context.AdditionalCopyRecords.Add_GetRef(MoveTemp(RecordCopy)); NewRecord.DestPropertyPath = OriginalRecord.DestPropertyPath; NewRecord.DestPropertyPath.Add(InputPin->PinName.ToString()); return true; } return false; }); } } else if(UK2Node_CallFunction* NativeMakeNode = Cast(FollowKnots(DestPin, SourcePin))) { UFunction* Function = NativeMakeNode->FunctionReference.ResolveMember(UKismetMathLibrary::StaticClass()); if(Function && Function->HasMetaData(TEXT("NativeMakeFunc")) && IsNativeMakeAllowed(Function->GetFName())) { // Idea here is to account for split pins, so we want to narrow the scope to not also include user-placed makes UObject* SourceObject = Context.MessageLog.FindSourceObject(NativeMakeNode); if(SourceObject && SourceObject->IsA()) { return ForEachInputPin(NativeMakeNode, [this, &Context, &OriginalRecord](UEdGraphPin* InputPin) { Context.CopyRecord->SourcePropertyPath = OriginalRecord.SourcePropertyPath; if(CheckForStructMemberAccess(Context, InputPin) || CheckForVariableGet(Context, InputPin) || CheckForArrayAccess(Context, InputPin)) { check(Context.CopyRecord->DestPropertyPath.Num() > 0); FPropertyCopyRecord RecordCopy = *Context.CopyRecord; FPropertyCopyRecord& NewRecord = Context.AdditionalCopyRecords.Add_GetRef(MoveTemp(RecordCopy)); NewRecord.DestPropertyPath = OriginalRecord.DestPropertyPath; NewRecord.DestPropertyPath.Add(InputPin->PinName.ToString()); return true; } return false; }); } } } } return false; } bool UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForArrayAccess(FCopyRecordGraphCheckContext& Context, UEdGraphPin* DestPin) { if(DestPin) { UEdGraphPin* SourcePin = nullptr; if(UK2Node_CallArrayFunction* CallArrayFunctionNode = Cast(FollowKnots(DestPin, SourcePin))) { if(CallArrayFunctionNode->GetTargetFunction() == UKismetArrayLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetArrayLibrary, Array_Get))) { // Check array index is constant int32 ArrayIndex = INDEX_NONE; if(UEdGraphPin* IndexPin = CallArrayFunctionNode->FindPin(TEXT("Index"))) { if(IndexPin->LinkedTo.Num() > 0) { return false; } ArrayIndex = FCString::Atoi(*IndexPin->DefaultValue); } if(UEdGraphPin* TargetArrayPin = CallArrayFunctionNode->FindPin(TEXT("TargetArray"))) { if(CheckForVariableGet(Context, TargetArrayPin) || CheckForStructMemberAccess(Context, TargetArrayPin)) { check(Context.CopyRecord->SourcePropertyPath.Num() > 0); // this should have been filled in by CheckForVariableGet() or CheckForStructMemberAccess() above Context.CopyRecord->SourcePropertyPath.Last().Append(FString::Printf(TEXT("[%d]"), ArrayIndex)); return true; } } } } } return false; } bool UAnimBlueprintExtension_Base::FEvaluationHandlerRecord::CheckForMemberOnlyAccess(FPropertyCopyRecord& CopyRecord, UEdGraphPin* DestPin) { const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); if(DestPin) { // traverse pins to leaf nodes and check for member access/pure only TArray PinStack; PinStack.Add(DestPin); while(PinStack.Num() > 0) { UEdGraphPin* CurrentPin = PinStack.Pop(EAllowShrinking::No); for(auto& LinkedPin : CurrentPin->LinkedTo) { UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode(); if(LinkedNode) { bool bLeafNode = true; for(auto& Pin : LinkedNode->Pins) { if(Pin != LinkedPin && Pin->Direction == EGPD_Input && !AnimGraphDefaultSchema->IsPosePin(Pin->PinType)) { bLeafNode = false; PinStack.Add(Pin); } } if(bLeafNode) { if(UK2Node_VariableGet* LinkedVariableGetNode = Cast(LinkedNode)) { if(!LinkedVariableGetNode->IsNodePure() || !LinkedVariableGetNode->VariableReference.IsSelfContext()) { // only local variable access is allowed for leaf nodes CopyRecord.InvalidateFastPath(); } } else if(UK2Node_CallFunction* CallFunctionNode = Cast(LinkedNode)) { if(!CallFunctionNode->IsNodePure()) { // only allow pure function calls CopyRecord.InvalidateFastPath(); } } else if(!LinkedNode->IsA()) { CopyRecord.InvalidateFastPath(); } } } } } } return CopyRecord.IsFastPath(); } #undef LOCTEXT_NAMESPACE