// Copyright Epic Games, Inc. All Rights Reserved. #include "BlueprintActionDatabase.h" #include "UObject/Class.h" #include "Templates/SubclassOf.h" #include "EdGraph/EdGraphNode.h" #include "Engine/Blueprint.h" #include "UObject/UnrealType.h" #include "Components/ActorComponent.h" #include "GameFramework/Actor.h" #include "Layout/SlateRect.h" #include "Engine/World.h" #include "BlueprintNodeBinder.h" #include "BlueprintActionFilter.h" #include "Modules/ModuleManager.h" #include "EngineGlobals.h" #include "UObject/UObjectIterator.h" #include "Engine/MemberReference.h" #include "Animation/Skeleton.h" #include "Animation/AnimBlueprint.h" #include "Engine/BlueprintGeneratedClass.h" #include "Animation/AnimBlueprintGeneratedClass.h" #include "Engine/Engine.h" #include "AssetData.h" #include "EdGraphSchema_K2.h" #include "K2Node.h" #include "K2Node_Event.h" #include "K2Node_ActorBoundEvent.h" #include "K2Node_AddDelegate.h" #include "K2Node_CallDelegate.h" #include "K2Node_ClearDelegate.h" #include "K2Node_ComponentBoundEvent.h" #include "K2Node_DynamicCast.h" #include "K2Node_FunctionEntry.h" #include "K2Node_MacroInstance.h" #include "K2Node_RemoveDelegate.h" #include "K2Node_VariableGet.h" #include "K2Node_VariableSet.h" #include "Kismet2/BlueprintEditorUtils.h" #include "BlueprintAssetHandler.h" #include "EdGraphNode_Comment.h" #include "Animation/AnimInstance.h" #include "Editor.h" #include "ComponentTypeRegistry.h" #include "EditorCategoryUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "BlueprintNodeSpawner.h" #include "BlueprintFunctionNodeSpawner.h" #include "BlueprintDelegateNodeSpawner.h" #include "BlueprintEventNodeSpawner.h" #include "BlueprintComponentNodeSpawner.h" #include "BlueprintBoundEventNodeSpawner.h" #include "BlueprintVariableNodeSpawner.h" #include "AssetRegistryModule.h" #include "BlueprintActionDatabaseRegistrar.h" #include "Engine/LevelScriptBlueprint.h" // used below in FBlueprintNodeSpawnerFactory::MakeMacroNodeSpawner() // used below in FBlueprintNodeSpawnerFactory::MakeComponentBoundEventSpawner()/MakeActorBoundEventSpawner() // used below in FBlueprintNodeSpawnerFactory::MakeMessageNodeSpawner() #include "K2Node_Message.h" // used below in BlueprintActionDatabaseImpl::AddClassPropertyActions() #include "K2Node_AssignDelegate.h" // used below in BlueprintActionDatabaseImpl::AddClassCastActions() #include "K2Node_ClassDynamicCast.h" // used below in BlueprintActionDatabaseImpl::GetNodeSpectificActions() #include "EdGraph/EdGraphNode_Documentation.h" #include "BlueprintTypePromotion.h" #define LOCTEXT_NAMESPACE "BlueprintActionDatabase" /******************************************************************************* * FBlueprintNodeSpawnerFactory ******************************************************************************/ namespace FBlueprintNodeSpawnerFactory { /** * Constructs a UK2Node_MacroInstance spawner. Evolved from * FK2ActionMenuBuilder::AttachMacroGraphAction(). Sets up the spawner to * set spawned nodes with the supplied macro. * * @param MacroGraph The macro you want spawned nodes referencing. * @return A new node-spawner, setup to spawn a UK2Node_MacroInstance. */ static UBlueprintNodeSpawner* MakeMacroNodeSpawner(UEdGraph* MacroGraph); /** * Constructs a UK2Node_Message spawner. Sets up the spawner to set * spawned nodes with the supplied function. * * @param InterfaceFunction The function you want spawned nodes referencing. * @return A new node-spawner, setup to spawn a UK2Node_Message. */ static UBlueprintNodeSpawner* MakeMessageNodeSpawner(UFunction* InterfaceFunction); /** * Constructs a UEdGraphNode_Comment spawner. Since UEdGraphNode_Comment is * not a UK2Node then we can't have it create a spawner for itself (using * UK2Node's GetMenuActions() method). * * @param DocNodeType The node class type that you want the spawner to be responsible for. * * @return A new node-spawner, setup to spawn a UEdGraphNode_Comment. */ template static UBlueprintNodeSpawner* MakeDocumentationNodeSpawner(); /** * * * @return */ static UBlueprintNodeSpawner* MakeCommentNodeSpawner(); /** * Constructs a delegate binding node along with a connected event that is * triggered from the specified delegate. * * @param DelegateProperty The delegate the spawner will bind to. * @return A new node-spawner, setup to spawn a UK2Node_AssignDelegate. */ static UBlueprintNodeSpawner* MakeAssignDelegateNodeSpawner(FMulticastDelegateProperty* DelegateProperty); /** * * * @param DelegateProperty * @return */ static UBlueprintNodeSpawner* MakeComponentBoundEventSpawner(FMulticastDelegateProperty* DelegateProperty); /** * * * @param DelegateProperty * @return */ static UBlueprintNodeSpawner* MakeActorBoundEventSpawner(FMulticastDelegateProperty* DelegateProperty); /** * Constructs UK2Node_Event spawner that is owned by UAnimInstance. Used for Anim Notificatios and montage * branching points. * * @param EventName * @return A new node-spawner, set up to spawn UK2Node_Event */ static UBlueprintNodeSpawner* MakeAnimOwnedEventSpawner(FName SignatureName, FText CustomCategory); }; //------------------------------------------------------------------------------ static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeMacroNodeSpawner(UEdGraph* MacroGraph) { check(MacroGraph != nullptr); check(MacroGraph->GetSchema()->GetGraphType(MacroGraph) == GT_Macro); UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(UK2Node_MacroInstance::StaticClass()); check(NodeSpawner != nullptr); auto CustomizeMacroNodeLambda = [](UEdGraphNode* NewNode, bool bIsTemplateNode, TWeakObjectPtr InMacroGraph) { UK2Node_MacroInstance* MacroNode = CastChecked(NewNode); if (InMacroGraph.IsValid()) { MacroNode->SetMacroGraph(InMacroGraph.Get()); } }; TWeakObjectPtr GraphPtr = MacroGraph; NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeMacroNodeLambda, GraphPtr); return NodeSpawner; } //------------------------------------------------------------------------------ static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeMessageNodeSpawner(UFunction* InterfaceFunction) { check(InterfaceFunction != nullptr); check(FKismetEditorUtilities::IsClassABlueprintInterface(CastChecked(InterfaceFunction->GetOuter()))); UBlueprintFunctionNodeSpawner* NodeSpawner = UBlueprintFunctionNodeSpawner::Create(UK2Node_Message::StaticClass(), InterfaceFunction); check(NodeSpawner != nullptr); auto SetNodeFunctionLambda = [](UEdGraphNode* NewNode, FFieldVariant FuncField) { UK2Node_Message* MessageNode = CastChecked(NewNode); MessageNode->FunctionReference.SetFromField(FuncField.Get(), /*bIsConsideredSelfContext =*/false); }; NodeSpawner->SetNodeFieldDelegate = UBlueprintFunctionNodeSpawner::FSetNodeFieldDelegate::CreateStatic(SetNodeFunctionLambda); NodeSpawner->DefaultMenuSignature.MenuName = FText::Format(LOCTEXT("MessageNodeMenuName", "{0} (Message)"), NodeSpawner->DefaultMenuSignature.MenuName); return NodeSpawner; } //------------------------------------------------------------------------------ template static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeDocumentationNodeSpawner() { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(DocNodeType::StaticClass()); check(NodeSpawner != nullptr); auto CustomizeMessageNodeLambda = [](UEdGraphNode* NewNode, bool bIsTemplateNode) { DocNodeType* DocNode = CastChecked(NewNode); UEdGraph* OuterGraph = NewNode->GetGraph(); check(OuterGraph != nullptr); UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(OuterGraph); check(Blueprint != nullptr); float const OldNodePosX = NewNode->NodePosX; float const OldNodePosY = NewNode->NodePosY; float const OldHalfHeight = NewNode->NodeHeight / 2.f; float const OldHalfWidth = NewNode->NodeWidth / 2.f; static const float DocNodePadding = 50.0f; FSlateRect Bounds(OldNodePosX - OldHalfWidth, OldNodePosY - OldHalfHeight, OldNodePosX + OldHalfWidth, OldNodePosY + OldHalfHeight); FKismetEditorUtilities::GetBoundsForSelectedNodes(Blueprint, Bounds, DocNodePadding); DocNode->SetBounds(Bounds); }; NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeMessageNodeLambda); return NodeSpawner; } //------------------------------------------------------------------------------ static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeCommentNodeSpawner() { UBlueprintNodeSpawner* NodeSpawner = MakeDocumentationNodeSpawner(); NodeSpawner->DefaultMenuSignature.MenuName = LOCTEXT("AddCommentActionMenuName", "Add Comment..."); auto OverrideMenuNameLambda = [](FBlueprintActionContext const& Context, IBlueprintNodeBinder::FBindingSet const& /*Bindings*/, FBlueprintActionUiSpec* UiSpecOut) { for (UBlueprint* Blueprint : Context.Blueprints) { if (FKismetEditorUtilities::GetNumberOfSelectedNodes(Blueprint) > 0) { UiSpecOut->MenuName = LOCTEXT("AddCommentFromSelectionMenuName", "Add Comment to Selection"); break; } } }; NodeSpawner->DynamicUiSignatureGetter = UBlueprintNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(OverrideMenuNameLambda); return NodeSpawner; } //------------------------------------------------------------------------------ static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeAssignDelegateNodeSpawner(FMulticastDelegateProperty* DelegateProperty) { // @TODO: it'd be awesome to have both nodes spawned by this available for // context pin matching (the delegate inputs and the event outputs) return UBlueprintDelegateNodeSpawner::Create(UK2Node_AssignDelegate::StaticClass(), DelegateProperty); } //------------------------------------------------------------------------------ static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeComponentBoundEventSpawner(FMulticastDelegateProperty* DelegateProperty) { return UBlueprintBoundEventNodeSpawner::Create(UK2Node_ComponentBoundEvent::StaticClass(), DelegateProperty); } //------------------------------------------------------------------------------ static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeActorBoundEventSpawner(FMulticastDelegateProperty* DelegateProperty) { return UBlueprintBoundEventNodeSpawner::Create(UK2Node_ActorBoundEvent::StaticClass(), DelegateProperty); } //------------------------------------------------------------------------------ static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeAnimOwnedEventSpawner(FName SignatureName, FText CustomCategory) { auto PostSpawnSetupLambda = [](UEdGraphNode* NewNode, bool /*bIsTemplateNode*/) { UK2Node_Event* ActorRefNode = CastChecked(NewNode); ActorRefNode->EventReference.SetExternalMember(ActorRefNode->CustomFunctionName, UAnimInstance::StaticClass()); }; UBlueprintNodeSpawner* NodeSpawner = UBlueprintEventNodeSpawner::Create(UK2Node_Event::StaticClass(), SignatureName); NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(PostSpawnSetupLambda); NodeSpawner->DefaultMenuSignature.Category = CustomCategory; return NodeSpawner; } /******************************************************************************* * Static FBlueprintActionDatabase Helpers ******************************************************************************/ namespace BlueprintActionDatabaseImpl { typedef FBlueprintActionDatabase::FActionList FActionList; /** * Mimics UEdGraphSchema_K2::CanUserKismetAccessVariable(); however, this * omits the filtering that CanUserKismetAccessVariable() does (saves that * for later with FBlueprintActionFilter). * * @param Property The property you want to check. * @return True if the property can be seen from a blueprint. */ static bool IsPropertyBlueprintVisible(FProperty const* const Property); /** * Checks to see if the specified function is a blueprint owned function * that was inherited from an implemented interface. * * @param Function The function to check. * @return True if the function is owned by a blueprint, and some (implemented) interface has a matching function name. */ static bool IsBlueprintInterfaceFunction(const UFunction* Function); /** * Checks to see if the specified function is a blueprint owned function * that was inherited from the blueprint's parent. * * @param Function The function to check. * @return True if the function is owned by a blueprint, and some parent has a matching function name. */ static bool IsInheritedBlueprintFunction(const UFunction* Function); /** * Retrieves all the actions pertaining to a class and its fields (functions, * variables, delegates, etc.). Actions that are conceptually owned by the * class. * * @param Class The class you want actions for. * @param ActionListOut The array you want filled with the requested actions. */ static void GetClassMemberActions(UClass* const Class, FActionList& ActionListOut); /** * Loops over all of the class's functions and creates a node-spawners for * any that are viable for blueprint use. Evolved from * FK2ActionMenuBuilder::GetFuncNodesForClass(), plus a series of other * FK2ActionMenuBuilder methods (GetAllInterfaceMessageActions, * GetEventsForBlueprint, etc). * * Ideally, any node that is constructed from a UFunction should go in here * (so we only ever loop through the class's functions once). We handle * UK2Node_CallFunction alongside UK2Node_Event. * * @param Class The class whose functions you want node-spawners for. * @param ActionListOut The list you want populated with the new spawners. */ static void AddClassFunctionActions(UClass const* const Class, FActionList& ActionListOut); /** * Loops over all of the class's properties and creates node-spawners for * any that are viable for blueprint use. Evolved from certain parts of * FK2ActionMenuBuilder::GetAllActionsForClass(). * * @param Class The class whose properties you want node-spawners for. * @param ActionListOut The list you want populated with the new spawners. */ static void AddClassPropertyActions(UClass const* const Class, FActionList& ActionListOut); /** * Loops over all of the class's data object's properties and creates node-spawners for * any that are viable for blueprint use. * * @param Class The class whose properties you want node-spawners for. * @param ActionListOut The list you want populated with the new spawners. */ static void AddClassDataObjectActions(UClass const* const Class, FActionList& ActionListOut); /** * Evolved from FClassDynamicCastHelper::GetClassDynamicCastNodes(). If the * specified class is a viable blueprint variable type, then two cast nodes * are added for it (UK2Node_DynamicCast, and UK2Node_ClassDynamicCast). * * @param Class The class who you want cast nodes for (they cast to this class). * @param ActionListOut The list you want populated with the new spawners. */ static void AddClassCastActions(UClass* const Class, FActionList& ActionListOut); /** * Adds custom actions to operate on the provided skeleton. Used primarily * to find AnimNotify event vocabulary * * @Param Skeleton The skeleton that may have anim notifies defined on it. */ static void AddSkeletonActions( const USkeleton& Skeleton, FActionList& ActionListOut); /** * If the associated class is a blueprint generated class, then this will * loop over the blueprint's graphs and create any node-spawners associated * with those graphs (like UK2Node_MacroInstance spawners for macro graphs). * * @param Blueprint The blueprint which you want graph associated node-spawners for. * @param ActionListOut The list you want populated with new spawners. */ static void AddBlueprintGraphActions(UBlueprint const* const Blueprint, FActionList& ActionListOut); /** * If the associated class is an anim blueprint generated class, then this * will loop over AnimNotification events in the anim blueprint generated * class and create node spawners for those events. * * @param Blueprint The blueprint which you want graph associated node-spawners for. * @param ActionListOut The list you want populated with new spawners. */ static void AddAnimBlueprintGraphActions( UAnimBlueprint const* AnimBlueprint, FActionList& ActionListOut ); /** * Emulates UEdGraphSchema::GetGraphContextActions(). If the supplied class * is a node type, then it will query the node's CDO for any actions it * wishes to add. This helps us keep the code in this file paired down, and * makes it easily extensible for new node types. * * @param NodeClass The class which you want node-spawners for. * @param ActionListOut The list you want populated with new spawners. */ static void GetNodeSpecificActions(TSubclassOf const NodeClass, FBlueprintActionDatabaseRegistrar& Registrar); /** * Callback to refresh the database when a blueprint has been altered * (clears the database entries for the blueprint's classes and recreates * them... a property/function could have been added/removed). * * @param InBlueprint The blueprint that has been altered. */ static void OnBlueprintChanged(UBlueprint* InBlueprint); /** * Callback to refresh the database when a new object has just been loaded * (to catch blueprint classes that weren't in the initial set, etc.) * * @param NewObject The object that was just loaded. */ static void OnAssetLoaded(UObject* NewObject); /** * Callback to refresh the database when a new object has just been created * (to catch new blueprint classes that weren't in the initial set, etc.) * * @param NewAssetInfo Data regarding the newly added asset. */ static void OnAssetAdded(FAssetData const& NewAssetInfo); /** * Callback to clear out object references so that a object can be deleted * without resistance from the actions cached here. Objects passed here * won't necessarily be deleted (the user could still choose to cancel). * * @param ObjectsForDelete A list of objects that MIGHT be deleted. */ static void OnAssetsPendingDelete(TArray const& ObjectsForDelete); /** * Callback to refresh the database when an object has been deleted (to * clear any related entries that were stored in the database) * * @param AssetInfo Data regarding the freshly removed asset. */ static void OnAssetRemoved(FAssetData const& AssetInfo); /** * Callback to refresh the database when an object has been deleted/unloaded * (to clear any related entries that were stored in the database) * * @param AssetObject The object being removed/deleted/unloaded. */ static void OnAssetRemoved(UObject* AssetObject); /** * Callback to refresh the database when a blueprint has been unloaded * (to clear any related entries that were stored in the database) * * @param BlueprintObj The blueprint being unloaded. */ static void OnBlueprintUnloaded(UBlueprint* BlueprintObj); /** * Callback to refresh the database when an object has been renamed (to clear * any related classes that were stored in the database under the old name) * * @param AssetInfo Data regarding the freshly removed asset. */ static void OnAssetRenamed(FAssetData const& AssetInfo, const FString& InOldName); /** * Callback to refresh/add all level blueprints owned by this world to the database * * @param NewWorld The world that was added. */ static void OnWorldAdded(UWorld* NewWorld); /** * Callback to clear all levels from the database when a world is destroyed * * @param DestroyedWorld The world that was destroyed */ static void OnWorldDestroyed(UWorld* DestroyedWorld); /** * Callback to re-evaluate all level blueprints owned by the world when the layout has changed * * @param World The owner of the level and the world to be re-evaluated. */ static void OnRefreshLevelScripts(UWorld* World); /** * Returns TRUE if the Object is valid for the database * * @param Object Object to check for validity * @return TRUE if the Blueprint is valid for the database */ static bool IsObjectValidForDatabase(UObject const* Object); /** * Refreshes database after a module is loaded or unloaded. */ static void OnModulesChanged(FName InModuleName, EModuleChangeReason InModuleChangeReason); /** * Refreshes database after project was hot-reloaded. */ static void OnReloadComplete(EReloadCompleteReason Reason); /** * Assets that we cleared from the database (to remove references, and make * way for a delete), but in-case the class wasn't deleted we need them * tracked here so we can add them back in. */ TSet> PendingDelete; /** */ bool bIsInitializing = false; /** True if a RefreshAll has been requested for the next Tick */ bool bRefreshAllRequested = false; } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnModulesChanged(FName InModuleName, EModuleChangeReason InModuleChangeReason) { if (InModuleChangeReason == EModuleChangeReason::ModuleLoaded || InModuleChangeReason == EModuleChangeReason::ModuleUnloaded) { BlueprintActionDatabaseImpl::bRefreshAllRequested = true; } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnReloadComplete(EReloadCompleteReason Reason) { BlueprintActionDatabaseImpl::bRefreshAllRequested = true; } //------------------------------------------------------------------------------ static bool BlueprintActionDatabaseImpl::IsPropertyBlueprintVisible(FProperty const* const Property) { bool const bIsAccessible = Property->HasAllPropertyFlags(CPF_BlueprintVisible); bool const bIsDelegate = Property->IsA(FMulticastDelegateProperty::StaticClass()); bool const bIsAssignableOrCallable = Property->HasAnyPropertyFlags(CPF_BlueprintAssignable | CPF_BlueprintCallable); return !Property->HasAnyPropertyFlags(CPF_Parm) && (bIsAccessible || (bIsDelegate && bIsAssignableOrCallable)); } //------------------------------------------------------------------------------ static bool BlueprintActionDatabaseImpl::IsBlueprintInterfaceFunction(const UFunction* Function) { bool bIsBpInterfaceFunc = false; if (UClass* FuncClass = Function->GetOwnerClass()) { if (UBlueprint* BpOuter = Cast(FuncClass->ClassGeneratedBy)) { FName FuncName = Function->GetFName(); for (int32 InterfaceIndex = 0; (InterfaceIndex < BpOuter->ImplementedInterfaces.Num()) && !bIsBpInterfaceFunc; ++InterfaceIndex) { FBPInterfaceDescription& InterfaceDesc = BpOuter->ImplementedInterfaces[InterfaceIndex]; if(!InterfaceDesc.Interface) { continue; } bIsBpInterfaceFunc = (InterfaceDesc.Interface->FindFunctionByName(FuncName) != nullptr); } } } return bIsBpInterfaceFunc; } //------------------------------------------------------------------------------ static bool BlueprintActionDatabaseImpl::IsInheritedBlueprintFunction(const UFunction* Function) { bool bIsBpInheritedFunc = false; if (UClass* FuncClass = Function->GetOwnerClass()) { if (UBlueprint* BpOwner = Cast(FuncClass->ClassGeneratedBy)) { FName FuncName = Function->GetFName(); if (UClass* ParentClass = BpOwner->ParentClass) { bIsBpInheritedFunc = (ParentClass->FindFunctionByName(FuncName, EIncludeSuperFlag::IncludeSuper) != nullptr); } } } return bIsBpInheritedFunc; } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::GetClassMemberActions(UClass* const Class, FActionList& ActionListOut) { // class field actions (nodes that represent and perform actions on // specific fields of the class... functions, properties, etc.) { AddClassFunctionActions(Class, ActionListOut); AddClassPropertyActions(Class, ActionListOut); AddClassDataObjectActions(Class, ActionListOut); // class UEnum actions are added by individual nodes via GetNodeSpecificActions() // class UScriptStruct actions are added by individual nodes via GetNodeSpecificActions() } AddClassCastActions(Class, ActionListOut); } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::AddClassFunctionActions(UClass const* const Class, FActionList& ActionListOut) { using namespace FBlueprintNodeSpawnerFactory; // for MakeMessageNodeSpawner() check(Class != nullptr); // loop over all the functions in the specified class; exclude-super because // we can always get the super functions by looking up that class separately for (TFieldIterator FunctionIt(Class, EFieldIteratorFlags::ExcludeSuper); FunctionIt; ++FunctionIt) { UFunction* Function = *FunctionIt; bool const bIsInheritedFunction = BlueprintActionDatabaseImpl::IsInheritedBlueprintFunction(Function); if (bIsInheritedFunction) { // inherited functions will be captured when the parent class is ran // through this function (no need to duplicate) continue; } bool const bIsBpInterfaceFunc = BlueprintActionDatabaseImpl::IsBlueprintInterfaceFunction(Function); if (UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(Function) && !bIsBpInterfaceFunc) { if (UBlueprintEventNodeSpawner* NodeSpawner = UBlueprintEventNodeSpawner::Create(Function)) { ActionListOut.Add(NodeSpawner); } } // If this is a promotable function, and it has already been registered // than do NOT add it to the asset action database. We should // probably have some better logic for this, like adding our own node spawner const bool bIsRegisteredPromotionFunc = TypePromoDebug::IsTypePromoEnabled() && FTypePromotion::IsFunctionPromotionReady(Function) && FTypePromotion::IsOperatorSpawnerRegistered(Function); if (UEdGraphSchema_K2::CanUserKismetCallFunction(Function)) { // @TODO: if this is a Blueprint, and this function is from a // Blueprint "implemented interface", then we don't need to // include it (the function is accounted for in from the // interface class). UBlueprintFunctionNodeSpawner* FuncSpawner = UBlueprintFunctionNodeSpawner::Create(Function); // Only add this action to the list of the operator function is not already registered. Otherwise we will // get a bunch of duplicate operator actions if (!bIsRegisteredPromotionFunc) { ActionListOut.Add(FuncSpawner); } if (FKismetEditorUtilities::IsClassABlueprintInterface(Class)) { // Use the default function name ActionListOut.Add(MakeMessageNodeSpawner(Function)); } } } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::AddClassPropertyActions(UClass const* const Class, FActionList& ActionListOut) { using namespace FBlueprintNodeSpawnerFactory; // for MakeDelegateNodeSpawner() bool const bIsComponent = Class->IsChildOf(); bool const bIsActorClass = Class->IsChildOf(); // loop over all the properties in the specified class; exclude-super because // we can always get the super properties by looking up that class separately for (TFieldIterator PropertyIt(Class, EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; if (!IsPropertyBlueprintVisible(Property)) { continue; } bool const bIsDelegate = Property->IsA(FMulticastDelegateProperty::StaticClass()); if (bIsDelegate) { FMulticastDelegateProperty* DelegateProperty = CastFieldChecked(Property); if (DelegateProperty->HasAnyPropertyFlags(CPF_BlueprintAssignable)) { UBlueprintNodeSpawner* AddSpawner = UBlueprintDelegateNodeSpawner::Create(UK2Node_AddDelegate::StaticClass(), DelegateProperty); ActionListOut.Add(AddSpawner); UBlueprintNodeSpawner* AssignSpawner = MakeAssignDelegateNodeSpawner(DelegateProperty); ActionListOut.Add(AssignSpawner); } if (DelegateProperty->HasAnyPropertyFlags(CPF_BlueprintCallable)) { UBlueprintNodeSpawner* CallSpawner = UBlueprintDelegateNodeSpawner::Create(UK2Node_CallDelegate::StaticClass(), DelegateProperty); ActionListOut.Add(CallSpawner); } UBlueprintNodeSpawner* RemoveSpawner = UBlueprintDelegateNodeSpawner::Create(UK2Node_RemoveDelegate::StaticClass(), DelegateProperty); ActionListOut.Add(RemoveSpawner); UBlueprintNodeSpawner* ClearSpawner = UBlueprintDelegateNodeSpawner::Create(UK2Node_ClearDelegate::StaticClass(), DelegateProperty); ActionListOut.Add(ClearSpawner); if (bIsComponent) { ActionListOut.Add(MakeComponentBoundEventSpawner(DelegateProperty)); } else if (bIsActorClass) { ActionListOut.Add(MakeActorBoundEventSpawner(DelegateProperty)); } } else { UBlueprintVariableNodeSpawner* GetterSpawner = UBlueprintVariableNodeSpawner::CreateFromMemberOrParam(UK2Node_VariableGet::StaticClass(), Property); ActionListOut.Add(GetterSpawner); UBlueprintVariableNodeSpawner* SetterSpawner = UBlueprintVariableNodeSpawner::CreateFromMemberOrParam(UK2Node_VariableSet::StaticClass(), Property); ActionListOut.Add(SetterSpawner); } } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::AddClassDataObjectActions(UClass const* const Class, FActionList& ActionListOut) { using namespace FBlueprintNodeSpawnerFactory; // for MakeDelegateNodeSpawner() // loop over all the properties in the specified class; exclude-super because // we can always get the super properties by looking up that class separately const UStruct* SparseDataStruct = Class->GetSparseClassDataStruct(); const UStruct* ParentSparseDataStruct = Class->GetSuperClass() ? Class->GetSuperClass()->GetSparseClassDataStruct() : nullptr; if (ParentSparseDataStruct != SparseDataStruct) { for (TFieldIterator PropertyIt(SparseDataStruct, EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; if (!IsPropertyBlueprintVisible(Property)) { continue; } UClass* NonConstClass = const_cast(Class); UBlueprintVariableNodeSpawner* GetterSpawner = UBlueprintVariableNodeSpawner::CreateFromMemberOrParam(UK2Node_VariableGet::StaticClass(), Property, nullptr, NonConstClass); ActionListOut.Add(GetterSpawner); // UBlueprintVariableNodeSpawner* SetterSpawner = UBlueprintVariableNodeSpawner::CreateFromMemberOrParam(UK2Node_VariableSet::StaticClass(), Property); // ActionListOut.Add(SetterSpawner); } } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::AddClassCastActions(UClass* Class, FActionList& ActionListOut) { Class = Class->GetAuthoritativeClass(); check(Class); UEdGraphSchema_K2 const* K2Schema = GetDefault(); bool bIsCastPermitted = UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Class); if (bIsCastPermitted) { auto CustomizeCastNodeLambda = [](UEdGraphNode* NewNode, bool bIsTemplateNode, UClass* TargetType) { UK2Node_DynamicCast* CastNode = CastChecked(NewNode); CastNode->TargetType = TargetType; }; UBlueprintNodeSpawner* CastObjNodeSpawner = UBlueprintNodeSpawner::Create(); CastObjNodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeCastNodeLambda, Class); ActionListOut.Add(CastObjNodeSpawner); UBlueprintNodeSpawner* CastClassNodeSpawner = UBlueprintNodeSpawner::Create(); CastClassNodeSpawner->CustomizeNodeDelegate = CastObjNodeSpawner->CustomizeNodeDelegate; ActionListOut.Add(CastClassNodeSpawner); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::AddSkeletonActions(const USkeleton& Skeleton, FActionList& ActionListOut) { TArray NotifyNames; Skeleton.CollectAnimationNotifies(NotifyNames); for (const FName& NotifyName : NotifyNames) { FString Label = NotifyName.ToString(); FString SignatureName = FString::Printf(TEXT("AnimNotify_%s"), *Label); ActionListOut.Add(FBlueprintNodeSpawnerFactory::MakeAnimOwnedEventSpawner(FName(*SignatureName), FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::AnimNotify))); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::AddBlueprintGraphActions(UBlueprint const* const Blueprint, FActionList& ActionListOut) { using namespace FBlueprintNodeSpawnerFactory; // for MakeMacroNodeSpawner() for (UEdGraph* MacroGraph : Blueprint->MacroGraphs) { ActionListOut.Add(MakeMacroNodeSpawner(MacroGraph)); } auto CreateEntriesForGraphLambda = [Blueprint, &ActionListOut](UEdGraph* FunctionGraph) { if (!FunctionGraph) { return; } TArray GraphEntryNodes; FunctionGraph->GetNodesOfClass(GraphEntryNodes); for (UK2Node_FunctionEntry* FunctionEntry : GraphEntryNodes) { UFunction* SkeletonFunction = FindUField(Blueprint->SkeletonGeneratedClass, FunctionGraph->GetFName()); // Create entries for function parameters if (SkeletonFunction != nullptr) { for (TFieldIterator ParamIt(SkeletonFunction); ParamIt && (ParamIt->PropertyFlags & CPF_Parm); ++ParamIt) { FProperty* Param = *ParamIt; const bool bIsFunctionInput = !Param->HasAnyPropertyFlags(CPF_ReturnParm) && (!Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm)); if (bIsFunctionInput) { UBlueprintNodeSpawner* GetVarSpawner = UBlueprintVariableNodeSpawner::CreateFromMemberOrParam(UK2Node_VariableGet::StaticClass(), Param, FunctionGraph); ActionListOut.Add(GetVarSpawner); } } } // Create entries for local variables for (FBPVariableDescription const& LocalVar : FunctionEntry->LocalVariables) { // Create a member reference so we can safely resolve the FProperty FMemberReference Reference; Reference.SetLocalMember(LocalVar.VarName, FunctionGraph->GetName(), LocalVar.VarGuid); UBlueprintNodeSpawner* GetVarSpawner = UBlueprintVariableNodeSpawner::CreateFromLocal(UK2Node_VariableGet::StaticClass(), FunctionGraph, LocalVar, Reference.ResolveMember(Blueprint->SkeletonGeneratedClass)); ActionListOut.Add(GetVarSpawner); UBlueprintNodeSpawner* SetVarSpawner = UBlueprintVariableNodeSpawner::CreateFromLocal(UK2Node_VariableSet::StaticClass(), FunctionGraph, LocalVar, Reference.ResolveMember(Blueprint->SkeletonGeneratedClass)); ActionListOut.Add(SetVarSpawner); } } }; auto CreateEntriesLambda = [&](const TArray& Graphs) { for (UEdGraph* const Graph : Graphs) { CreateEntriesForGraphLambda(Graph); } }; // local variables and parameters for functions CreateEntriesLambda(Blueprint->FunctionGraphs); // local variables and parameters for interfaces for (const FBPInterfaceDescription& Interface : Blueprint->ImplementedInterfaces) { CreateEntriesLambda(Interface.Graphs); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::AddAnimBlueprintGraphActions(UAnimBlueprint const* AnimBlueprint, FActionList& ActionListOut) { if (UAnimBlueprintGeneratedClass* GeneratedClass = AnimBlueprint->GetAnimBlueprintGeneratedClass()) { for (int32 NotifyIdx = 0; NotifyIdx < GeneratedClass->GetAnimNotifies().Num(); NotifyIdx++) { FName NotifyName = GeneratedClass->GetAnimNotifies()[NotifyIdx].NotifyName; if (NotifyName != NAME_None) { FString Label = NotifyName.ToString(); FString SignatureName = FString::Printf(TEXT("AnimNotify_%s"), *Label); ActionListOut.Add(FBlueprintNodeSpawnerFactory::MakeAnimOwnedEventSpawner(FName(*SignatureName), FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::AnimNotify))); } } } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::GetNodeSpecificActions(TSubclassOf const NodeClass, FBlueprintActionDatabaseRegistrar& Registrar) { using namespace FBlueprintNodeSpawnerFactory; // for MakeCommentNodeSpawner()/MakeDocumentationNodeSpawner() if (NodeClass->IsChildOf() && !NodeClass->HasAnyClassFlags(CLASS_Abstract)) { UK2Node const* const NodeCDO = NodeClass->GetDefaultObject(); check(NodeCDO != nullptr); NodeCDO->GetMenuActions(Registrar); } // unfortunately, UEdGraphNode_Comment is not a UK2Node and therefore cannot // leverage UK2Node's GetMenuActions() function, so here we HACK it in // // @TODO: DO NOT follow this example! Do as I say, not as I do! If we need // to support other nodes in a similar way, then we should come up // with a better (more generalized) solution. if (NodeClass == UEdGraphNode_Comment::StaticClass()) { Registrar.AddBlueprintAction(MakeCommentNodeSpawner()); } else if (NodeClass == UEdGraphNode_Documentation::StaticClass()) { // @TODO: BOOOOOOO! (see comment above) UBlueprintNodeSpawner* DocumentationSpawner = MakeDocumentationNodeSpawner(); DocumentationSpawner->DefaultMenuSignature.Category = LOCTEXT("DocumentationNodeCategory", "Documentation"); Registrar.AddBlueprintAction(DocumentationSpawner); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnBlueprintChanged(UBlueprint* Blueprint) { if (IsObjectValidForDatabase(Blueprint)) { FBlueprintActionDatabase& ActionDatabase = FBlueprintActionDatabase::Get(); ActionDatabase.RefreshAssetActions(Blueprint); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnAssetLoaded(UObject* NewObject) { if (UBlueprint* NewBlueprint = Cast(NewObject)) { OnBlueprintChanged(NewBlueprint); } else { FBlueprintActionDatabase& ActionDatabase = FBlueprintActionDatabase::Get(); ActionDatabase.RefreshAssetActions(NewObject); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnAssetAdded(FAssetData const& NewAssetInfo) { if (NewAssetInfo.IsAssetLoaded()) { UObject* AssetObject = NewAssetInfo.GetAsset(); if (UBlueprint* NewBlueprint = Cast(AssetObject)) { OnBlueprintChanged(NewBlueprint); } else { FBlueprintActionDatabase& ActionDatabase = FBlueprintActionDatabase::Get(); ActionDatabase.RefreshAssetActions(AssetObject); } } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnAssetsPendingDelete(TArray const& ObjectsForDelete) { FBlueprintActionDatabase& ActionDatabase = FBlueprintActionDatabase::Get(); for (UObject* DeletingObject : ObjectsForDelete) { // if (!IsObjectValidForDatabase(DeletingObject)) // { // continue; // } // have to temporarily remove references (so that this delete isn't // blocked by dangling references) if (ActionDatabase.ClearAssetActions(DeletingObject)) { ensure(IsObjectValidForDatabase(DeletingObject)); // in case they choose not to delete the object, we need to add // these back in to the database, so we track them here PendingDelete.Add(DeletingObject); } // If this asset contains a blueprint, ensure that it's actions are also marked for pending delete else if (const IBlueprintAssetHandler* Handler = FBlueprintAssetHandler::Get().FindHandler(DeletingObject->GetClass())) { UBlueprint* Blueprint = Handler->RetrieveBlueprint(DeletingObject); if (Blueprint && ActionDatabase.ClearAssetActions(Blueprint)) { PendingDelete.Add(Blueprint); } } } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnAssetRemoved(FAssetData const& AssetInfo) { FBlueprintActionDatabase& ActionDatabase = FBlueprintActionDatabase::Get(); if (AssetInfo.IsAssetLoaded()) { UObject* AssetObject = AssetInfo.GetAsset(); OnAssetRemoved(AssetObject); } else { ActionDatabase.ClearUnloadedAssetActions(AssetInfo.ObjectPath); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnAssetRemoved(UObject* AssetObject) { FBlueprintActionDatabase& ActionDatabase = FBlueprintActionDatabase::Get(); ActionDatabase.ClearAssetActions(AssetObject); for (auto It(PendingDelete.CreateIterator()); It; ++It) { if ((*It).Get() == AssetObject) { // the delete went through, so we don't need to track these for re-add It.RemoveCurrent(); break; } } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnBlueprintUnloaded(UBlueprint* BlueprintObj) { OnAssetRemoved(BlueprintObj); } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnAssetRenamed(FAssetData const& AssetInfo, const FString& InOldName) { FBlueprintActionDatabase& ActionDatabase = FBlueprintActionDatabase::Get(); if (!AssetInfo.IsAssetLoaded()) { ActionDatabase.MoveUnloadedAssetActions(*InOldName, AssetInfo.ObjectPath); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnWorldAdded(UWorld* NewWorld) { if (IsObjectValidForDatabase(NewWorld)) { FBlueprintActionDatabase::Get().RefreshAssetActions((UObject*)NewWorld); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnWorldDestroyed(UWorld* DestroyedWorld) { if (IsObjectValidForDatabase(DestroyedWorld)) { FBlueprintActionDatabase::Get().ClearAssetActions((UObject*)DestroyedWorld); } } //------------------------------------------------------------------------------ static void BlueprintActionDatabaseImpl::OnRefreshLevelScripts(UWorld* World) { if (IsObjectValidForDatabase(World)) { FBlueprintActionDatabase::Get().RefreshAssetActions((UObject*)World); } } //------------------------------------------------------------------------------ static bool BlueprintActionDatabaseImpl::IsObjectValidForDatabase(UObject const* Object) { bool bReturn = false; if( Object == nullptr ) { bReturn = false; } else if(Object->GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor | PKG_ForDiffing)) { // Do not keep track of any PIE/diff objects as they will not exist after those processes finish bReturn = false; } else if(Object->IsAsset()) { bReturn = true; } else if(Object->IsA()) { // If this is a blueprint contained within an asset, we can include it in the action database UObject* PotentialAsset = Object->GetOuter(); while (PotentialAsset) { if (PotentialAsset->IsAsset()) { bReturn = true; break; } PotentialAsset = PotentialAsset->GetOuter(); } } else if(UWorld const* World = Cast(Object)) { // We now use worlds as databse keys to manage the level scripts they own, but we only want Editor worlds. if(World->WorldType == EWorldType::Editor ) { bReturn = true; } } return bReturn; } /******************************************************************************* * FBlueprintActionDatabase ******************************************************************************/ static FBlueprintActionDatabase* DatabaseInst = nullptr; //------------------------------------------------------------------------------ FBlueprintActionDatabase& FBlueprintActionDatabase::Get() { if (DatabaseInst == nullptr) { DatabaseInst = new FBlueprintActionDatabase(); } return *DatabaseInst; } //------------------------------------------------------------------------------ FBlueprintActionDatabase* FBlueprintActionDatabase::TryGet() { return DatabaseInst; } //------------------------------------------------------------------------------ FBlueprintActionDatabase::FBlueprintActionDatabase() { RefreshAll(); OnAssetLoadedDelegateHandle = FCoreUObjectDelegates::OnAssetLoaded.AddStatic(&BlueprintActionDatabaseImpl::OnAssetLoaded); IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); OnAssetAddedDelegateHandle = AssetRegistry.OnAssetAdded().AddStatic(&BlueprintActionDatabaseImpl::OnAssetAdded); OnAssetRemovedDelegateHandle = AssetRegistry.OnAssetRemoved().AddStatic(&BlueprintActionDatabaseImpl::OnAssetRemoved); OnAssetRenamedDelegateHandle = AssetRegistry.OnAssetRenamed().AddStatic(&BlueprintActionDatabaseImpl::OnAssetRenamed); OnAssetsPreDeleteDelegateHandle = FEditorDelegates::OnAssetsPreDelete.AddStatic(&BlueprintActionDatabaseImpl::OnAssetsPendingDelete); OnBlueprintUnloadedDelegateHandle = FKismetEditorUtilities::OnBlueprintUnloaded.AddStatic(&BlueprintActionDatabaseImpl::OnBlueprintUnloaded); OnWorldAddedDelegateHandle = GEngine->OnWorldAdded().AddStatic(&BlueprintActionDatabaseImpl::OnWorldAdded); OnWorldDestroyedDelegateHandle = GEngine->OnWorldDestroyed().AddStatic(&BlueprintActionDatabaseImpl::OnWorldDestroyed); RefreshLevelScriptActionsDelegateHandle = FWorldDelegates::RefreshLevelScriptActions.AddStatic(&BlueprintActionDatabaseImpl::OnRefreshLevelScripts); OnModulesChangedDelegateHandle = FModuleManager::Get().OnModulesChanged().AddStatic(&BlueprintActionDatabaseImpl::OnModulesChanged); OnReloadCompleteDelegateHandle = FCoreUObjectDelegates::ReloadCompleteDelegate.AddStatic(&BlueprintActionDatabaseImpl::OnReloadComplete); } //------------------------------------------------------------------------------ FBlueprintActionDatabase::~FBlueprintActionDatabase() { FCoreUObjectDelegates::OnAssetLoaded.Remove(OnAssetLoadedDelegateHandle); if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetRegistry"))) { IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); AssetRegistry.OnAssetAdded().Remove(OnAssetAddedDelegateHandle); AssetRegistry.OnAssetRemoved().Remove(OnAssetRemovedDelegateHandle); AssetRegistry.OnAssetRenamed().Remove(OnAssetRenamedDelegateHandle); } FEditorDelegates::OnAssetsPreDelete.Remove(OnAssetsPreDeleteDelegateHandle); FKismetEditorUtilities::OnBlueprintUnloaded.Remove(OnBlueprintUnloadedDelegateHandle); if (GEngine) { GEngine->OnWorldAdded().Remove(OnWorldAddedDelegateHandle); GEngine->OnWorldDestroyed().Remove(OnWorldDestroyedDelegateHandle); } FWorldDelegates::RefreshLevelScriptActions.Remove(RefreshLevelScriptActionsDelegateHandle); FModuleManager::Get().OnModulesChanged().Remove(OnModulesChangedDelegateHandle); FCoreUObjectDelegates::ReloadCompleteDelegate.Remove(OnReloadCompleteDelegateHandle); } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::AddReferencedObjects(FReferenceCollector& Collector) { TSet AllActions; for (auto& ActionListIt : ActionRegistry) { AllActions.Append(ActionListIt.Value); Collector.AddReferencedObjects(ActionListIt.Value); } // shouldn't have to do this, as the elements listed here should also be // accounted for in the regular ActionRegistry, but just in case we fail to // remove an element from here when we should.... this'll make sure these // elements stick around (so we don't crash in ClearUnloadedAssetActions) if (UnloadedActionRegistry.Num() > 0) { TSet UnloadedActions; for (auto& UnloadedActionListIt : UnloadedActionRegistry) { UnloadedActions.Append(UnloadedActionListIt.Value); Collector.AddReferencedObjects(UnloadedActionListIt.Value); } auto OrphanedUnloadedActions = UnloadedActions.Difference(AllActions.Intersect(UnloadedActions)); ensureMsgf(OrphanedUnloadedActions.Num() == 0, TEXT("Found %d unloaded actions that were not also present in the Action Registry. This should be 0."), UnloadedActions.Num()); } } FString FBlueprintActionDatabase::GetReferencerName() const { return TEXT("FBlueprintActionDatabase"); } int32 GBlueprintDatabasePrimingMaxPerFrame = 16; static FAutoConsoleVariableRef CVarBlueprintDatabasePrimingMaxPerFrame( TEXT("bp.DatabasePrimingMaxPerFrame"), GBlueprintDatabasePrimingMaxPerFrame, TEXT("How many entries should be primed in to the database per frame."), ECVF_Default ); //------------------------------------------------------------------------------ void FBlueprintActionDatabase::Tick(float DeltaTime) { const double DurationThreshold = FMath::Min(0.003, DeltaTime * 0.01); if (BlueprintActionDatabaseImpl::bRefreshAllRequested) { RefreshAll(); } // entries that were removed from the database, in preparation for a delete // (but the user ended up not deleting the object) for (TWeakObjectPtr AssetObj : BlueprintActionDatabaseImpl::PendingDelete) { if (AssetObj.IsValid()) { RefreshAssetActions(AssetObj.Get()); } } BlueprintActionDatabaseImpl::PendingDelete.Empty(); // priming every database entry at once would cause a hitch, so we spread it // out over several frames int32 PrimedCount = 0; while ((ActionPrimingQueue.Num() > 0) && (PrimedCount < GBlueprintDatabasePrimingMaxPerFrame)) { auto ActionIndex = ActionPrimingQueue.CreateIterator(); const FObjectKey& ActionsKey = ActionIndex.Key(); if (ActionsKey.ResolveObjectPtr()) { // make sure this class is still listed in the database if (FActionList* ClassActionList = ActionRegistry.Find(ActionsKey)) { int32& ActionListIndex = ActionIndex.Value(); for (; (ActionListIndex < ClassActionList->Num()) && (PrimedCount < GBlueprintDatabasePrimingMaxPerFrame); ++ActionListIndex) { UBlueprintNodeSpawner* Action = (*ClassActionList)[ActionListIndex]; Action->Prime(); ++PrimedCount; } if (ActionListIndex >= ClassActionList->Num()) { ActionPrimingQueue.Remove(ActionsKey); } } else { ActionPrimingQueue.Remove(ActionsKey); } } else { ActionPrimingQueue.Remove(ActionsKey); } } // Handle deferred removals. while (ActionRemoveQueue.Num() > 0) { ClearAssetActions(ActionRemoveQueue.Pop()); } } //------------------------------------------------------------------------------ TStatId FBlueprintActionDatabase::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FBlueprintActionDatabase, STATGROUP_Tickables); } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::DeferredRemoveEntry(FObjectKey const& InKey) { ActionRemoveQueue.AddUnique(InKey); } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::RefreshAll() { TGuardValue ScopedInitialization(BlueprintActionDatabaseImpl::bIsInitializing, true); BlueprintActionDatabaseImpl::bRefreshAllRequested = false; // Remove callbacks from blueprints for (TObjectIterator BlueprintIt; BlueprintIt; ++BlueprintIt) { UBlueprint* Blueprint = *BlueprintIt; // Level script BPs are registered using the associated world context. This will clear any registered LSBP actions. const bool bIsLevelScript = FBlueprintEditorUtils::IsLevelScriptBlueprint(Blueprint); if (bIsLevelScript) { const ULevelScriptBlueprint* LSBP = CastChecked(Blueprint); if (UWorld* World = LSBP->GetWorld()) { ClearAssetActions(World); } } // Do this for all BP assets to both clear registered actions and remove callbacks. In the case of LSBPs, this will just remove callbacks. ClearAssetActions(Blueprint); } ActionRegistry.Empty(); UnloadedActionRegistry.Empty(); for (TObjectIterator ClassIt; ClassIt; ++ClassIt) { UClass* const Class = (*ClassIt); RefreshClassActions(Class); } // this handles creating entries for skeletons that were loaded before the database was alive: for( TObjectIterator SkeletonIt; SkeletonIt; ++SkeletonIt ) { FActionList& ClassActionList = ActionRegistry.FindOrAdd(*SkeletonIt); BlueprintActionDatabaseImpl::AddSkeletonActions(**SkeletonIt, ClassActionList); } FComponentTypeRegistry::Get().SubscribeToComponentList(ComponentTypes).RemoveAll(this); // this handles creating entries for components that were loaded before the database was alive: FComponentTypeRegistry::Get().SubscribeToComponentList(ComponentTypes).AddRaw(this, &FBlueprintActionDatabase::RefreshComponentActions); RefreshComponentActions(); // Refresh existing worlds RefreshWorlds(); } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::RefreshWorlds() { // Add all level scripts from current world const TIndirectArray& WorldContexts = GEngine->GetWorldContexts(); for (const FWorldContext& Context : WorldContexts) { if( Context.WorldType == EWorldType::Editor ) { if( UWorld* CurrentWorld = Context.World()) { RefreshAssetActions((UObject*)CurrentWorld); } } } } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::RefreshClassActions(UClass* const Class) { using namespace BlueprintActionDatabaseImpl; check(Class != nullptr); bool const bOutOfDateClass = Class->HasAnyClassFlags(CLASS_NewerVersionExists); bool const bHiddenClass = Class->HasAnyClassFlags(CLASS_Hidden); bool const bIsBlueprintClass = (Cast(Class) != nullptr); bool const bIsLevelScript = Class->ClassGeneratedBy && Cast(Class->ClassGeneratedBy)->BlueprintType == EBlueprintType::BPTYPE_LevelScript; if (bOutOfDateClass || bIsLevelScript || bHiddenClass) { ActionRegistry.Remove(Class); return; } else if (bIsBlueprintClass) { UBlueprint* Blueprint = Cast(Class->ClassGeneratedBy); if ((Blueprint != nullptr) && BlueprintActionDatabaseImpl::IsObjectValidForDatabase(Blueprint)) { // to prevent us from hitting this twice on init (once for the skel // class, again for the generated class) bool const bRefresh = !bIsInitializing || (Blueprint->SkeletonGeneratedClass == nullptr) || (Blueprint->SkeletonGeneratedClass == Class); if (bRefresh) { RefreshAssetActions(Blueprint); } } } // here we account for "autonomous" standalone nodes, and any nodes that // exist in a separate module; each UK2Node has a chance to append its // own actions (presumably ones that would spawn that node)... else if (Class->IsChildOf()) { FActionList& ClassActionList = ActionRegistry.FindOrAdd(Class); if (!bIsInitializing) { ClassActionList.Empty(); } FBlueprintActionDatabaseRegistrar Registrar(ActionRegistry, UnloadedActionRegistry, ActionPrimingQueue, Class); if (!bIsInitializing) { // if this a call to RefreshClassActions() from somewhere other than // RefreshAll(), then we should only add actions for this class (the // node could be adding actions, probably duplicate ones for assets) Registrar.ActionKeyFilter = Class; } // also, should catch any actions dealing with global UFields (like // global structs, enums, etc.; elements that wouldn't be caught // normally when sifting through fields on all known classes) GetNodeSpecificActions(Class, Registrar); // don't worry, the registrar marks new actions for priming } else if (Class->IsChildOf()) { FBlueprintActionDatabaseRegistrar Registrar(ActionRegistry, UnloadedActionRegistry, ActionPrimingQueue); Cast(Class->ClassDefaultObject)->GetTypeActions(Registrar); } else { FActionList& ClassActionList = ActionRegistry.FindOrAdd(Class); if (!bIsInitializing) { ClassActionList.Empty(); // if we're only refreshing this class (and not init'ing the whole // database), then we have to reach out to individual nodes in case // they'd add entries for this as well FBlueprintActionDatabaseRegistrar Registrar(ActionRegistry, UnloadedActionRegistry, ActionPrimingQueue); Registrar.ActionKeyFilter = Class; // only want actions for this class RegisterAllNodeActions(Registrar); } GetClassMemberActions(Class, ClassActionList); // queue the newly added actions for priming if (ClassActionList.Num() > 0) { ActionPrimingQueue.Add(Class, 0); } else { ActionRegistry.Remove(Class); } } // blueprints are handled in RefreshAssetActions() if (!bIsInitializing && !bIsBlueprintClass) { EntryRefreshDelegate.Broadcast(Class); } } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::RefreshAssetActions(UObject* const AssetObject) { using namespace BlueprintActionDatabaseImpl; // this method is very expensive and is only for blueprint editor functionality // it should remain that way as *greatly* increases cook times, etc! if (IsRunningCommandlet()) { return; } FActionList& AssetActionList = ActionRegistry.FindOrAdd(AssetObject); for (UBlueprintNodeSpawner* Action : AssetActionList) { // because some asserts expect everything to be cleaned up in a // single GC pass, we need to ensure that any previously cached node templates // are cleaned up here before we add any new node spawners. Action->ClearCachedTemplateNode(); } AssetActionList.Empty(); if (!IsObjectValidForDatabase(AssetObject)) { return; } if(const USkeleton* Skeleton = Cast(AssetObject)) { AddSkeletonActions(*Skeleton, AssetActionList); } UBlueprint* BlueprintAsset = Cast(AssetObject); if (BlueprintAsset != nullptr) { AddBlueprintGraphActions(BlueprintAsset, AssetActionList); if (UClass* SkeletonClass = BlueprintAsset->SkeletonGeneratedClass) { GetClassMemberActions(SkeletonClass, AssetActionList); } if( const UAnimBlueprint* AnimBlueprint = Cast(BlueprintAsset) ) { AddAnimBlueprintGraphActions( AnimBlueprint, AssetActionList ); } FBlueprintActionDatabaseRegistrar Registrar(ActionRegistry, UnloadedActionRegistry, ActionPrimingQueue); if (!bIsInitializing) { // if this a call to RefreshAssetActions() from somewhere other than // RefreshAll(), then we should only add actions for this class (the // node could be adding actions, probably duplicate ones for assets) Registrar.ActionKeyFilter = BlueprintAsset->GeneratedClass; } BlueprintAsset->GetInstanceActions(Registrar); UBlueprint::FChangedEvent& OnBPChanged = BlueprintAsset->OnChanged(); UBlueprint::FCompiledEvent& OnBPCompiled = BlueprintAsset->OnCompiled(); // have to be careful not to register this callback twice for the // blueprint if (!OnBPChanged.IsBoundToObject(this)) { OnBPChanged.AddRaw(this, &FBlueprintActionDatabase::OnBlueprintChanged); } if (!OnBPCompiled.IsBoundToObject(this)) { OnBPCompiled.AddRaw(this, &FBlueprintActionDatabase::OnBlueprintChanged); } } UWorld* WorldAsset = Cast(AssetObject); if (WorldAsset && WorldAsset->WorldType == EWorldType::Editor) { for( ULevel* Level : WorldAsset->GetLevels() ) { UBlueprint* LevelScript = Level->GetLevelScriptBlueprint(true); if (LevelScript != nullptr) { AddBlueprintGraphActions(LevelScript, AssetActionList); if (UClass* SkeletonClass = LevelScript->SkeletonGeneratedClass) { GetClassMemberActions(SkeletonClass, AssetActionList); } // Register for change and compilation notifications if (!LevelScript->OnChanged().IsBoundToObject(this)) { LevelScript->OnChanged().AddRaw(this, &FBlueprintActionDatabase::OnBlueprintChanged); } if (!LevelScript->OnCompiled().IsBoundToObject(this)) { LevelScript->OnCompiled().AddRaw(this, &FBlueprintActionDatabase::OnBlueprintChanged); } } } } FBlueprintActionDatabaseRegistrar Registrar(ActionRegistry, UnloadedActionRegistry, ActionPrimingQueue); Registrar.ActionKeyFilter = AssetObject; // make sure actions only associated with this asset get added // nodes may have actions they wish to add actions for this asset RegisterAllNodeActions(Registrar); // Will clear up any unloaded asset actions associated with this object, if any ClearUnloadedAssetActions(*AssetObject->GetPathName()); if (!IsValid(AssetObject)) { ClearAssetActions(AssetObject); } else if (AssetActionList.Num() > 0) { // queue these assets for priming ActionPrimingQueue.Add(AssetObject, 0); } // we don't want to clear entries for blueprints, mainly because we // use the presence of an entry to know if we've set the blueprint's // OnChanged(), but also because most blueprints will have actions at some // later point. Same goes for in-editor world assets because they are used to manage level script blueprints. else if (!BlueprintAsset && (!WorldAsset || WorldAsset->WorldType != EWorldType::Editor)) { ClearAssetActions(AssetObject); } if (!bIsInitializing) { EntryRefreshDelegate.Broadcast(AssetObject); } } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::RefreshComponentActions() { check(ComponentTypes); FActionList& ClassActionList = ActionRegistry.FindOrAdd(UBlueprintComponentNodeSpawner::StaticClass()); ClassActionList.Empty(ComponentTypes->Num()); for (const FComponentTypeEntry& ComponentType : *ComponentTypes) { if (UBlueprintComponentNodeSpawner* NodeSpawner = UBlueprintComponentNodeSpawner::Create(ComponentType)) { ClassActionList.Add(NodeSpawner); } } } //------------------------------------------------------------------------------ bool FBlueprintActionDatabase::ClearAssetActions(UObject* const AssetObject) { FObjectKey ObjectKey(AssetObject); return ClearAssetActions(ObjectKey); } //------------------------------------------------------------------------------ bool FBlueprintActionDatabase::ClearAssetActions(const FObjectKey& AssetObjectKey) { FActionList* ActionList = ActionRegistry.Find(AssetObjectKey); bool const bHasEntry = (ActionList != nullptr); if (bHasEntry) { for (UBlueprintNodeSpawner* Action : *ActionList) { if (Action != nullptr) { // because some asserts expect everything to be cleaned up in a // single GC pass, we can't wait for the GC'd Action to release its // template node from the cache Action->ClearCachedTemplateNode(); } } ActionRegistry.Remove(AssetObjectKey); } if (UObject* AssetObject = AssetObjectKey.ResolveObjectPtr()) { if (UBlueprint* BlueprintAsset = Cast(AssetObject)) { BlueprintAsset->OnChanged().RemoveAll(this); BlueprintAsset->OnCompiled().RemoveAll(this); } if (bHasEntry && (ActionList->Num() > 0) && !BlueprintActionDatabaseImpl::bIsInitializing) { EntryRemovedDelegate.Broadcast(AssetObject); } } return bHasEntry; } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::ClearUnloadedAssetActions(FName ObjectPath) { // Check if the asset can be found in the unloaded action registry, if it can, we need to remove it if(TArray* UnloadedActionList = UnloadedActionRegistry.Find(ObjectPath)) { for(UBlueprintNodeSpawner* NodeSpawner : *UnloadedActionList) { FActionList* ActionList = ActionRegistry.Find(NodeSpawner->NodeClass.Get()); // Remove the NodeSpawner from the main registry, it will be replaced with the loaded version of the action ActionList->Remove(NodeSpawner); } // Remove the asset's path from the unloaded registry, it is no longer needed UnloadedActionRegistry.Remove(ObjectPath); } } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::MoveUnloadedAssetActions(FName SourceObjectPath, FName TargetObjectPath) { // Check if the asset can be found in the unloaded action registry, if it can, we need to remove it and re-add under the new name if(TArray* UnloadedActionList = UnloadedActionRegistry.Find(SourceObjectPath)) { check(!UnloadedActionRegistry.Find(TargetObjectPath)); // Add the entire array to the database under the new path UnloadedActionRegistry.Add(TargetObjectPath, *UnloadedActionList); // Remove the old asset's path from the unloaded registry, it is no longer needed UnloadedActionRegistry.Remove(SourceObjectPath); } } //------------------------------------------------------------------------------ FBlueprintActionDatabase::FActionRegistry const& FBlueprintActionDatabase::GetAllActions() { // if this is the first time that we're querying for actions, generate the // list before returning it if (ActionRegistry.Num() == 0) { RefreshAll(); } return ActionRegistry; } //------------------------------------------------------------------------------ void FBlueprintActionDatabase::RegisterAllNodeActions(FBlueprintActionDatabaseRegistrar& Registrar) { // nodes may have actions they wish to add for this asset TArray NodeClassList; GetDerivedClasses(UK2Node::StaticClass(), NodeClassList); for (UClass* NodeClass : NodeClassList) { TGuardValue< TSubclassOf > ScopedNodeClass(Registrar.GeneratingClass, NodeClass); BlueprintActionDatabaseImpl::GetNodeSpecificActions(NodeClass, Registrar); } } void FBlueprintActionDatabase::OnBlueprintChanged(UBlueprint* InBlueprint) { if( InBlueprint->BlueprintType == BPTYPE_LevelScript ) { // Levelscript blueprints are managed through their owning worlds. RefreshWorlds(); } else { BlueprintActionDatabaseImpl::OnBlueprintChanged(InBlueprint); } } #undef LOCTEXT_NAMESPACE