// 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 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 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); const float OldNodePosX = static_cast(NewNode->NodePosX); const float OldNodePosY = static_cast(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(); 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 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; /** * 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 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 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(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; } // 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(); 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) && FBlueprintActionDatabase::IsClassAllowed(Class, FBlueprintActionDatabase::EPermissionsContext::Node); 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::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::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.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()) { // 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(); // 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(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")).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>>& ActionListIt : ActionRegistry) { TArray>& ActionList = ActionListIt.Value; for (TObjectPtr& 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>>& UnloadedActionListIt : UnloadedActionRegistry) { TArray>& ActionList = UnloadedActionListIt.Value; for (TObjectPtr& 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 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 ObjectsToProcess; const bool bIncludeNestedObjects = false; GetObjectsWithPackage(ModuleScriptPackage, ObjectsToProcess, bIncludeNestedObjects, RF_ClassDefaultObject); for (UObject* Object : ObjectsToProcess) { UClass* ObjectAsClass = Cast(Object); const bool bIsNativeTypeObject = ObjectAsClass != nullptr || Object->IsA() || Object->IsA(); // 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 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 ScopedInitialization(BlueprintActionDatabaseImpl::bIsInitializing, true); BlueprintActionDatabaseImpl::bRefreshAllRequested = false; // Refresh other systems before the database is recreated PreRefresh(true); // 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); } 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) { 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(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) { // Early out if the class is filtered if (bFilterClass) { return; } 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()) { // 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()) { // Early out if the class is filtered if (bFilterClass) { return; } FBlueprintActionDatabaseRegistrar Registrar(ActionRegistry, UnloadedActionRegistry, ActionPrimingQueue); Cast(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(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(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(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 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); } } 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(); 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(); 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(); return BlueprintEditorSettings->HasClassFiltering() || BlueprintEditorSettings->HasClassOnPinFiltering() || BlueprintEditorSettings->HasClassPathFiltering() || BlueprintEditorSettings->HasClassPathOnPinFiltering(); } bool FBlueprintActionDatabase::IsFieldAllowed(UField const* InField, EPermissionsContext InContext) { if (UFunction const* Function = Cast(InField)) { return IsFunctionAllowed(Function, InContext); } else if (UEnum const* Enum = Cast(InField)) { return IsEnumAllowed(Enum, InContext); } else if (UScriptStruct const* ScriptStruct = Cast(InField)) { return IsStructAllowed(ScriptStruct, InContext); } else if (UClass const* Class = Cast(InField)) { return IsClassAllowed(Class, InContext); } return true; } bool FBlueprintActionDatabase::IsFunctionAllowed(UFunction const* InFunction, EPermissionsContext InContext) { UBlueprintEditorSettings* BlueprintEditorSettings = GetMutableDefault(); 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 PropertyIt(InFunction); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; if (FStructProperty* StructProperty = CastField(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(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()->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()->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()->GetEnumPermissions(); if(EnumPermissions.HasFiltering()) { return EnumPermissions.PassesFilter(InEnumPath.ToString()); } return true; } bool FBlueprintActionDatabase::IsStructAllowed(UScriptStruct const* InStruct, EPermissionsContext InContext) { const FPathPermissionList& StructPermissions = GetMutableDefault()->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()->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()->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(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(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(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(InPinType.PinSubCategoryMemberReference)) { return IsFunctionAllowed(PinSignature, EPermissionsContext::Pin); } } } return true; } #undef LOCTEXT_NAMESPACE