You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
"Call function" anim node: - Adds the ability to call functions at different points in the anim graph execution (and under different conditions, e.g. when a branch has started blending in). Only thread-safe functions are allowed to be called. - Adds a thread-safe override point BlueprintThreadSafeUpdateAnimation, called on worker threads for the main instance and for linked instances when they are relevant in the graph (only one call per frame for linked layer instances). Subsystems: - Added new override points pre/post event graph(s) (moved override point for worker thread work to around the thread safe update function call). Improves property access integration: - Property access now shows (and allows the user to override) the call site of accesses. This is to allow users to see when their property access calls will be made, hopefully making its use less confusing for power users. - Tweaked UX for property access nodes and dropdowns. - Anim node pins now have property access bindings in-line on the pin. Also adds the abilility for the anim graph to opt-in (via a config flag) to more stringent thread safety checks. Disabled by default for now as this requires content fixup. #jira UE-115745 - Anim Blueprint Encapsulation #rb Jurre.deBaare [CL 16434092 by Thomas Sarkanen in ue5-main branch]
353 lines
12 KiB
C++
353 lines
12 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnimGraphNode_CallFunction.h"
|
|
|
|
#include "BlueprintCompilationManager.h"
|
|
#include "Subsystems/AssetEditorSubsystem.h"
|
|
#include "IAnimationBlueprintEditor.h"
|
|
#include "K2Node_CustomEvent.h"
|
|
#include "KismetCompiler.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "K2Node_CallFunction.h"
|
|
#include "BlueprintNodeSpawner.h"
|
|
#include "BlueprintActionDatabaseRegistrar.h"
|
|
#include "IAnimBlueprintCompilationContext.h"
|
|
#include "Classes/EditorStyleSettings.h"
|
|
#include "AnimBlueprintExtension_CallFunction.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "AnimGraphNode_CallFunction"
|
|
|
|
void UAnimGraphNode_CallFunction::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
BindDelegates();
|
|
}
|
|
|
|
FLinearColor UAnimGraphNode_CallFunction::GetNodeTitleColor() const
|
|
{
|
|
return CallFunctionPrototype ? CallFunctionPrototype->GetNodeTitleColor() : Super::GetNodeTitleColor();
|
|
}
|
|
|
|
FText UAnimGraphNode_CallFunction::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
|
{
|
|
FText FunctionName;
|
|
UFunction* Function = CallFunctionPrototype ? CallFunctionPrototype->GetTargetFunction() : nullptr;
|
|
if (Function)
|
|
{
|
|
FunctionName = UK2Node_CallFunction::GetUserFacingFunctionName(Function);
|
|
}
|
|
else if(CallFunctionPrototype)
|
|
{
|
|
FunctionName = FText::FromName(CallFunctionPrototype->FunctionReference.GetMemberName());
|
|
if ((GEditor != nullptr) && (GetDefault<UEditorStyleSettings>()->bShowFriendlyNames))
|
|
{
|
|
FunctionName = FText::FromString(FName::NameToDisplayString(FunctionName.ToString(), false));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FunctionName = LOCTEXT("Function", "Function");
|
|
}
|
|
|
|
if(TitleType == ENodeTitleType::FullTitle)
|
|
{
|
|
FTextBuilder TextBuilder;
|
|
TextBuilder.AppendLine(FunctionName);
|
|
TextBuilder.AppendLine(UEnum::GetDisplayValueAsText(Node.CallSite));
|
|
|
|
return TextBuilder.ToText();
|
|
}
|
|
else
|
|
{
|
|
return FunctionName;
|
|
}
|
|
}
|
|
|
|
FText UAnimGraphNode_CallFunction::GetTooltipText() const
|
|
{
|
|
return LOCTEXT("NodeTooltip", "A node that calls user-defined functions during animation graph execution");
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::AllocateFunctionPins()
|
|
{
|
|
if(CallFunctionPrototype)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
for(UEdGraphPin* Pin : CallFunctionPrototype->Pins)
|
|
{
|
|
if(!K2Schema->IsExecPin(*Pin) && Pin->PinName != UEdGraphSchema_K2::PN_Self && Pin->Direction == EGPD_Input)
|
|
{
|
|
// Create and copy pin data from prototype K2 node
|
|
UEdGraphPin* NewPin = CreatePin(EGPD_Input, Pin->PinType, Pin->PinName);
|
|
NewPin->DefaultObject = Pin->DefaultObject;
|
|
NewPin->DefaultValue = Pin->DefaultValue;
|
|
NewPin->DefaultTextValue = Pin->DefaultTextValue;
|
|
NewPin->AutogeneratedDefaultValue = Pin->AutogeneratedDefaultValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& InOldPins)
|
|
{
|
|
Super::ReallocatePinsDuringReconstruction(InOldPins);
|
|
|
|
AllocateFunctionPins();
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::AllocateDefaultPins()
|
|
{
|
|
Super::AllocateDefaultPins();
|
|
|
|
AllocateFunctionPins();
|
|
}
|
|
|
|
FText UAnimGraphNode_CallFunction::GetMenuCategory() const
|
|
{
|
|
if(CallFunctionPrototype)
|
|
{
|
|
if(UFunction* Function = CallFunctionPrototype->GetTargetFunction())
|
|
{
|
|
return UK2Node_CallFunction::GetDefaultCategoryForFunction(CallFunctionPrototype->GetTargetFunction(), LOCTEXT("BaseCategory", "Call Function"));
|
|
}
|
|
}
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
|
|
{
|
|
// Note we dont call super here as we dont have an 'evaluation handler'
|
|
|
|
if(CallFunctionPrototype)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
UAnimBlueprintExtension_CallFunction* Extension = UAnimBlueprintExtension::GetExtension<UAnimBlueprintExtension_CallFunction>(GetAnimBlueprint());
|
|
|
|
const FName EventName = Extension->AddCustomEventName(this);
|
|
|
|
// @TODO: move this name copy to the CDO baking step
|
|
Node.FunctionName = EventName;
|
|
|
|
UK2Node_CustomEvent* CustomEventNode = CompilerContext.SpawnIntermediateEventNode<UK2Node_CustomEvent>(this, nullptr, CompilerContext.ConsolidatedEventGraph);
|
|
CustomEventNode->bInternalEvent = true;
|
|
CustomEventNode->CustomFunctionName = EventName;
|
|
CustomEventNode->AllocateDefaultPins();
|
|
|
|
UEdGraphPin* ExecChain = K2Schema->FindExecutionPin(*CustomEventNode, EGPD_Output);
|
|
|
|
// Add call function node
|
|
UK2Node_CallFunction* NewCallFunctionNode = CompilerContext.SpawnIntermediateEventNode<UK2Node_CallFunction>(this, nullptr, CompilerContext.ConsolidatedEventGraph);
|
|
NewCallFunctionNode->FunctionReference = CallFunctionPrototype->FunctionReference;
|
|
NewCallFunctionNode->AllocateDefaultPins();
|
|
|
|
// link up pins
|
|
for(UEdGraphPin* Pin : CallFunctionPrototype->Pins)
|
|
{
|
|
if(!K2Schema->IsExecPin(*Pin) && Pin->PinName != UEdGraphSchema_K2::PN_Self && Pin->Direction == EGPD_Input)
|
|
{
|
|
UEdGraphPin* AnimGraphPin = FindPinChecked(Pin->PinName);
|
|
UEdGraphPin* NewPin = NewCallFunctionNode->FindPinChecked(Pin->PinName);
|
|
|
|
NewPin->CopyPersistentDataFromOldPin(*AnimGraphPin);
|
|
}
|
|
}
|
|
|
|
// Link function call into exec chain
|
|
UEdGraphPin* ExecFunctionCall = K2Schema->FindExecutionPin(*NewCallFunctionNode, EGPD_Input);
|
|
ExecChain->MakeLinkTo(ExecFunctionCall);
|
|
}
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::SetupFromFunction(UFunction* InFunction)
|
|
{
|
|
// Create graph and inner node
|
|
InnerGraph = FBlueprintEditorUtils::CreateNewGraph(this, NAME_None, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
|
|
|
FGraphNodeCreator<UK2Node_CallFunction> NodeCreator(*InnerGraph);
|
|
CallFunctionPrototype = NodeCreator.CreateNode();
|
|
CallFunctionPrototype->FunctionReference.SetFromField<UFunction>(InFunction, true);
|
|
NodeCreator.Finalize();
|
|
|
|
BindDelegates();
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::BindDelegates()
|
|
{
|
|
if(!GraphChangedHandle.IsValid())
|
|
{
|
|
GraphChangedHandle = InnerGraph->AddOnGraphChangedHandler(FOnGraphChanged::FDelegate::CreateLambda([this](const FEdGraphEditAction& InAction)
|
|
{
|
|
// Reconstruct node when the inner graph changes (this catches changes to the function signature)
|
|
ReconstructNode();
|
|
}));
|
|
}
|
|
|
|
if(!PinRenamedHandle.IsValid())
|
|
{
|
|
PinRenamedHandle = CallFunctionPrototype->OnUserDefinedPinRenamed().AddLambda([this](UK2Node* InNode, FName InOldName, FName InNewName)
|
|
{
|
|
RenameUserDefinedPin(InOldName, InNewName);
|
|
});
|
|
}
|
|
}
|
|
|
|
bool UAnimGraphNode_CallFunction::IsFunctionBlacklisted(const UFunction* InFunction) const
|
|
{
|
|
return InFunction->GetFName() == GET_FUNCTION_NAME_CHECKED(UAnimInstance, BlueprintThreadSafeUpdateAnimation);
|
|
}
|
|
|
|
bool UAnimGraphNode_CallFunction::AreFunctionParamsValid(const UFunction* InFunction) const
|
|
{
|
|
for(TFieldIterator<FProperty> It(InFunction); It; ++It)
|
|
{
|
|
const FProperty* Property = *It;
|
|
|
|
// As we cant process return params, we don't allow functions with them to be called in the anim graph
|
|
if(Property->HasAnyPropertyFlags(CPF_ReturnParm))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UAnimGraphNode_CallFunction::ValidateFunction(const UFunction* InFunction, FCompilerResultsLog* InMessageLog) const
|
|
{
|
|
bool bValid = true;
|
|
|
|
auto InvalidateMessage = [this, &bValid, InMessageLog](const FText& InMessage)
|
|
{
|
|
bValid = false;
|
|
if(InMessageLog)
|
|
{
|
|
InMessageLog->Error(*InMessage.ToString(), this);
|
|
}
|
|
};
|
|
|
|
if(InFunction->HasAnyFunctionFlags(FUNC_BlueprintPure))
|
|
{
|
|
InvalidateMessage(LOCTEXT("PureFunctionError", "@@ cannot call a pure function"));
|
|
}
|
|
|
|
if(!FBlueprintEditorUtils::HasFunctionBlueprintThreadSafeMetaData(InFunction))
|
|
{
|
|
InvalidateMessage(LOCTEXT("ThreadSafetyError", "@@ call is not thread safe"));
|
|
}
|
|
|
|
if(!AreFunctionParamsValid(InFunction))
|
|
{
|
|
InvalidateMessage(LOCTEXT("FunctionParamsInvalidError", "@@ has invalid parameters. Return parameters are not allowed"));
|
|
}
|
|
|
|
if(InFunction->HasMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly))
|
|
{
|
|
InvalidateMessage(LOCTEXT("FunctionInternalError", "@@ uses an internal-only function"));
|
|
}
|
|
|
|
if(IsFunctionBlacklisted(InFunction))
|
|
{
|
|
InvalidateMessage(LOCTEXT("FunctionBlacklistedError", "@@ uses a blacklisted function"));
|
|
}
|
|
|
|
return bValid;
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
|
|
{
|
|
const UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(ActionRegistrar.GetActionKeyFilter());
|
|
if(AnimBlueprint && ActionRegistrar.IsOpenForRegistration(AnimBlueprint))
|
|
{
|
|
auto MakeFunctionActionsForClass = [this, &ActionRegistrar, AnimBlueprint](UClass* InClass)
|
|
{
|
|
auto MakeFunctionAction = [this, &ActionRegistrar, AnimBlueprint](UFunction* InFunction)
|
|
{
|
|
if(UEdGraphSchema_K2::CanUserKismetCallFunction(InFunction) && ValidateFunction(InFunction))
|
|
{
|
|
auto CustomizeNode = [InFunction](UEdGraphNode* InNode, bool bIsTemplate)
|
|
{
|
|
UAnimGraphNode_CallFunction* CallFunctionNode = CastChecked<UAnimGraphNode_CallFunction>(InNode);
|
|
CallFunctionNode->SetupFromFunction(InFunction);
|
|
};
|
|
|
|
UBlueprintNodeSpawner* Spawner = UBlueprintNodeSpawner::Create(UAnimGraphNode_CallFunction::StaticClass(), nullptr, UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateLambda(CustomizeNode));
|
|
FBlueprintActionUiSpec& MenuSignature = Spawner->DefaultMenuSignature;
|
|
|
|
MenuSignature.MenuName = FText::Format(LOCTEXT("MenuNameFormat", "{0} (From Anim Graph)"), UK2Node_CallFunction::GetUserFacingFunctionName(InFunction));
|
|
MenuSignature.Category = UK2Node_CallFunction::GetDefaultCategoryForFunction(InFunction, LOCTEXT("BaseCategory", "Call Function From Anim Graph"));
|
|
MenuSignature.Tooltip = FText::FromString(UK2Node_CallFunction::GetDefaultTooltipForFunction(InFunction));
|
|
MenuSignature.Keywords = UK2Node_CallFunction::GetKeywordsForFunction(InFunction);
|
|
|
|
// add at least one character, so that PrimeDefaultUiSpec() doesn't attempt to query the template node
|
|
if (MenuSignature.Keywords.IsEmpty())
|
|
{
|
|
MenuSignature.Keywords = FText::FromString(TEXT(" "));
|
|
}
|
|
|
|
ActionRegistrar.AddBlueprintAction(AnimBlueprint, Spawner);
|
|
}
|
|
};
|
|
|
|
for(TFieldIterator<UFunction> It(InClass); It; ++It)
|
|
{
|
|
MakeFunctionAction(*It);
|
|
}
|
|
};
|
|
|
|
// Add this class
|
|
MakeFunctionActionsForClass(AnimBlueprint->GetAnimBlueprintGeneratedClass());
|
|
|
|
// Add function libraries too
|
|
for(TObjectIterator<UBlueprintFunctionLibrary> It(RF_NoFlags); It; ++It)
|
|
{
|
|
MakeFunctionActionsForClass(It->GetClass());
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::GetRequiredExtensions(TArray<TSubclassOf<UAnimBlueprintExtension>>& OutExtensions) const
|
|
{
|
|
OutExtensions.Add(UAnimBlueprintExtension_CallFunction::StaticClass());
|
|
}
|
|
|
|
UObject* UAnimGraphNode_CallFunction::GetJumpTargetForDoubleClick() const
|
|
{
|
|
return CallFunctionPrototype ? CallFunctionPrototype->GetTargetFunction() : nullptr;
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::JumpToDefinition() const
|
|
{
|
|
if(CallFunctionPrototype)
|
|
{
|
|
UFunction* Function = CallFunctionPrototype->GetTargetFunction();
|
|
TSharedPtr<IBlueprintEditor> BlueprintEditor = FKismetEditorUtilities::GetIBlueprintEditorForObject(Function, true);
|
|
if(BlueprintEditor.IsValid())
|
|
{
|
|
BlueprintEditor->JumpToHyperlink(Function, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAnimGraphNode_CallFunction::ValidateNodeDuringCompilation(FCompilerResultsLog& MessageLog) const
|
|
{
|
|
if(CallFunctionPrototype == nullptr || CallFunctionPrototype->GetTargetFunction() == nullptr)
|
|
{
|
|
MessageLog.Error(*LOCTEXT("MissingFunctionPrototypeError", "Missing function, node @@ is invalid").ToString(), this);
|
|
}
|
|
|
|
if(CallFunctionPrototype != nullptr)
|
|
{
|
|
CallFunctionPrototype->ValidateNodeDuringCompilation(MessageLog);
|
|
|
|
UFunction* Function = CallFunctionPrototype->GetTargetFunction();
|
|
if(Function)
|
|
{
|
|
ValidateFunction(Function, &MessageLog);
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |