767 lines
27 KiB
C++
767 lines
27 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "BlueprintActionMenuBuilder.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "BlueprintEditorSettings.h"
|
|
#include "Settings/EditorStyleSettings.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "BlueprintNodeBinder.h"
|
|
#include "BlueprintActionMenuItem.h"
|
|
#include "BlueprintActionFilter.h"
|
|
#include "BlueprintDragDropMenuItem.h"
|
|
#include "BlueprintActionDatabase.h"
|
|
#include "BlueprintNodeSpawner.h"
|
|
#include "BlueprintDelegateNodeSpawner.h"
|
|
#include "BlueprintVariableNodeSpawner.h"
|
|
#include "EditorCategoryUtils.h"
|
|
#include "ObjectEditorUtils.h"
|
|
|
|
#if ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
#include "Misc/OutputDeviceFile.h"
|
|
#endif // ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
|
|
#define LOCTEXT_NAMESPACE "BlueprintActionMenuBuilder"
|
|
DEFINE_LOG_CATEGORY_STATIC(LogBlueprintActionMenuItemFactory, Log, All);
|
|
|
|
/*******************************************************************************
|
|
* FBlueprintActionMenuItemFactory
|
|
******************************************************************************/
|
|
|
|
class FBlueprintActionMenuItemFactory
|
|
{
|
|
public:
|
|
/**
|
|
* Menu item factory constructor. Sets up the blueprint context, which
|
|
* is utilized when configuring blueprint menu items' names/tooltips/etc.
|
|
*
|
|
* @param Context The blueprint context for the menu being built.
|
|
*/
|
|
FBlueprintActionMenuItemFactory(FBlueprintActionContext const& Context);
|
|
|
|
/** A root category to perpend every menu item with */
|
|
FText RootCategory;
|
|
/** The menu sort order to set every menu item with */
|
|
int32 MenuGrouping;
|
|
/** Cached context for the blueprint menu being built */
|
|
FBlueprintActionContext const& Context;
|
|
|
|
/**
|
|
* Spawns a new FBlueprintActionMenuItem with the node-spawner. Constructs
|
|
* the menu item's category, name, tooltip, etc.
|
|
*
|
|
* @param Action The node-spawner that the new menu item should wrap.
|
|
* @return A newly allocated FBlueprintActionMenuItem (which wraps the supplied action).
|
|
*/
|
|
TSharedPtr<FBlueprintActionMenuItem> MakeActionMenuItem(FBlueprintActionInfo const& ActionInfo);
|
|
|
|
/**
|
|
* Spawns a new FBlueprintDragDropMenuItem with the node-spawner. Constructs
|
|
* the menu item's category, name, tooltip, etc.
|
|
*
|
|
* @param SampleAction One of the (possibly) many node-spawners that this menu item is set to represent.
|
|
* @return A newly allocated FBlueprintActionMenuItem (which wraps the supplied action).
|
|
*/
|
|
TSharedPtr<FBlueprintDragDropMenuItem> MakeDragDropMenuItem(UBlueprintNodeSpawner const* SampleAction);
|
|
|
|
/**
|
|
*
|
|
*
|
|
* @param BoundAction
|
|
* @return
|
|
*/
|
|
TSharedPtr<FBlueprintActionMenuItem> MakeBoundMenuItem(FBlueprintActionInfo const& ActionInfo);
|
|
|
|
private:
|
|
/**
|
|
* Utility getter function that retrieves the blueprint context for the menu
|
|
* items being made.
|
|
*
|
|
* @return The first blueprint out of the cached FBlueprintActionContext.
|
|
*/
|
|
UBlueprint* GetTargetBlueprint() const;
|
|
|
|
/**
|
|
* @return
|
|
*/
|
|
UEdGraph* GetTargetGraph() const;
|
|
|
|
/**
|
|
* @param ActionInfo
|
|
* @return
|
|
*/
|
|
FBlueprintActionUiSpec GetActionUiSignature(FBlueprintActionInfo const& ActionInfo);
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
FBlueprintActionMenuItemFactory::FBlueprintActionMenuItemFactory(FBlueprintActionContext const& ContextIn)
|
|
: RootCategory(FText::GetEmpty())
|
|
, MenuGrouping(0)
|
|
, Context(ContextIn)
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
TSharedPtr<FBlueprintActionMenuItem> FBlueprintActionMenuItemFactory::MakeActionMenuItem(FBlueprintActionInfo const& ActionInfo)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionMenuItemFactory::MakeActionMenuItem);
|
|
|
|
FBlueprintActionUiSpec UiSignature = GetActionUiSignature(ActionInfo);
|
|
|
|
UBlueprintNodeSpawner const* Action = ActionInfo.NodeSpawner;
|
|
FBlueprintActionMenuItem* NewMenuItem = new FBlueprintActionMenuItem(Action, UiSignature, IBlueprintNodeBinder::FBindingSet(), FText::FromString(RootCategory.ToString() + TEXT('|') + UiSignature.Category.ToString()), MenuGrouping);
|
|
|
|
return MakeShareable(NewMenuItem);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
TSharedPtr<FBlueprintDragDropMenuItem> FBlueprintActionMenuItemFactory::MakeDragDropMenuItem(UBlueprintNodeSpawner const* SampleAction)
|
|
{
|
|
// FBlueprintDragDropMenuItem takes care of its own menu MenuDescription, etc.
|
|
FProperty const* SampleProperty = nullptr;
|
|
if (UBlueprintDelegateNodeSpawner const* DelegateSpawner = Cast<UBlueprintDelegateNodeSpawner const>(SampleAction))
|
|
{
|
|
SampleProperty = DelegateSpawner->GetDelegateProperty();
|
|
}
|
|
else if (UBlueprintVariableNodeSpawner const* VariableSpawner = Cast<UBlueprintVariableNodeSpawner const>(SampleAction))
|
|
{
|
|
SampleProperty = VariableSpawner->GetVarProperty();
|
|
}
|
|
|
|
FText MenuDescription;
|
|
FText TooltipDescription;
|
|
FText Category;
|
|
if (SampleProperty != nullptr)
|
|
{
|
|
bool const bShowFriendlyNames = GetDefault<UEditorStyleSettings>()->bShowFriendlyNames;
|
|
MenuDescription = bShowFriendlyNames ? FText::FromString(UEditorEngine::GetFriendlyName(SampleProperty)) : FText::FromName(SampleProperty->GetFName());
|
|
|
|
TooltipDescription = SampleProperty->GetToolTipText();
|
|
Category = FObjectEditorUtils::GetCategoryText(SampleProperty);
|
|
|
|
bool const bIsDelegateProperty = SampleProperty->IsA<FMulticastDelegateProperty>();
|
|
if (bIsDelegateProperty && Category.IsEmpty())
|
|
{
|
|
Category = FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Delegates);
|
|
}
|
|
else if (!bIsDelegateProperty)
|
|
{
|
|
check(Context.Blueprints.Num() > 0);
|
|
UBlueprint const* Blueprint = Context.Blueprints[0];
|
|
UClass const* BlueprintClass = (Blueprint->SkeletonGeneratedClass != nullptr) ? Blueprint->SkeletonGeneratedClass : Blueprint->ParentClass;
|
|
|
|
UClass const* PropertyClass = SampleProperty->GetOwnerClass();
|
|
checkSlow(PropertyClass != nullptr);
|
|
bool const bIsMemberProperty = BlueprintClass && BlueprintClass->IsChildOf(PropertyClass);
|
|
|
|
FText TextCategory;
|
|
if (Category.IsEmpty())
|
|
{
|
|
TextCategory = FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Variables);
|
|
}
|
|
else if (bIsMemberProperty)
|
|
{
|
|
TextCategory = FEditorCategoryUtils::BuildCategoryString(FCommonEditorCategory::Variables, Category);
|
|
}
|
|
|
|
if (!bIsMemberProperty)
|
|
{
|
|
TextCategory = FText::Format(LOCTEXT("NonMemberVarCategory", "Class|{0}|{1}"), PropertyClass->GetDisplayNameText(), TextCategory);
|
|
}
|
|
Category = TextCategory;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogBlueprintActionMenuItemFactory, Warning, TEXT("Unhandled (or invalid) spawner: '%s'"), *SampleAction->GetName());
|
|
}
|
|
|
|
Category = FText::FromString(RootCategory.ToString() + TEXT('|') + Category.ToString());
|
|
|
|
FBlueprintDragDropMenuItem* NewMenuItem = new FBlueprintDragDropMenuItem(Context, SampleAction, MenuGrouping, Category, MenuDescription, TooltipDescription);
|
|
return MakeShareable(NewMenuItem);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
TSharedPtr<FBlueprintActionMenuItem> FBlueprintActionMenuItemFactory::MakeBoundMenuItem(FBlueprintActionInfo const& ActionInfo)
|
|
{
|
|
FBlueprintActionUiSpec UiSignature = GetActionUiSignature(ActionInfo);
|
|
|
|
UBlueprintNodeSpawner const* Action = ActionInfo.NodeSpawner;
|
|
FBlueprintActionMenuItem* NewMenuItem = new FBlueprintActionMenuItem(Action, UiSignature, ActionInfo.GetBindings(), FText::FromString(RootCategory.ToString() + TEXT('|') + UiSignature.Category.ToString()), MenuGrouping);
|
|
|
|
return MakeShareable(NewMenuItem);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
UBlueprint* FBlueprintActionMenuItemFactory::GetTargetBlueprint() const
|
|
{
|
|
UBlueprint* TargetBlueprint = nullptr;
|
|
if (Context.Blueprints.Num() > 0)
|
|
{
|
|
TargetBlueprint = Context.Blueprints[0];
|
|
}
|
|
return TargetBlueprint;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
UEdGraph* FBlueprintActionMenuItemFactory::GetTargetGraph() const
|
|
{
|
|
UEdGraph* TargetGraph = nullptr;
|
|
if (Context.Graphs.Num() > 0)
|
|
{
|
|
TargetGraph = Context.Graphs[0];
|
|
}
|
|
else
|
|
{
|
|
UBlueprint* Blueprint = GetTargetBlueprint();
|
|
check(Blueprint != nullptr);
|
|
|
|
if (Blueprint->UbergraphPages.Num() > 0)
|
|
{
|
|
TargetGraph = Blueprint->UbergraphPages[0];
|
|
}
|
|
else if (Context.EditorPtr.IsValid())
|
|
{
|
|
TargetGraph = Context.EditorPtr.Pin()->GetFocusedGraph();
|
|
}
|
|
}
|
|
return TargetGraph;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
FBlueprintActionUiSpec FBlueprintActionMenuItemFactory::GetActionUiSignature(FBlueprintActionInfo const& ActionInfo)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionMenuItemFactory::GetActionUiSignature);
|
|
|
|
UBlueprintNodeSpawner const* Action = ActionInfo.NodeSpawner;
|
|
|
|
UEdGraph* TargetGraph = GetTargetGraph();
|
|
Action->PrimeDefaultUiSpec(TargetGraph);
|
|
|
|
return Action->GetUiSpec(Context, ActionInfo.GetBindings());
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Static FBlueprintActionMenuBuilder Helpers
|
|
******************************************************************************/
|
|
|
|
namespace FBlueprintActionMenuBuilderImpl
|
|
{
|
|
/** Defines a sub-section of the overall blueprint menu (filter, heading, etc.) */
|
|
struct FMenuSectionDefinition
|
|
{
|
|
public:
|
|
FMenuSectionDefinition(FBlueprintActionFilter const& SectionFilter, uint32 const Flags);
|
|
|
|
/** Series of ESectionFlags, aimed at customizing how we construct this menu section */
|
|
uint32 Flags;
|
|
/** A filter for this section of the menu */
|
|
FBlueprintActionFilter Filter;
|
|
|
|
/** Sets the root category for menu items in this section. */
|
|
void SetSectionHeading(FText const& RootCategory);
|
|
/** Gets the root category for menu items in this section. */
|
|
FText const& GetSectionHeading() const;
|
|
|
|
/** Sets the grouping for menu items belonging to this section. */
|
|
void SetSectionSortOrder(int32 const MenuGrouping);
|
|
/** Gets the grouping for menu items belonging to this section. */
|
|
int32 GetSectionSortOrder() const;
|
|
|
|
/**
|
|
* Filters the supplied action and if it passes, spawns a new
|
|
* FBlueprintActionMenuItem for the specified menu (does not add the
|
|
* item to the menu-builder itself).
|
|
*
|
|
* @param DatabaseAction The node-spawner that the new menu item should wrap.
|
|
* @return An empty TSharedPtr if the action was filtered out, otherwise a newly allocated FBlueprintActionMenuItem.
|
|
*/
|
|
TArray< TSharedPtr<FEdGraphSchemaAction> > MakeMenuItems(FBlueprintActionInfo& DatabaseAction);
|
|
|
|
/**
|
|
*
|
|
*
|
|
* @param DatabaseAction
|
|
* @param Bindings
|
|
* @return
|
|
*/
|
|
void AddBoundMenuItems(FBlueprintActionInfo& DatabaseAction, TArray<FFieldVariant> const& Bindings, TArray< TSharedPtr<FEdGraphSchemaAction> >& MenuItemsOut);
|
|
|
|
/**
|
|
* Clears out any consolidated properties that this may have been
|
|
* tracking (so we can start a new and spawn new consolidated menu items).
|
|
*/
|
|
void Empty();
|
|
|
|
private:
|
|
/** In charge of spawning menu items for this section (holds category/ordering information)*/
|
|
FBlueprintActionMenuItemFactory ItemFactory;
|
|
/** Tracks the properties that we've already consolidated and passed (when using the ConsolidatePropertyActions flag)*/
|
|
TMap<FProperty const*, TSharedPtr<FBlueprintDragDropMenuItem>> ConsolidatedProperties;
|
|
};
|
|
|
|
/** A utility for building the menu item list based on a set of action descriptors */
|
|
struct FMenuItemListAddHelper
|
|
{
|
|
/** Reset for a new menu build */
|
|
void Reset(int32 NewSize)
|
|
{
|
|
NextIndex = 0;
|
|
PendingActionList.Reset(NewSize);
|
|
}
|
|
|
|
/** Add a new pending action */
|
|
void AddPendingAction(FBlueprintActionInfo&& Action)
|
|
{
|
|
PendingActionList.Add(Forward<FBlueprintActionInfo>(Action));
|
|
}
|
|
|
|
/** @return the next pending action and advance */
|
|
FBlueprintActionInfo* GetNextAction()
|
|
{
|
|
return PendingActionList.IsValidIndex(NextIndex) ? &PendingActionList[NextIndex++] : nullptr;
|
|
}
|
|
|
|
/** @return the allocated size of the pending action list */
|
|
SIZE_T GetAllocatedSize() const
|
|
{
|
|
return PendingActionList.GetAllocatedSize();
|
|
}
|
|
|
|
/** @return the total number of actions that are still pending */
|
|
int32 GetNumPendingActions() const
|
|
{
|
|
return PendingActionList.IsValidIndex(NextIndex) ? PendingActionList.Num() - NextIndex : 0;
|
|
}
|
|
|
|
/** @return the total number of actions that were added to the pending list */
|
|
int32 GetNumTotalAddedActions() const
|
|
{
|
|
return PendingActionList.Num();
|
|
}
|
|
|
|
private:
|
|
/** Keeps track of the next action list item to process */
|
|
int32 NextIndex = 0;
|
|
|
|
/** All actions pending menu items for the current context */
|
|
TArray<FBlueprintActionInfo> PendingActionList;
|
|
};
|
|
|
|
#if ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
static TAutoConsoleVariable<bool> CVarBPEnableActionMenuDumpToFile(
|
|
TEXT("BP.EnableActionMenuDumpToFile"),
|
|
false,
|
|
TEXT("If enabled, action menu contents will be dumped to a file after building.")
|
|
);
|
|
#endif // ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
FBlueprintActionMenuBuilderImpl::FMenuSectionDefinition::FMenuSectionDefinition(FBlueprintActionFilter const& SectionFilterIn, uint32 const FlagsIn)
|
|
: Flags(FlagsIn)
|
|
, Filter(SectionFilterIn)
|
|
, ItemFactory(Filter.Context)
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FBlueprintActionMenuBuilderImpl::FMenuSectionDefinition::SetSectionHeading(FText const& RootCategory)
|
|
{
|
|
ItemFactory.RootCategory = RootCategory;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
FText const& FBlueprintActionMenuBuilderImpl::FMenuSectionDefinition::GetSectionHeading() const
|
|
{
|
|
return ItemFactory.RootCategory;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FBlueprintActionMenuBuilderImpl::FMenuSectionDefinition::SetSectionSortOrder(int32 const MenuGrouping)
|
|
{
|
|
ItemFactory.MenuGrouping = MenuGrouping;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
int32 FBlueprintActionMenuBuilderImpl::FMenuSectionDefinition::GetSectionSortOrder() const
|
|
{
|
|
return ItemFactory.MenuGrouping;
|
|
}
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
void FBlueprintActionMenuBuilderImpl::FMenuSectionDefinition::AddBoundMenuItems(FBlueprintActionInfo& DatabaseActionInfo, TArray<FFieldVariant> const& PerspectiveBindings, TArray< TSharedPtr<FEdGraphSchemaAction> >& MenuItemsOut)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMenuSectionDefinition::AddBoundMenuItems);
|
|
|
|
UBlueprintNodeSpawner const* DatabaseAction = DatabaseActionInfo.NodeSpawner;
|
|
|
|
TSharedPtr<FBlueprintActionMenuItem> LastMadeMenuItem;
|
|
bool const bConsolidate = (Flags & FBlueprintActionMenuBuilder::ConsolidateBoundActions) != 0;
|
|
|
|
IBlueprintNodeBinder::FBindingSet CompatibleBindings;
|
|
// we don't want the blueprint database growing out of control with an entry
|
|
// for every object you could ever possibly bind to, so each
|
|
// UBlueprintNodeSpawner comes with an interface to test/bind through...
|
|
for (auto BindingIt = PerspectiveBindings.CreateConstIterator(); BindingIt;)
|
|
{
|
|
const FFieldVariant& BindingObj = *BindingIt;
|
|
++BindingIt;
|
|
bool const bIsLastBinding = !BindingIt;
|
|
|
|
// check to see if this object can be bound to this action
|
|
if (DatabaseAction->IsBindingCompatible(BindingObj))
|
|
{
|
|
// add bindings before filtering (in case tests accept/reject based off of this)
|
|
CompatibleBindings.Add(BindingObj);
|
|
}
|
|
|
|
// if BoundAction is now "full" (meaning it can take any more
|
|
// bindings), or if this is the last binding to test...
|
|
if ((CompatibleBindings.Num() > 0) && (!DatabaseAction->CanBindMultipleObjects() || bIsLastBinding || !bConsolidate))
|
|
{
|
|
// we don't want binding to mutate DatabaseActionInfo, so we clone
|
|
// the action info, and tack on some binding data
|
|
FBlueprintActionInfo BoundActionInfo(DatabaseActionInfo, CompatibleBindings);
|
|
|
|
// have to check IsFiltered() for every "fully bound" action (in
|
|
// case there are tests that reject based off of this), we may
|
|
// test this multiple times per action (we have to make sure
|
|
// that every set of bound objects pass before folding them into
|
|
// MenuItem)
|
|
bool const bPassedFilter = !Filter.IsFiltered(BoundActionInfo);
|
|
if (bPassedFilter)
|
|
{
|
|
if (!bConsolidate || !LastMadeMenuItem.IsValid())
|
|
{
|
|
LastMadeMenuItem = ItemFactory.MakeBoundMenuItem(BoundActionInfo);
|
|
MenuItemsOut.Add(LastMadeMenuItem);
|
|
|
|
if (Flags & FBlueprintActionMenuBuilder::FlattenCategoryHierarcy)
|
|
{
|
|
LastMadeMenuItem->CosmeticUpdateCategory( ItemFactory.RootCategory );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// move these bindings over to the menu item (so we can
|
|
// test the next set)
|
|
LastMadeMenuItem->AppendBindings(Filter.Context, CompatibleBindings);
|
|
}
|
|
}
|
|
CompatibleBindings.Empty(); // do before we copy back over cached fields for DatabaseActionInfo
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
TArray< TSharedPtr<FEdGraphSchemaAction> > FBlueprintActionMenuBuilderImpl::FMenuSectionDefinition::MakeMenuItems(FBlueprintActionInfo& DatabaseAction)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMenuSectionDefinition::MakeMenuItems);
|
|
|
|
TSharedPtr<FEdGraphSchemaAction> UnBoundMenuEntry;
|
|
bool bPassedFilter = !Filter.IsFiltered(DatabaseAction);
|
|
|
|
// if the caller wants to consolidate all property actions, then we have to
|
|
// check and see if this is one of those that needs consolidating (needs
|
|
// a FBlueprintDragDropMenuItem instead of a FBlueprintActionMenuItem)
|
|
if (bPassedFilter && (Flags & FBlueprintActionMenuBuilder::ConsolidatePropertyActions))
|
|
{
|
|
FProperty const* ActionProperty = nullptr;
|
|
if (UBlueprintVariableNodeSpawner const* VariableSpawner = Cast<UBlueprintVariableNodeSpawner>(DatabaseAction.NodeSpawner))
|
|
{
|
|
ActionProperty = VariableSpawner->GetVarProperty();
|
|
bPassedFilter = (ActionProperty != nullptr);
|
|
}
|
|
else if (UBlueprintDelegateNodeSpawner const* DelegateSpawner = Cast<UBlueprintDelegateNodeSpawner>(DatabaseAction.NodeSpawner))
|
|
{
|
|
ActionProperty = DelegateSpawner->GetDelegateProperty();
|
|
bPassedFilter = (ActionProperty != nullptr);
|
|
}
|
|
|
|
if (ActionProperty != nullptr)
|
|
{
|
|
if (TSharedPtr<FBlueprintDragDropMenuItem>* ConsolidatedMenuItem = ConsolidatedProperties.Find(ActionProperty))
|
|
{
|
|
(*ConsolidatedMenuItem)->AppendAction(DatabaseAction.NodeSpawner);
|
|
// this menu entry has already been returned, don't need to
|
|
// create/insert a new one
|
|
bPassedFilter = false;
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FBlueprintDragDropMenuItem> NewMenuItem = ItemFactory.MakeDragDropMenuItem(DatabaseAction.NodeSpawner);
|
|
ConsolidatedProperties.Add(ActionProperty, NewMenuItem);
|
|
UnBoundMenuEntry = NewMenuItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!UnBoundMenuEntry.IsValid() && bPassedFilter)
|
|
{
|
|
UnBoundMenuEntry = ItemFactory.MakeActionMenuItem(DatabaseAction);
|
|
if (Flags & FBlueprintActionMenuBuilder::FlattenCategoryHierarcy)
|
|
{
|
|
UnBoundMenuEntry->CosmeticUpdateCategory( ItemFactory.RootCategory );
|
|
}
|
|
}
|
|
|
|
TArray< TSharedPtr<FEdGraphSchemaAction> > MenuItems;
|
|
if (UnBoundMenuEntry.IsValid())
|
|
{
|
|
MenuItems.Add(UnBoundMenuEntry);
|
|
}
|
|
AddBoundMenuItems(DatabaseAction, Filter.Context.SelectedObjects, MenuItems);
|
|
|
|
return MenuItems;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FBlueprintActionMenuBuilderImpl::FMenuSectionDefinition::Empty()
|
|
{
|
|
ConsolidatedProperties.Empty();
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* FBlueprintActionMenuBuilder
|
|
******************************************************************************/
|
|
|
|
//------------------------------------------------------------------------------
|
|
FBlueprintActionMenuBuilder::FBlueprintActionMenuBuilder(EConfigFlags ConfigFlags)
|
|
{
|
|
bUsePendingActionList = !!(ConfigFlags & EConfigFlags::UseTimeSlicing);
|
|
MenuItemListAddHelper = MakeShared<FBlueprintActionMenuBuilderImpl::FMenuItemListAddHelper>();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// @todo_deprecated - Remove this definition along w/ its declaration in a future release.
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
FBlueprintActionMenuBuilder::FBlueprintActionMenuBuilder(TWeakPtr<FBlueprintEditor> InBlueprintEditorPtr)
|
|
{
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FBlueprintActionMenuBuilder::Empty()
|
|
{
|
|
FGraphActionListBuilderBase::Empty();
|
|
MenuSections.Empty();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FBlueprintActionMenuBuilder::AddMenuSection(FBlueprintActionFilter const& Filter, FText const& Heading/* = FText::GetEmpty()*/, int32 MenuOrder/* = 0*/, uint32 const Flags/* = 0*/)
|
|
{
|
|
using namespace FBlueprintActionMenuBuilderImpl;
|
|
|
|
TSharedRef<FMenuSectionDefinition> SectionDescRef = MakeShareable(new FMenuSectionDefinition(Filter, Flags));
|
|
SectionDescRef->SetSectionHeading(Heading);
|
|
SectionDescRef->SetSectionSortOrder(MenuOrder);
|
|
|
|
MenuSections.Add(SectionDescRef);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FBlueprintActionMenuBuilder::RebuildActionList()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionMenuBuilder::RebuildActionList);
|
|
|
|
using namespace FBlueprintActionMenuBuilderImpl;
|
|
|
|
FGraphActionListBuilderBase::Empty();
|
|
for (TSharedRef<FMenuSectionDefinition> MenuSection : MenuSections)
|
|
{
|
|
// clear out intermediate actions that may have been spawned (like
|
|
// consolidated property actions).
|
|
MenuSection->Empty();
|
|
}
|
|
|
|
FBlueprintActionDatabase& ActionDatabase = FBlueprintActionDatabase::Get();
|
|
FBlueprintActionDatabase::FActionRegistry const& ActionRegistry = ActionDatabase.GetAllActions();
|
|
|
|
#if ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
int32 TotalActionCount = 0;
|
|
#endif // ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
|
|
if (bUsePendingActionList)
|
|
{
|
|
MenuItemListAddHelper->Reset(ActionRegistry.Num());
|
|
}
|
|
|
|
for (auto Iterator(ActionRegistry.CreateConstIterator()); Iterator; ++Iterator)
|
|
{
|
|
const FObjectKey& ObjKey = Iterator->Key;
|
|
const FBlueprintActionDatabase::FActionList& ActionList = Iterator->Value;
|
|
|
|
if (UObject* ActionObject = ObjKey.ResolveObjectPtr())
|
|
{
|
|
for (UBlueprintNodeSpawner const* NodeSpawner : ActionList)
|
|
{
|
|
FBlueprintActionInfo BlueprintAction(ActionObject, NodeSpawner);
|
|
|
|
#if ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
++TotalActionCount;
|
|
#endif // ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
|
|
if (bUsePendingActionList)
|
|
{
|
|
MenuItemListAddHelper->AddPendingAction(MoveTemp(BlueprintAction));
|
|
}
|
|
else
|
|
{
|
|
MakeMenuItems(BlueprintAction);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove this (invalid) entry on the next tick.
|
|
ActionDatabase.DeferredRemoveEntry(ObjKey);
|
|
}
|
|
}
|
|
|
|
#if ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
if (FBlueprintActionFilter::IsFilterTestStatsLoggingEnabled())
|
|
{
|
|
UE_LOG(LogBlueprintActionMenuItemFactory, Log, TEXT("=== UNFILTERED ACTIONS: %d"), TotalActionCount);
|
|
if (MenuItemListAddHelper->GetNumPendingActions() > 0)
|
|
{
|
|
UE_LOG(LogBlueprintActionMenuItemFactory, Log, TEXT("== PENDING ACTION LIST MEMSIZE: %0.02f MB"), MenuItemListAddHelper->GetAllocatedSize() / 1024.0f / 1024.0f);
|
|
}
|
|
|
|
// Dump detailed stats information about each filter test that was involved with building each menu section.
|
|
for (int32 SectionIdx = 0; SectionIdx < MenuSections.Num(); ++SectionIdx)
|
|
{
|
|
const TSharedRef<FMenuSectionDefinition>& MenuSection = MenuSections[SectionIdx];
|
|
FString DisplayName = FString::Printf(TEXT("MenuSection[%d:%d]"), SectionIdx, MenuSection->GetSectionSortOrder());
|
|
const FText& SectionHeading = MenuSection->GetSectionHeading();
|
|
if (!SectionHeading.IsEmptyOrWhitespace())
|
|
{
|
|
DisplayName.Appendf(TEXT(" (%s)"), *SectionHeading.ToString());
|
|
}
|
|
|
|
UE_LOG(LogBlueprintActionMenuItemFactory, Log, TEXT("=== FILTER TEST PROFILE: %s ==="), *DisplayName);
|
|
|
|
for (const FString& ProfileEntry : MenuSection->Filter.GetFilterTestProfile())
|
|
{
|
|
UE_LOG(LogBlueprintActionMenuItemFactory, Log, TEXT("%s"), *ProfileEntry);
|
|
}
|
|
}
|
|
}
|
|
#endif // ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
|
|
if (MenuItemListAddHelper->GetNumPendingActions() == 0)
|
|
{
|
|
BuildCompleted();
|
|
}
|
|
}
|
|
|
|
void FBlueprintActionMenuBuilder::MakeMenuItems(FBlueprintActionInfo& InAction)
|
|
{
|
|
using namespace FBlueprintActionMenuBuilderImpl;
|
|
|
|
for (TSharedRef<FMenuSectionDefinition> const& MenuSection : MenuSections)
|
|
{
|
|
for (TSharedPtr<FEdGraphSchemaAction> MenuEntry : MenuSection->MakeMenuItems(InAction))
|
|
{
|
|
AddAction(MenuEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 FBlueprintActionMenuBuilder::GetNumPendingActions() const
|
|
{
|
|
return MenuItemListAddHelper->GetNumPendingActions();
|
|
}
|
|
|
|
float FBlueprintActionMenuBuilder::GetPendingActionsProgress() const
|
|
{
|
|
const float NumPendingActions = static_cast<float>(MenuItemListAddHelper->GetNumPendingActions());
|
|
const float NumTotalAddedActions = static_cast<float>(MenuItemListAddHelper->GetNumTotalAddedActions());
|
|
|
|
check(NumTotalAddedActions > 0.0f);
|
|
return 1.0f - (NumPendingActions / NumTotalAddedActions);
|
|
}
|
|
|
|
bool FBlueprintActionMenuBuilder::ProcessPendingActions()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionMenuBuilder::ProcessPendingActions);
|
|
|
|
using namespace FBlueprintActionMenuBuilderImpl;
|
|
|
|
bool bProcessedActions = false;
|
|
const double StartTime = FPlatformTime::Seconds();
|
|
const float MaxTimeThresholdSeconds = GetDefault<UBlueprintEditorSettings>()->ContextMenuTimeSlicingThresholdMs / 1000.0f;
|
|
|
|
FBlueprintActionInfo* CurrentAction = MenuItemListAddHelper->GetNextAction();
|
|
while (CurrentAction)
|
|
{
|
|
bProcessedActions = true;
|
|
|
|
MakeMenuItems(*CurrentAction);
|
|
|
|
if ((FPlatformTime::Seconds() - StartTime) < MaxTimeThresholdSeconds)
|
|
{
|
|
CurrentAction = MenuItemListAddHelper->GetNextAction();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bProcessedActions && !CurrentAction)
|
|
{
|
|
BuildCompleted();
|
|
}
|
|
|
|
return bProcessedActions;
|
|
}
|
|
|
|
void FBlueprintActionMenuBuilder::BuildCompleted()
|
|
{
|
|
#if ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
using namespace FBlueprintActionMenuBuilderImpl;
|
|
|
|
if (CVarBPEnableActionMenuDumpToFile.GetValueOnGameThread())
|
|
{
|
|
TStringBuilder<512> CurrentLine;
|
|
TStringBuilder<256> CategoryPrefix;
|
|
|
|
FString DumpFilePath = FPaths::ProfilingDir() / TEXT("ActionMenu") / FString::Printf(TEXT("MenuDump_%s.txt"), *FDateTime::Now().ToString());
|
|
FOutputDeviceFile DumpFile(*DumpFilePath, true);
|
|
|
|
DumpFile.SetSuppressEventTag(true);
|
|
|
|
UE_LOG(LogBlueprintActionMenuItemFactory, Log, TEXT("Dumping menu builder action list to \"%s\"..."), *DumpFilePath);
|
|
|
|
int32 TotalCount = 0;
|
|
|
|
for (int32 i = 0; i < GetNumActions(); ++i)
|
|
{
|
|
const ActionGroup& CurrentEntry = GetAction(i);
|
|
|
|
CategoryPrefix.Reset();
|
|
for (const FString& Category : CurrentEntry.GetCategoryChain())
|
|
{
|
|
CategoryPrefix.Append(FString::Printf(TEXT("%s|"), *Category));
|
|
}
|
|
|
|
for (const TSharedPtr<FEdGraphSchemaAction>& Action : CurrentEntry.Actions)
|
|
{
|
|
CurrentLine = FString::Printf(TEXT("%05d "), ++TotalCount);
|
|
CurrentLine.Append(CategoryPrefix.ToString());
|
|
CurrentLine.Append(Action->GetMenuDescription().ToString());
|
|
|
|
DumpFile.Log(CurrentLine.ToString());
|
|
}
|
|
}
|
|
|
|
CVarBPEnableActionMenuDumpToFile.AsVariable()->Set(false);
|
|
}
|
|
#endif // ENABLE_BLUEPRINT_ACTION_FILTER_PROFILING
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|