Files
UnrealEngine/Engine/Source/Editor/BlueprintGraph/Private/BlueprintActionDatabase.cpp
2025-05-18 13:04:45 +08:00

2188 lines
79 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BlueprintActionDatabase.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintActionFilter.h"
#include "BlueprintAssetHandler.h"
#include "BlueprintBoundEventNodeSpawner.h"
#include "BlueprintComponentNodeSpawner.h"
#include "BlueprintDelegateNodeSpawner.h"
#include "BlueprintEditorSettings.h"
#include "BlueprintEventNodeSpawner.h"
#include "BlueprintFieldNodeSpawner.h"
#include "BlueprintFunctionNodeSpawner.h"
#include "BlueprintNodeBinder.h"
#include "BlueprintNodeSpawner.h"
#include "BlueprintTypePromotion.h"
#include "BlueprintVariableNodeSpawner.h"
#include "ComponentTypeRegistry.h"
#include "Components/ActorComponent.h"
#include "Containers/EnumAsByte.h"
#include "Containers/IndirectArray.h"
#include "Containers/Set.h"
#include "Containers/StringFwd.h"
#include "CoreGlobals.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
// used below in BlueprintActionDatabaseImpl::GetNodeSpectificActions()
#include "EdGraph/EdGraphNode_Documentation.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphNode_Comment.h"
#include "EdGraphSchema_K2.h"
#include "Editor.h"
#include "EditorCategoryUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Engine/Engine.h"
#include "Engine/EngineTypes.h"
#include "Engine/Level.h"
#include "Engine/LevelScriptBlueprint.h"
#include "Engine/MemberReference.h"
#include "Engine/World.h"
#include "EngineLogs.h"
#include "GameFramework/Actor.h"
#include "HAL/IConsoleManager.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "K2Node.h"
#include "K2Node_ActorBoundEvent.h"
#include "K2Node_AddDelegate.h"
// used below in BlueprintActionDatabaseImpl::AddClassPropertyActions()
#include "K2Node_AssignDelegate.h"
#include "K2Node_CallDelegate.h"
// used below in BlueprintActionDatabaseImpl::AddClassCastActions()
#include "K2Node_ClassDynamicCast.h"
#include "K2Node_ClearDelegate.h"
#include "K2Node_ComponentBoundEvent.h"
#include "K2Node_DynamicCast.h"
#include "K2Node_Event.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_MacroInstance.h"
// used below in FBlueprintNodeSpawnerFactory::MakeMacroNodeSpawner()
// used below in FBlueprintNodeSpawnerFactory::MakeComponentBoundEventSpawner()/MakeActorBoundEventSpawner()
// used below in FBlueprintNodeSpawnerFactory::MakeMessageNodeSpawner()
#include "K2Node_Message.h"
#include "K2Node_RemoveDelegate.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Layout/SlateRect.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Math/UnrealMathSSE.h"
#include "Misc/AssertionMacros.h"
#include "Misc/NamePermissionList.h"
#include "Modules/ModuleManager.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
#include "PropertyPermissionList.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "Templates/Tuple.h"
#include "Templates/UnrealTemplate.h"
#include "Trace/Detail/Channel.h"
#include "UObject/Class.h"
#include "UObject/EnumProperty.h"
#include "UObject/Field.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"
#include "UObject/ObjectPtr.h"
#include "UObject/Package.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "UObject/UnrealNames.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.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 <class DocNodeType>
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);
};
//------------------------------------------------------------------------------
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<UEdGraph> InMacroGraph)
{
UK2Node_MacroInstance* MacroNode = CastChecked<UK2Node_MacroInstance>(NewNode);
if (InMacroGraph.IsValid())
{
MacroNode->SetMacroGraph(InMacroGraph.Get());
}
};
TWeakObjectPtr<UEdGraph> GraphPtr = MacroGraph;
NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeMacroNodeLambda, GraphPtr);
return NodeSpawner;
}
//------------------------------------------------------------------------------
static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeMessageNodeSpawner(UFunction* InterfaceFunction)
{
check(InterfaceFunction != nullptr);
check(FKismetEditorUtilities::IsClassABlueprintInterface(CastChecked<UClass>(InterfaceFunction->GetOuter())));
UBlueprintFunctionNodeSpawner* NodeSpawner = UBlueprintFunctionNodeSpawner::Create(UK2Node_Message::StaticClass(), InterfaceFunction);
check(NodeSpawner != nullptr);
auto SetNodeFunctionLambda = [](UEdGraphNode* NewNode, FFieldVariant FuncField)
{
UK2Node_Message* MessageNode = CastChecked<UK2Node_Message>(NewNode);
MessageNode->FunctionReference.SetFromField<UFunction>(FuncField.Get<UField>(), /*bIsConsideredSelfContext =*/false);
};
NodeSpawner->SetNodeFieldDelegate = UBlueprintFunctionNodeSpawner::FSetNodeFieldDelegate::CreateStatic(SetNodeFunctionLambda);
NodeSpawner->DefaultMenuSignature.MenuName = FText::Format(LOCTEXT("MessageNodeMenuName", "{0} (Message)"), NodeSpawner->DefaultMenuSignature.MenuName);
return NodeSpawner;
}
//------------------------------------------------------------------------------
template <class DocNodeType>
static UBlueprintNodeSpawner* FBlueprintNodeSpawnerFactory::MakeDocumentationNodeSpawner()
{
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(DocNodeType::StaticClass());
check(NodeSpawner != nullptr);
auto CustomizeMessageNodeLambda = [](UEdGraphNode* NewNode, bool bIsTemplateNode)
{
DocNodeType* DocNode = CastChecked<DocNodeType>(NewNode);
UEdGraph* OuterGraph = NewNode->GetGraph();
check(OuterGraph != nullptr);
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(OuterGraph);
check(Blueprint != nullptr);
const float OldNodePosX = static_cast<float>(NewNode->NodePosX);
const float OldNodePosY = static_cast<float>(NewNode->NodePosY);
const float OldHalfHeight = NewNode->NodeHeight / 2.f;
const float 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<UEdGraphNode_Comment>();
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 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);
/**
* 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);
/**
* 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<UEdGraphNode const> 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<UObject*> 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<TWeakObjectPtr<UObject>> PendingDelete;
/**
* Modules that were explicitly loaded at runtime but have not yet been
* registered into the database. These will be processed on the next tick.
*/
TSet<FName> PendingModules;
/**
* Modules that were explicitly loaded at runtime and registered into
* the database. We keep track of them here so that in the off chance
* we allow it to be unloaded, we can then trigger a database refresh.
*/
TSet<FName> LoadedModules;
/** */
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)
{
switch (InModuleChangeReason)
{
case EModuleChangeReason::ModuleLoaded:
// If not already tracked, add it to the list of modules that need to be registered on the next tick.
if (!LoadedModules.Contains(InModuleName))
{
PendingModules.Add(InModuleName);
}
break;
case EModuleChangeReason::ModuleUnloaded:
// If pending, it was unloaded in the same frame, so we just need to remove it, and no refresh is needed.
if (!PendingModules.Remove(InModuleName))
{
// If already registered as a loaded module, then we need to remove it and do a full refresh on the next tick.
if (LoadedModules.Remove(InModuleName))
{
bRefreshAllRequested = true;
}
}
// Guard against the possibility of unloading a pending module that is also already registered.
checkf(!LoadedModules.Contains(InModuleName), TEXT("Module %s was unloaded, but wasn't unregistered from the Blueprint action database."), *InModuleName.ToString());
break;
default:
break;
}
}
//------------------------------------------------------------------------------
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);
bool bVisible = !Property->HasAnyPropertyFlags(CPF_Parm) && (bIsAccessible || (bIsDelegate && bIsAssignableOrCallable));
if (bVisible)
{
bVisible = FPropertyEditorPermissionList::Get().DoesPropertyPassFilter(Property->GetOwnerStruct(), Property->GetFName());
}
return bVisible;
}
//------------------------------------------------------------------------------
static bool BlueprintActionDatabaseImpl::IsBlueprintInterfaceFunction(const UFunction* Function)
{
bool bIsBpInterfaceFunc = false;
if (UClass* FuncClass = Function->GetOwnerClass())
{
if (UBlueprint* BpOuter = Cast<UBlueprint>(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<UBlueprint>(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<UFunction> 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;
}
// Apply general filtering for functions
if(!FBlueprintActionDatabase::IsFunctionAllowed(Function, FBlueprintActionDatabase::EPermissionsContext::Node))
{
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<UActorComponent>();
bool const bIsActorClass = Class->IsChildOf<AActor>();
// 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<FProperty> 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<FMulticastDelegateProperty>(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<FProperty> PropertyIt(SparseDataStruct, EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt)
{
FProperty* Property = *PropertyIt;
if (!IsPropertyBlueprintVisible(Property))
{
continue;
}
UClass* NonConstClass = const_cast<UClass*>(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<UEdGraphSchema_K2>();
bool bIsCastPermitted = UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Class) && FBlueprintActionDatabase::IsClassAllowed(Class, FBlueprintActionDatabase::EPermissionsContext::Node);
if (bIsCastPermitted)
{
auto CustomizeCastNodeLambda = [](UEdGraphNode* NewNode, bool bIsTemplateNode, UClass* TargetType)
{
UK2Node_DynamicCast* CastNode = CastChecked<UK2Node_DynamicCast>(NewNode);
CastNode->TargetType = TargetType;
};
UBlueprintNodeSpawner* CastObjNodeSpawner = UBlueprintNodeSpawner::Create<UK2Node_DynamicCast>();
CastObjNodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(CustomizeCastNodeLambda, Class);
ActionListOut.Add(CastObjNodeSpawner);
UBlueprintNodeSpawner* CastClassNodeSpawner = UBlueprintNodeSpawner::Create<UK2Node_ClassDynamicCast>();
CastClassNodeSpawner->CustomizeNodeDelegate = CastObjNodeSpawner->CustomizeNodeDelegate;
ActionListOut.Add(CastClassNodeSpawner);
}
}
//------------------------------------------------------------------------------
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<UK2Node_FunctionEntry*> GraphEntryNodes;
FunctionGraph->GetNodesOfClass<UK2Node_FunctionEntry>(GraphEntryNodes);
for (UK2Node_FunctionEntry* FunctionEntry : GraphEntryNodes)
{
UFunction* SkeletonFunction = FindUField<UFunction>(Blueprint->SkeletonGeneratedClass, FunctionGraph->GetFName());
// Create entries for function parameters
if (SkeletonFunction != nullptr)
{
for (TFieldIterator<FProperty> 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<FProperty>(Blueprint->SkeletonGeneratedClass));
ActionListOut.Add(GetVarSpawner);
UBlueprintNodeSpawner* SetVarSpawner = UBlueprintVariableNodeSpawner::CreateFromLocal(UK2Node_VariableSet::StaticClass(), FunctionGraph, LocalVar, Reference.ResolveMember<FProperty>(Blueprint->SkeletonGeneratedClass));
ActionListOut.Add(SetVarSpawner);
}
}
};
auto CreateEntriesLambda = [&](const TArray<UEdGraph*>& 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::GetNodeSpecificActions(TSubclassOf<UEdGraphNode const> const NodeClass, FBlueprintActionDatabaseRegistrar& Registrar)
{
using namespace FBlueprintNodeSpawnerFactory; // for MakeCommentNodeSpawner()/MakeDocumentationNodeSpawner()
if (NodeClass->IsChildOf<UK2Node>() && !NodeClass->HasAnyClassFlags(CLASS_Abstract))
{
UK2Node const* const NodeCDO = NodeClass->GetDefaultObject<UK2Node>();
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<UEdGraphNode_Documentation>();
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<UBlueprint>(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<UBlueprint>(AssetObject))
{
OnBlueprintChanged(NewBlueprint);
}
else
{
FBlueprintActionDatabase& ActionDatabase = FBlueprintActionDatabase::Get();
ActionDatabase.RefreshAssetActions(AssetObject);
}
}
}
//------------------------------------------------------------------------------
static void BlueprintActionDatabaseImpl::OnAssetsPendingDelete(TArray<UObject*> 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.GetSoftObjectPath());
}
}
//------------------------------------------------------------------------------
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(FSoftObjectPath(InOldName), AssetInfo.GetSoftObjectPath());
}
}
//------------------------------------------------------------------------------
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<UBlueprint>())
{
// 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<UWorld>(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();
// Run init after constructing to avoid potentially creating multiple
// instances of the BAD:
DatabaseInst->Init();
}
return *DatabaseInst;
}
//------------------------------------------------------------------------------
FBlueprintActionDatabase* FBlueprintActionDatabase::TryGet()
{
return DatabaseInst;
}
//------------------------------------------------------------------------------
FBlueprintActionDatabase::FBlueprintActionDatabase()
{
}
void FBlueprintActionDatabase::Init()
{
RefreshAll();
OnAssetLoadedDelegateHandle = FCoreUObjectDelegates::OnAssetLoaded.AddStatic(&BlueprintActionDatabaseImpl::OnAssetLoaded);
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(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<FAssetRegistryModule>(TEXT("AssetRegistry")).TryGet();
if (AssetRegistry)
{
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)
{
for (TPair<FObjectKey, TArray<TObjectPtr<UBlueprintNodeSpawner>>>& ActionListIt : ActionRegistry)
{
TArray<TObjectPtr<UBlueprintNodeSpawner>>& ActionList = ActionListIt.Value;
for (TObjectPtr<UBlueprintNodeSpawner>& Action : ActionList)
{
// We have some reports of invalid action ptrs during GC - try to catch that case here without crashing the editor while reference gathering.
if (!Action || (GIsGarbageCollecting && !Action->IsValidLowLevel()))
{
const UObject* Key = ActionListIt.Key.ResolveObjectPtr();
ensureMsgf(false, TEXT("Invalid action (0x%016llx) registered for object: %s"), (int64)(PTRINT)Action.Get(), Key ? *Key->GetName() : TEXT("NULL"));
continue;
}
Collector.AddReferencedObject(Action);
}
}
for (TPair<FSoftObjectPath, TArray<TObjectPtr<UBlueprintNodeSpawner>>>& UnloadedActionListIt : UnloadedActionRegistry)
{
TArray<TObjectPtr<UBlueprintNodeSpawner>>& ActionList = UnloadedActionListIt.Value;
for (TObjectPtr<UBlueprintNodeSpawner>& Action : ActionList)
{
// Similar to above; however, we don't have any reports of failure here during GC. Nonetheless, we'll try and catch an invalid ptr value just in case.
if (!Action || (GIsGarbageCollecting && !Action->IsValidLowLevel()))
{
ensureMsgf(false, TEXT("Invalid action (0x%016llx) registered for unloaded object path: %s"), (int64)(PTRINT)Action.Get(), *UnloadedActionListIt.Key.ToString());
continue;
}
Collector.AddReferencedObject(Action);
}
}
}
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();
}
else if (!BlueprintActionDatabaseImpl::PendingModules.IsEmpty())
{
PreRefresh(false);
}
// Check for any modules that may have been loaded since the last tick. Even if we call RefreshAll() above, we still want to run
// through this list in order to keep track of loaded modules containing native script types that are registered into the database.
if(!BlueprintActionDatabaseImpl::PendingModules.IsEmpty())
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionDatabase::ProcessLoadedModules);
// Treat this as an initialization event so that the registrar is configured to register new types rather than to refresh existing ones. For
// example, if this module contains a new node type that implements a GetMenuActions() override, most implementations assume that a NULL action
// key filter indicates the initialization path. This ensures we get the same behavior as node types that are registered via the RefreshAll() API.
TGuardValue<bool> ScopedInitialization(BlueprintActionDatabaseImpl::bIsInitializing, true);
// Register actions for any new modules that were explicitly loaded prior to this tick. Note that native type objects defined within the module
// may have already been registered prior to receiving the load event; in that case, action(s) associated with those objects are already present.
for (const FName& LoadedModule : BlueprintActionDatabaseImpl::PendingModules)
{
if (BlueprintActionDatabaseImpl::LoadedModules.Contains(LoadedModule))
{
continue;
}
// Look for a script package that's associated with this module load. If there isn't one, we can skip it.
const FName ModuleScriptPackageName = FPackageName::GetModuleScriptPackageName(LoadedModule);
if (const UPackage* ModuleScriptPackage = FindPackage(nullptr, *ModuleScriptPackageName.ToString()))
{
TArray<UObject*> ObjectsToProcess;
const bool bIncludeNestedObjects = false;
GetObjectsWithPackage(ModuleScriptPackage, ObjectsToProcess, bIncludeNestedObjects, RF_ClassDefaultObject);
for (UObject* Object : ObjectsToProcess)
{
UClass* ObjectAsClass = Cast<UClass>(Object);
const bool bIsNativeTypeObject = ObjectAsClass != nullptr || Object->IsA<UScriptStruct>() || Object->IsA<UEnum>();
// Only need to include native types and those not already added through the registrar at initialization time.
if (!bIsNativeTypeObject || ActionRegistry.Contains(Object))
{
continue;
}
if (ObjectAsClass)
{
RefreshClassActions(ObjectAsClass);
}
else
{
RefreshAssetActions(Object);
}
}
// We only need to track modules that contain a script package in the off-chance that it is ever unloaded, in which case we'd need
// to refresh the database to account for any types that go missing as a result. Otherwise, we can ignore this module when unloaded.
BlueprintActionDatabaseImpl::LoadedModules.Add(LoadedModule);
}
}
BlueprintActionDatabaseImpl::PendingModules.Empty();
}
// entries that were removed from the database, in preparation for a delete
// (but the user ended up not deleting the object)
for (TWeakObjectPtr<UObject> 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()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionDatabase::RefreshAll);
TGuardValue<bool> ScopedInitialization(BlueprintActionDatabaseImpl::bIsInitializing, true);
BlueprintActionDatabaseImpl::bRefreshAllRequested = false;
// Refresh other systems before the database is recreated
PreRefresh(true);
// Remove callbacks from blueprints
for (TObjectIterator<UBlueprint> 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<ULevelScriptBlueprint>(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<UClass> ClassIt; ClassIt; ++ClassIt)
{
UClass* const Class = (*ClassIt);
RefreshClassActions(Class);
}
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<FWorldContext>& 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)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionDatabase::RefreshClassActions);
using namespace BlueprintActionDatabaseImpl;
check(Class != nullptr);
bool const bFilterClass = !IsClassAllowed(Class, EPermissionsContext::Asset);
bool const bOutOfDateClass = Class->HasAnyClassFlags(CLASS_NewerVersionExists);
bool const bHiddenClass = Class->HasAnyClassFlags(CLASS_Hidden);
bool const bIsBlueprintClass = (Cast<UBlueprintGeneratedClass>(Class) != nullptr);
bool const bIsLevelScript = Class->ClassGeneratedBy && Cast<UBlueprint>(Class->ClassGeneratedBy)->BlueprintType == EBlueprintType::BPTYPE_LevelScript;
if (bOutOfDateClass || bIsLevelScript || bHiddenClass)
{
ActionRegistry.Remove(Class);
return;
}
else if (bIsBlueprintClass)
{
// Early out if the class is filtered
if (bFilterClass)
{
return;
}
UBlueprint* Blueprint = Cast<UBlueprint>(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<UEdGraphNode>())
{
// Early out if the class is filtered
if (bFilterClass)
{
return;
}
{
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
// Filter out actions by node class
if(HasClassFiltering())
{
FActionList& ClassActionList = ActionRegistry.FindOrAdd(Class);
ClassActionList.RemoveAllSwap([this](UBlueprintNodeSpawner* InAction)
{
return !IsClassAllowed(InAction->NodeClass.Get(), EPermissionsContext::Node);
});
}
}
else if (Class->IsChildOf<UBlueprint>())
{
// Early out if the class is filtered
if (bFilterClass)
{
return;
}
FBlueprintActionDatabaseRegistrar Registrar(ActionRegistry, UnloadedActionRegistry, ActionPrimingQueue);
Cast<UBlueprint>(Class->GetDefaultObject(false))->GetTypeActions(Registrar);
}
else
{
FActionList& ClassActionList = ActionRegistry.FindOrAdd(Class);
if (!bIsInitializing && !bFilterClass)
{
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);
}
// Note: We still run this if we're filtering the class, as the class itself may expose properties/functions/etc that derived non-filtered classes need access to
GetClassMemberActions(Class, ClassActionList);
// queue the newly added actions for priming
if (ClassActionList.Num() > 0)
{
ActionPrimingQueue.Add(Class, 0);
if (HasClassFiltering())
{
// Filter out actions by node class
ClassActionList.RemoveAllSwap([this](UBlueprintNodeSpawner* InAction)
{
return !IsClassAllowed(InAction->NodeClass.Get(), EPermissionsContext::Node);
});
}
}
else
{
ActionRegistry.Remove(Class);
}
}
// blueprints are handled in RefreshAssetActions()
if (!bIsInitializing && !bIsBlueprintClass)
{
EntryRefreshDelegate.Broadcast(Class);
}
}
//------------------------------------------------------------------------------
void FBlueprintActionDatabase::RefreshAssetActions(UObject* const AssetObject)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionDatabase::RefreshAssetActions);
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 (!IsClassAllowed(AssetObject->GetClass(), EPermissionsContext::Asset))
{
return;
}
UBlueprint* BlueprintAsset = Cast<UBlueprint>(AssetObject);
if (BlueprintAsset != nullptr)
{
AddBlueprintGraphActions(BlueprintAsset, AssetActionList);
if (UClass* SkeletonClass = BlueprintAsset->SkeletonGeneratedClass)
{
GetClassMemberActions(SkeletonClass, 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<UWorld>(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(FSoftObjectPath(AssetObject));
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);
}
if (HasClassFiltering())
{
// Filter out actions by node class
AssetActionList.RemoveAllSwap([this](UBlueprintNodeSpawner* InAction)
{
return !IsClassAllowed(InAction->NodeClass.Get(), EPermissionsContext::Node);
});
}
}
//------------------------------------------------------------------------------
void FBlueprintActionDatabase::RefreshComponentActions()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionDatabase::RefreshComponentActions);
check(ComponentTypes);
FActionList& ClassActionList = ActionRegistry.FindOrAdd(UBlueprintComponentNodeSpawner::StaticClass());
ClassActionList.Empty(ComponentTypes->Num());
for (const FComponentTypeEntry& ComponentType : *ComponentTypes)
{
if (!IsClassAllowed(ComponentType.ComponentClass, EPermissionsContext::Node))
{
continue;
}
if (UBlueprintComponentNodeSpawner* NodeSpawner = UBlueprintComponentNodeSpawner::Create(ComponentType))
{
ClassActionList.Add(NodeSpawner);
}
}
if (HasClassFiltering())
{
// Filter out actions by node class
ClassActionList.RemoveAllSwap([this](UBlueprintNodeSpawner* InAction)
{
return !IsClassAllowed(InAction->NodeClass.Get(), EPermissionsContext::Node);
});
}
}
//------------------------------------------------------------------------------
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<UBlueprint>(AssetObject))
{
BlueprintAsset->OnChanged().RemoveAll(this);
BlueprintAsset->OnCompiled().RemoveAll(this);
}
if (bHasEntry && (ActionList->Num() > 0) && !BlueprintActionDatabaseImpl::bIsInitializing)
{
EntryRemovedDelegate.Broadcast(AssetObject);
}
}
return bHasEntry;
}
//------------------------------------------------------------------------------
void FBlueprintActionDatabase::ClearUnloadedAssetActions(const FSoftObjectPath& ObjectPath)
{
// Check if the asset can be found in the unloaded action registry, if it can, we need to remove it
if(auto* 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(const FSoftObjectPath& SourceObjectPath, const FSoftObjectPath& 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(auto* 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)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionDatabase::RegisterAllNodeActions);
// nodes may have actions they wish to add for this asset
TArray<UClass*> NodeClassList;
GetDerivedClasses(UK2Node::StaticClass(), NodeClassList);
for (UClass* NodeClass : NodeClassList)
{
TGuardValue< TSubclassOf<UEdGraphNode> > 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);
}
}
void FBlueprintActionDatabase::PreRefresh(bool bRefreshAll)
{
// Refresh other systems as necessary, doing it here avoids redundant work
FTypePromotion::RefreshPromotionTables();
}
bool FBlueprintActionDatabase::IsClassAllowed(UClass const* InClass, EPermissionsContext InContext)
{
if (InClass == nullptr)
{
return false;
}
UBlueprintEditorSettings* BlueprintEditorSettings = GetMutableDefault<UBlueprintEditorSettings>();
switch(InContext)
{
case EPermissionsContext::Property:
case EPermissionsContext::Node:
case EPermissionsContext::Asset:
if(BlueprintEditorSettings->HasClassFiltering())
{
return BlueprintEditorSettings->IsClassAllowed(InClass);
}
break;
case EPermissionsContext::Pin:
if(BlueprintEditorSettings->HasClassOnPinFiltering())
{
return BlueprintEditorSettings->IsClassAllowedOnPin(InClass);
}
break;
default:
break;
}
return true;
}
bool FBlueprintActionDatabase::IsClassAllowed(const FTopLevelAssetPath& InClassPath, EPermissionsContext InContext)
{
if(!InClassPath.IsValid())
{
return false;
}
UBlueprintEditorSettings* BlueprintEditorSettings = GetMutableDefault<UBlueprintEditorSettings>();
switch(InContext)
{
case EPermissionsContext::Property:
case EPermissionsContext::Node:
case EPermissionsContext::Asset:
if (BlueprintEditorSettings->HasClassPathFiltering())
{
return BlueprintEditorSettings->IsClassPathAllowed(InClassPath);
}
break;
case EPermissionsContext::Pin:
if(BlueprintEditorSettings->HasClassPathOnPinFiltering())
{
return BlueprintEditorSettings->IsClassPathAllowedOnPin(InClassPath);
}
break;
default:
break;
}
return true;
}
bool FBlueprintActionDatabase::HasClassFiltering()
{
UBlueprintEditorSettings* BlueprintEditorSettings = GetMutableDefault<UBlueprintEditorSettings>();
return BlueprintEditorSettings->HasClassFiltering() ||
BlueprintEditorSettings->HasClassOnPinFiltering() ||
BlueprintEditorSettings->HasClassPathFiltering() ||
BlueprintEditorSettings->HasClassPathOnPinFiltering();
}
bool FBlueprintActionDatabase::IsFieldAllowed(UField const* InField, EPermissionsContext InContext)
{
if (UFunction const* Function = Cast<UFunction>(InField))
{
return IsFunctionAllowed(Function, InContext);
}
else if (UEnum const* Enum = Cast<UEnum>(InField))
{
return IsEnumAllowed(Enum, InContext);
}
else if (UScriptStruct const* ScriptStruct = Cast<UScriptStruct>(InField))
{
return IsStructAllowed(ScriptStruct, InContext);
}
else if (UClass const* Class = Cast<UClass>(InField))
{
return IsClassAllowed(Class, InContext);
}
return true;
}
bool FBlueprintActionDatabase::IsFunctionAllowed(UFunction const* InFunction, EPermissionsContext InContext)
{
UBlueprintEditorSettings* BlueprintEditorSettings = GetMutableDefault<UBlueprintEditorSettings>();
const FPathPermissionList& FunctionPermissions = BlueprintEditorSettings->GetFunctionPermissions();
const FPathPermissionList& EnumPermissions = BlueprintEditorSettings->GetEnumPermissions();
const FPathPermissionList& StructPermissions = BlueprintEditorSettings->GetStructPermissions();
const FNamePermissionList& PinCategoryPermissions = BlueprintEditorSettings->GetPinCategoryPermissions();
// Apply general filtering for functions
if (FunctionPermissions.HasFiltering())
{
TStringBuilder<256> ResultBuilder;
InFunction->GetPathName(nullptr, ResultBuilder);
if (!FunctionPermissions.PassesFilter(ResultBuilder.ToView()))
{
return false;
}
}
if (UClass* Class = InFunction->GetOuterUClass())
{
if (!IsClassAllowed(Class, EPermissionsContext::Asset))
{
UE_LOG(LogBlueprint, Warning, TEXT("Function %s was filtered because its class (%s) was filtered"), *InFunction->GetPathName(), *Class->GetName());
return false;
}
}
// If we have filtering for other fields we need to check function parameters that might reference them
if (EnumPermissions.HasFiltering() || StructPermissions.HasFiltering() || PinCategoryPermissions.HasFiltering())
{
for (TFieldIterator<FProperty> PropertyIt(InFunction); PropertyIt; ++PropertyIt)
{
FProperty* Property = *PropertyIt;
if (FStructProperty* StructProperty = CastField<FStructProperty>(Property))
{
if (StructProperty->Struct)
{
if (!IsStructAllowed(StructProperty->Struct, EPermissionsContext::Pin))
{
UE_LOG(LogBlueprint, Warning, TEXT("Function %s was filtered because one of its parameters (struct %s) was filtered"), *InFunction->GetPathName(), *Property->GetName(), *StructProperty->Struct->GetPathName());
return false;
}
}
}
else if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property))
{
if (UEnum* Enum = EnumProperty->GetEnum())
{
if (!IsEnumAllowed(Enum, EPermissionsContext::Pin))
{
UE_LOG(LogBlueprint, Warning, TEXT("Function %s was filtered because one of its parameters %s (enum %s) was filtered"), *InFunction->GetPathName(), *Property->GetName(), *Enum->GetPathName());
return false;
}
}
}
else
{
FEdGraphPinType PinType;
if(GetDefault<UEdGraphSchema_K2>()->ConvertPropertyToPinType(Property, PinType))
{
if(!IsPinTypeAllowed(PinType))
{
UE_LOG(LogBlueprint, Warning, TEXT("Function %s was filtered because one of its parameters %s (%s) was filtered"), *InFunction->GetPathName(), *Property->GetName(), *Property->GetCPPType());
return false;
}
}
else
{
UE_LOG(LogBlueprint, Warning, TEXT("Function %s was filtered because one of its parameters %s (%s) was not able to be converted to a pin"), *InFunction->GetPathName(), *Property->GetName(), *Property->GetCPPType());
return false;
}
}
}
}
return true;
}
bool FBlueprintActionDatabase::IsEnumAllowed(UEnum const* InEnum, EPermissionsContext InContext)
{
const FPathPermissionList& EnumPermissions = GetMutableDefault<UBlueprintEditorSettings>()->GetEnumPermissions();
if(EnumPermissions.HasFiltering())
{
TStringBuilder<256> ResultBuilder;
InEnum->GetPathName(nullptr, ResultBuilder);
return EnumPermissions.PassesFilter(ResultBuilder.ToView());
}
return true;
}
bool FBlueprintActionDatabase::IsEnumAllowed(const FTopLevelAssetPath& InEnumPath, EPermissionsContext InContext)
{
const FPathPermissionList& EnumPermissions = GetMutableDefault<UBlueprintEditorSettings>()->GetEnumPermissions();
if(EnumPermissions.HasFiltering())
{
return EnumPermissions.PassesFilter(InEnumPath.ToString());
}
return true;
}
bool FBlueprintActionDatabase::IsStructAllowed(UScriptStruct const* InStruct, EPermissionsContext InContext)
{
const FPathPermissionList& StructPermissions = GetMutableDefault<UBlueprintEditorSettings>()->GetStructPermissions();
if(StructPermissions.HasFiltering())
{
TStringBuilder<256> ResultBuilder;
InStruct->GetPathName(nullptr, ResultBuilder);
return StructPermissions.PassesFilter(ResultBuilder.ToView());
}
return true;
}
bool FBlueprintActionDatabase::IsStructAllowed(const FTopLevelAssetPath& InStructPath, EPermissionsContext InContext)
{
const FPathPermissionList& StructPermissions = GetMutableDefault<UBlueprintEditorSettings>()->GetStructPermissions();
if(StructPermissions.HasFiltering())
{
return StructPermissions.PassesFilter(InStructPath.ToString());
}
return true;
}
bool FBlueprintActionDatabase::IsPinTypeAllowed(const FEdGraphPinType& InPinType, const FTopLevelAssetPath& InUnloadedAssetPath)
{
// First check if the pin's category is allowed
const FNamePermissionList& PinCategoryPermissions = GetMutableDefault<UBlueprintEditorSettings>()->GetPinCategoryPermissions();
if(PinCategoryPermissions.HasFiltering())
{
if(!PinCategoryPermissions.PassesFilter(InPinType.PinCategory))
{
return false;
}
if(InPinType.PinCategory == UEdGraphSchema_K2::PC_Struct)
{
if (InUnloadedAssetPath.IsValid())
{
return IsStructAllowed(InUnloadedAssetPath, EPermissionsContext::Pin);
}
else if(const UScriptStruct* ScriptStruct = Cast<UScriptStruct>(InPinType.PinSubCategoryObject))
{
return IsStructAllowed(ScriptStruct, EPermissionsContext::Pin);
}
}
else if(InPinType.PinCategory == UEdGraphSchema_K2::PC_Enum || InPinType.PinCategory == UEdGraphSchema_K2::PC_Byte)
{
if (InUnloadedAssetPath.IsValid())
{
return IsEnumAllowed(InUnloadedAssetPath, EPermissionsContext::Pin);
}
else if(const UEnum* Enum = Cast<UEnum>(InPinType.PinSubCategoryObject))
{
return IsEnumAllowed(Enum, EPermissionsContext::Pin);
}
}
else if(InPinType.PinCategory == UEdGraphSchema_K2::AllObjectTypes ||
InPinType.PinCategory == UEdGraphSchema_K2::PC_Class ||
InPinType.PinCategory == UEdGraphSchema_K2::PC_Object ||
InPinType.PinCategory == UEdGraphSchema_K2::PC_Interface ||
InPinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass ||
InPinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject)
{
if (InUnloadedAssetPath.IsValid())
{
return IsClassAllowed(InUnloadedAssetPath, EPermissionsContext::Pin);
}
else if(const UClass* Class = Cast<UClass>(InPinType.PinSubCategoryObject))
{
return IsClassAllowed(Class, EPermissionsContext::Pin);
}
}
else if(InPinType.PinCategory == UEdGraphSchema_K2::PC_Delegate ||
InPinType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate)
{
if(const UFunction* PinSignature = FMemberReference::ResolveSimpleMemberReference<UFunction>(InPinType.PinSubCategoryMemberReference))
{
return IsFunctionAllowed(PinSignature, EPermissionsContext::Pin);
}
}
}
return true;
}
#undef LOCTEXT_NAMESPACE