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

926 lines
29 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EditorUtilitySubsystem.h"
#include "AssetRegistry/ARFilter.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "CoreGlobals.h"
#include "Editor.h"
#include "EditorUtilityCommon.h"
#include "EditorUtilityTask.h"
#include "EditorUtilityToolMenu.h"
#include "EditorUtilityWidgetBlueprint.h"
#include "Engine/Blueprint.h"
#include "Engine/BlueprintCore.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Docking/TabManager.h"
#include "GameFramework/Actor.h"
#include "HAL/IConsoleManager.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "IBlutilityModule.h"
#include "Interfaces/IMainFrameModule.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "LevelEditor.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Misc/AssertionMacros.h"
#include "Misc/CString.h"
#include "Misc/PackageName.h"
#include "Modules/ModuleManager.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "Templates/Tuple.h"
#include "Trace/Detail/Channel.h"
#include "UObject/Class.h"
#include "UObject/Script.h"
#include "UObject/SoftObjectPath.h"
#include "UObject/TopLevelAssetPath.h"
#include "UObject/UObjectBaseUtility.h"
#include "Widgets/Docking/SDockTab.h"
class FOutputDevice;
class FSubsystemCollectionBase;
class UWorld;
#define LOCTEXT_NAMESPACE "EditorUtilitySubsystem"
namespace UE::EditorUtility::Private
{
static const FName RunFunctionName = "Run";
static const FName RunOnStartupTagName = "bRunEditorUtilityOnStartup";
UClass* GetRunnableClassForAsset(UObject* Asset)
{
if (UClass* Class = Cast<UClass>(Asset))
{
return Class;
}
if (UBlueprint* Blueprint = Cast<UBlueprint>(Asset))
{
return Blueprint->GeneratedClass;
}
return Asset->GetClass();
}
}
UEditorUtilitySubsystem::UEditorUtilitySubsystem()
: UEditorSubsystem()
{
}
void UEditorUtilitySubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
if (!IConsoleManager::Get().FindConsoleObject(TEXT("RunTask")))
{
RunTaskCommandObject = IConsoleManager::Get().RegisterConsoleCommand(
TEXT("RunTask"),
TEXT(""),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateUObject(this, &UEditorUtilitySubsystem::RunTaskCommand),
ECVF_Default
);
}
if (!IConsoleManager::Get().FindConsoleObject(TEXT("CancelAllTasks")))
{
CancelAllTasksCommandObject = IConsoleManager::Get().RegisterConsoleCommand(
TEXT("CancelAllTasks"),
TEXT(""),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateUObject(this, &UEditorUtilitySubsystem::CancelAllTasksCommand),
ECVF_Default
);
}
IMainFrameModule& MainFrameModule = IMainFrameModule::Get();
if (MainFrameModule.IsWindowInitialized())
{
HandleStartup();
}
else
{
MainFrameModule.OnMainFrameCreationFinished().AddUObject(this, &UEditorUtilitySubsystem::MainFrameCreationFinished);
}
TickerHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &UEditorUtilitySubsystem::Tick), 0);
FEditorDelegates::BeginPIE.AddUObject(this, &UEditorUtilitySubsystem::HandleOnBeginPIE);
FEditorDelegates::EndPIE.AddUObject(this, &UEditorUtilitySubsystem::HandleOnEndPIE);
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditor.OnMapChanged().AddUObject(this, &UEditorUtilitySubsystem::OnMapChanged);
FKismetEditorUtilities::OnBlueprintGeneratedClassUnloaded.AddUObject(this, &UEditorUtilitySubsystem::OnBlueprintGeneratedClassUnloaded);
}
void UEditorUtilitySubsystem::Deinitialize()
{
if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
{
IMainFrameModule::Get().OnMainFrameCreationFinished().RemoveAll(this);
}
FTSTicker::GetCoreTicker().RemoveTicker(TickerHandle);
if (RunTaskCommandObject)
{
IConsoleManager::Get().UnregisterConsoleObject(RunTaskCommandObject);
}
if (CancelAllTasksCommandObject)
{
IConsoleManager::Get().UnregisterConsoleObject(CancelAllTasksCommandObject);
}
FEditorDelegates::BeginPIE.RemoveAll(this);
FEditorDelegates::EndPIE.RemoveAll(this);
if (FLevelEditorModule* LevelEditor = FModuleManager::GetModulePtr<FLevelEditorModule>("LevelEditor"))
{
LevelEditor->OnMapChanged().RemoveAll(this);
}
FKismetEditorUtilities::OnBlueprintGeneratedClassUnloaded.RemoveAll(this);
}
void UEditorUtilitySubsystem::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
Super::AddReferencedObjects(InThis, Collector);
UEditorUtilitySubsystem* This = static_cast<UEditorUtilitySubsystem*>(InThis);
for (auto& KVP : This->PendingTasks)
{
Collector.AddReferencedObjects(KVP.Value);
}
}
void UEditorUtilitySubsystem::MainFrameCreationFinished(TSharedPtr<SWindow> InRootWindow, bool bIsRunningStartupDialog)
{
HandleStartup();
}
void UEditorUtilitySubsystem::HandleStartup()
{
// Handle Blueprints in the StartupObjects config list
for (const FSoftObjectPath& ObjectPath : StartupObjects)
{
UObject* Object = GetValid(ObjectPath.TryLoad());
if (!Object || Object->IsUnreachable())
{
UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Could not load: %s"), *ObjectPath.ToString());
continue;
}
TryRun(Object);
}
// Handle Blueprints with bRunEditorUtilityOnStartup set to true
if (IAssetRegistry& AssetRegistry = IAssetRegistry::GetChecked();
AssetRegistry.IsLoadingAssets())
{
AssetRegistry.OnFilesLoaded().AddUObject(this, &UEditorUtilitySubsystem::HandleStartupAssets);
}
else
{
HandleStartupAssets();
}
}
void UEditorUtilitySubsystem::HandleStartupAssets()
{
IAssetRegistry& AssetRegistry = IAssetRegistry::GetChecked();
check(!AssetRegistry.IsLoadingAssets());
// Handle the current set of assets
{
FARFilter Filter;
Filter.bRecursiveClasses = true;
Filter.ClassPaths.Add(UBlueprintCore::StaticClass()->GetClassPathName());
Filter.ClassPaths.Add(UBlueprintGeneratedClass::StaticClass()->GetClassPathName());
Filter.TagsAndValues.Emplace(UE::EditorUtility::Private::RunOnStartupTagName, TEXT("True"));
TArray<FAssetData> StartupAssets;
AssetRegistry.GetAssets(Filter, StartupAssets);
for (const FAssetData& StartupAsset : StartupAssets)
{
UObject* Object = StartupAsset.GetAsset();
if (!Object || Object->IsUnreachable())
{
UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Could not load: %s"), *StartupAsset.GetSoftObjectPath().ToString());
continue;
}
TryRun(Object);
}
}
// Watch for new assets being added
AssetRegistry.OnAssetsAdded().AddUObject(this, &UEditorUtilitySubsystem::HandleDynamicStartupAssets);
}
void UEditorUtilitySubsystem::HandleDynamicStartupAssets(TConstArrayView<FAssetData> InAssets)
{
for (const FAssetData& Asset : InAssets)
{
if (const UClass* AssetClass = Asset.GetClass();
AssetClass && (AssetClass->IsChildOf<UBlueprintCore>() || AssetClass->IsChildOf<UBlueprintGeneratedClass>()))
{
if (bool bRunOnStartup = false;
Asset.GetTagValue<bool>(UE::EditorUtility::Private::RunOnStartupTagName, bRunOnStartup) && bRunOnStartup)
{
UObject* Object = Asset.GetAsset();
if (!Object || Object->IsUnreachable())
{
UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Could not load: %s"), *Asset.GetSoftObjectPath().ToString());
continue;
}
TryRun(Object);
}
}
}
}
void UEditorUtilitySubsystem::OnBlueprintGeneratedClassUnloaded(UBlueprintGeneratedClass* BPGC)
{
if (BPGC->IsChildOf<UEditorUtilityToolMenuEntry>())
{
// Unregister any menus related to this BPGC
TArray<UObject*> MenuInstanceObjects;
GetObjectsOfClass(BPGC, MenuInstanceObjects, /*bIncludeDerivedClasses*/false, RF_NewerVersionExists, EInternalObjectFlags::Garbage);
for (UObject* MenuInstanceObject : MenuInstanceObjects)
{
UEditorUtilityToolMenuEntry* MenuInstance = CastChecked<UEditorUtilityToolMenuEntry>(MenuInstanceObject);
MenuInstance->UnregisterMenuEntry();
}
}
ReleaseInstanceOfAsset(BPGC);
if (BPGC->ClassGeneratedBy)
{
ReleaseInstanceOfAsset(BPGC->ClassGeneratedBy);
}
}
bool UEditorUtilitySubsystem::TryRun(UObject* Asset)
{
if (!Asset || !IsValidChecked(Asset) || Asset->IsUnreachable())
{
UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Could not run: %s"), Asset ? *Asset->GetPathName() : TEXT("None"));
return false;
}
UClass* ObjectClass = UE::EditorUtility::Private::GetRunnableClassForAsset(Asset);
if (!ObjectClass)
{
UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Missing class: %s"), *Asset->GetPathName());
return false;
}
if (ObjectClass->IsChildOf(AActor::StaticClass()))
{
UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Could not run because functions on actors can only be called when spawned in a world: %s"), *Asset->GetPathName());
return false;
}
UFunction* RunFunction = ObjectClass->FindFunctionByName(UE::EditorUtility::Private::RunFunctionName);
if (RunFunction && RunFunction->ParmsSize == 0)
{
UObject* Instance = NewObject<UObject>(this, ObjectClass);
ObjectInstances.Add(Asset, Instance);
FEditorScriptExecutionGuard ScriptGuard;
Instance->ProcessEvent(RunFunction, nullptr);
return true;
}
else
{
UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Missing 0 param function named 'Run': %s"), *Asset->GetPathName());
}
return false;
}
bool UEditorUtilitySubsystem::TryRunClass(UClass* ObjectClass)
{
UFunction* RunFunction = ObjectClass->FindFunctionByName(UE::EditorUtility::Private::RunFunctionName);
if (RunFunction && RunFunction->ParmsSize == 0)
{
UObject* Instance = NewObject<UObject>(this, ObjectClass);
ObjectInstances.Add(ObjectClass, Instance);
FEditorScriptExecutionGuard ScriptGuard;
Instance->ProcessEvent(RunFunction, nullptr);
return true;
}
else
{
UE_LOG(LogEditorUtilityBlueprint, Warning, TEXT("Missing 0 param function named 'Run': %s"), *ObjectClass->GetPathName());
}
return false;
}
bool UEditorUtilitySubsystem::CanRun(UObject* Asset) const
{
UClass* ObjectClass = UE::EditorUtility::Private::GetRunnableClassForAsset(Asset);
if (ObjectClass)
{
if (ObjectClass->IsChildOf(AActor::StaticClass()))
{
return false;
}
return true;
}
return false;
}
void UEditorUtilitySubsystem::ReleaseInstanceOfAsset(UObject* Asset)
{
ObjectInstances.Remove(Asset);
}
UEditorUtilityWidget* UEditorUtilitySubsystem::SpawnAndRegisterTabAndGetID(UEditorUtilityWidgetBlueprint* InBlueprint, FName& NewTabID)
{
FName TabID;
RegisterTabAndGetID(InBlueprint, TabID);
SpawnRegisteredTabByID(TabID);
NewTabID = TabID;
return FindUtilityWidgetFromBlueprint(InBlueprint);
}
UEditorUtilityWidget* UEditorUtilitySubsystem::SpawnAndRegisterTab(class UEditorUtilityWidgetBlueprint* InBlueprint)
{
FName InTabID;
return SpawnAndRegisterTabAndGetID(InBlueprint, InTabID);
}
UEditorUtilityWidget* UEditorUtilitySubsystem::SpawnAndRegisterTabWithId(class UEditorUtilityWidgetBlueprint* InBlueprint, FName InTabID)
{
RegisterTabAndGetID(InBlueprint, InTabID);
SpawnRegisteredTabByID(InTabID);
return FindUtilityWidgetFromBlueprint(InBlueprint);
}
void UEditorUtilitySubsystem::RegisterTabAndGetID(class UEditorUtilityWidgetBlueprint* InBlueprint, FName& NewTabID)
{
if (InBlueprint && !IsRunningCommandlet())
{
FName RegistrationName = NewTabID.IsNone() ? FName(*(InBlueprint->GetPathName() + LOCTEXT("ActiveTabSuffix", "_ActiveTab").ToString())) : FName(*(InBlueprint->GetPathName() + NewTabID.ToString()));
FText DisplayName = InBlueprint->GetTabDisplayName();
IBlutilityModule& BlutilityModule = FModuleManager::GetModuleChecked<IBlutilityModule>("Blutility");
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
if (TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager())
{
// Nomad tabs need to have their tab spawner registered with the global tab manager via RegisterNomadTabSpawner
if (InBlueprint->ShouldSpawnAsNomadTab())
{
if (LevelEditorTabManager->HasTabSpawner(RegistrationName))
{
LevelEditorTabManager->UnregisterTabSpawner(RegistrationName);
}
if (!FGlobalTabmanager::Get()->HasTabSpawner(RegistrationName))
{
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(RegistrationName, FOnSpawnTab::CreateUObject(InBlueprint, &UEditorUtilityWidgetBlueprint::SpawnEditorUITab))
.SetDisplayName(DisplayName)
.SetGroup(BlutilityModule.GetMenuGroup().ToSharedRef());
InBlueprint->SetRegistrationName(RegistrationName);
}
}
else
{
if (FGlobalTabmanager::Get()->HasTabSpawner(RegistrationName))
{
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(RegistrationName);
}
if (!LevelEditorTabManager->HasTabSpawner(RegistrationName))
{
LevelEditorTabManager->RegisterTabSpawner(RegistrationName, FOnSpawnTab::CreateUObject(InBlueprint, &UEditorUtilityWidgetBlueprint::SpawnEditorUITab))
.SetDisplayName(DisplayName)
.SetGroup(BlutilityModule.GetMenuGroup().ToSharedRef());
InBlueprint->SetRegistrationName(RegistrationName);
}
}
RegisteredTabs.Add(RegistrationName, InBlueprint);
NewTabID = RegistrationName;
}
}
}
UEditorUtilityWidget* UEditorUtilitySubsystem::SpawnAndRegisterTabAndGetIDGeneratedClass(UWidgetBlueprintGeneratedClass* InGeneratedWidgetBlueprint, FName& NewTabID)
{
FName TabID;
RegisterTabAndGetIDGeneratedClass(InGeneratedWidgetBlueprint, TabID);
SpawnRegisteredTabByID(TabID);
NewTabID = TabID;
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
if (TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager())
{
if (TSharedPtr<SDockTab> FoundTab = LevelEditorTabManager->FindExistingLiveTab(NewTabID))
{
if (UEditorUtilityWidget **CreatedUMGWidget = SpawnedFromGeneratedClassTabs.Find(FoundTab.ToSharedRef()))
{
return *CreatedUMGWidget;
}
}
}
return nullptr;
}
UEditorUtilityWidget* UEditorUtilitySubsystem::SpawnAndRegisterTabGeneratedClass(UWidgetBlueprintGeneratedClass* InGeneratedWidgetBlueprint)
{
FName InTabID;
return SpawnAndRegisterTabAndGetIDGeneratedClass(InGeneratedWidgetBlueprint, InTabID);
}
UEditorUtilityWidget* UEditorUtilitySubsystem::SpawnAndRegisterTabWithIdGeneratedClass(UWidgetBlueprintGeneratedClass* InGeneratedWidgetBlueprint, FName InTabID)
{
return SpawnAndRegisterTabAndGetIDGeneratedClass(InGeneratedWidgetBlueprint, InTabID);
}
void UEditorUtilitySubsystem::RegisterTabAndGetIDGeneratedClass(UWidgetBlueprintGeneratedClass* InGeneratedWidgetBlueprint, FName& NewTabID)
{
if (InGeneratedWidgetBlueprint && !IsRunningCommandlet())
{
FName RegistrationName = NewTabID.IsNone() ? FName(*(InGeneratedWidgetBlueprint->GetPathName() + LOCTEXT("ActiveTabSuffix", "_ActiveTab").ToString())) : FName(*(InGeneratedWidgetBlueprint->GetPathName() + NewTabID.ToString()));
FText DisplayName;
const UEditorUtilityWidget* EditorUtilityWidget = InGeneratedWidgetBlueprint->GetDefaultObject<UEditorUtilityWidget>();
if (EditorUtilityWidget && !EditorUtilityWidget->GetTabDisplayName().IsEmpty())
{
DisplayName = EditorUtilityWidget->GetTabDisplayName();
}
else
{
DisplayName = FText::FromString(FName::NameToDisplayString(InGeneratedWidgetBlueprint->GetName(), false));
}
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
if (TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager())
{
if (!LevelEditorTabManager->HasTabSpawner(RegistrationName))
{
IBlutilityModule* BlutilityModule = FModuleManager::GetModulePtr<IBlutilityModule>("Blutility");
LevelEditorTabManager->RegisterTabSpawner(RegistrationName, FOnSpawnTab::CreateUObject(this, &UEditorUtilitySubsystem::SpawnEditorUITabFromGeneratedClass, InGeneratedWidgetBlueprint))
.SetDisplayName(DisplayName)
.SetGroup(BlutilityModule->GetMenuGroup().ToSharedRef());
}
RegisteredTabsByGeneratedClass.Add(RegistrationName, InGeneratedWidgetBlueprint);
NewTabID = RegistrationName;
}
}
}
bool UEditorUtilitySubsystem::SpawnRegisteredTabByID(FName NewTabID)
{
if (!IsRunningCommandlet())
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
if (TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager())
{
// Invoke the tab via the LevelEditor's TabManager even if the tab spawner is registered globally as a nomad tab.
// TryInvokeTab will invoke in the global tab manager if the tab is not found in the LevelEditor.
if (TSharedPtr<SDockTab> NewDockTab = LevelEditorTabManager->TryInvokeTab(NewTabID))
{
NewDockTab->SetEnabled(FSlateApplication::Get().GetNormalExecutionAttribute());
IBlutilityModule* BlutilityModule = FModuleManager::GetModulePtr<IBlutilityModule>("Blutility");
UEditorUtilityWidgetBlueprint** WidgetToSpawn = RegisteredTabs.Find(NewTabID);
if (WidgetToSpawn)
{
check(*WidgetToSpawn);
BlutilityModule->AddLoadedScriptUI(*WidgetToSpawn);
return true;
}
}
else
{
UE_LOG(LogEditorUtilityBlueprint, Error, TEXT("TryInvokeTab failed with TabId: %s"), *NewTabID.ToString());
}
}
}
return false;
}
bool UEditorUtilitySubsystem::DoesTabExist(FName NewTabID)
{
if (!IsRunningCommandlet())
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
if (TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager())
{
TSharedPtr<SDockTab> FoundTab = LevelEditorTabManager->FindExistingLiveTab(NewTabID);
if (FoundTab)
{
return true;
}
}
if (TSharedPtr<SDockTab> FoundNomadTab = FGlobalTabmanager::Get()->FindExistingLiveTab(NewTabID))
{
return true;
}
}
return false;
}
bool UEditorUtilitySubsystem::CloseTabByID(FName NewTabID)
{
if (!IsRunningCommandlet())
{
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
if (TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager())
{
TSharedPtr<SDockTab> FoundTab = LevelEditorTabManager->FindExistingLiveTab(NewTabID);
if (FoundTab)
{
FoundTab->RequestCloseTab();
return true;
}
}
if (TSharedPtr<SDockTab> FoundNomadTab = FGlobalTabmanager::Get()->FindExistingLiveTab(NewTabID))
{
FoundNomadTab->RequestCloseTab();
return true;
}
}
return false;
}
bool UEditorUtilitySubsystem::UnregisterTabByID(FName TabID)
{
if (!IsRunningCommandlet())
{
const FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
const TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager();
TSharedPtr<SDockTab> FoundTab = LevelEditorTabManager->FindExistingLiveTab(TabID);
if (!FoundTab)
{
FoundTab = FGlobalTabmanager::Get()->FindExistingLiveTab(TabID);
}
if (FoundTab)
{
FoundTab->RequestCloseTab();
}
if (RegisteredTabs.Contains(TabID))
{
RegisteredTabs.Remove(TabID);
if (LevelEditorTabManager->HasTabSpawner(TabID))
{
LevelEditorTabManager->UnregisterTabSpawner(TabID);
}
if (FGlobalTabmanager::Get()->HasTabSpawner(TabID))
{
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(TabID);
}
return true;
}
}
return false;
}
UEditorUtilityWidget* UEditorUtilitySubsystem::FindUtilityWidgetFromBlueprint(class UEditorUtilityWidgetBlueprint* InBlueprint)
{
if (!IsValid(InBlueprint))
{
UE_LOG(LogEditorUtilityBlueprint, Error, TEXT("Found Invalid Blueprint in FindUtilityWidgetFromBlueprint"));
return nullptr;
}
return InBlueprint->GetCreatedWidget();
}
bool UEditorUtilitySubsystem::Tick(float DeltaTime)
{
UEditorUtilityTask* CurrentOrParentTask = GetActiveTask();
TArray<TObjectPtr<UEditorUtilityTask>>* PendingChildTasks = PendingTasks.Find(CurrentOrParentTask);
if (PendingChildTasks && PendingChildTasks->Num())
{
UEditorUtilityTask* PendingChildTask = (*PendingChildTasks)[0];
PendingChildTasks->RemoveAt(0);
StartTask(PendingChildTask);
}
// Canceling happens without an event in the notification it's based on checking it during tick,
// so as we evaluate it, we check if cancel was requested, and if so, we manually trigger
// RequestCancel, to ensure an event is fired letting the task know we want to stop.
if (GetActiveTask() && GetActiveTask()->WasCancelRequested())
{
GetActiveTask()->RequestCancel();
}
return true;
}
void UEditorUtilitySubsystem::StartTask(UEditorUtilityTask* Task)
{
if (!Task)
{
return;
}
ActiveTaskStack.Add(Task);
UE_LOG(LogEditorUtilityBlueprint, Log, TEXT("Running task %s"), *GetPathNameSafe(Task));
// And start executing it
Task->StartExecutingTask();
}
void UEditorUtilitySubsystem::RunTaskCommand(const TArray<FString>& Params, UWorld* InWorld, FOutputDevice& Ar)
{
if (Params.Num() >= 1)
{
FString TaskName = Params[0];
if (UClass* FoundClass = FindClassByName(TaskName))
{
if (!FoundClass->IsChildOf<UEditorUtilityTask>())
{
UE_LOG(LogEditorUtilityBlueprint, Error, TEXT("Found Task: %s, but it's not a subclass of 'EditorUtilityTask'."), *FoundClass->GetName());
return;
}
UE_LOG(LogEditorUtilityBlueprint, Log, TEXT("Running task %s"), *FoundClass->GetPathName());
UEditorUtilityTask* NewTask = NewObject<UEditorUtilityTask>(this, FoundClass);
//TODO Attempt to map XXX=YYY to properties on the task to make the tasks parameterizable
RegisterAndExecuteTask(NewTask);
}
else
{
UE_LOG(LogEditorUtilityBlueprint, Error, TEXT("Unable to find task named %s."), *TaskName);
}
}
else
{
UE_LOG(LogEditorUtilityBlueprint, Error, TEXT("No task specified. RunTask <Name of Task>"));
}
}
void UEditorUtilitySubsystem::CancelAllTasksCommand(const TArray<FString>& Params, UWorld* InWorld, FOutputDevice& Ar)
{
PendingTasks.Reset();
for (UEditorUtilityTask* ActiveTask : ActiveTaskStack)
{
ActiveTask->RequestCancel();
}
}
void UEditorUtilitySubsystem::RegisterAndExecuteTask(UEditorUtilityTask* NewTask, UEditorUtilityTask* OptionalParentTask)
{
if (NewTask != nullptr)
{
// Make sure this task wasn't already registered somehow
ensureAlwaysMsgf(NewTask->MyTaskManager == nullptr, TEXT("RegisterAndExecuteTask(this=%s, task=%s) - Passed in task is already registered to %s"), *GetPathName(), *NewTask->GetPathName(), *GetPathNameSafe(NewTask->MyTaskManager));
if (NewTask->MyTaskManager != nullptr)
{
NewTask->MyTaskManager->RemoveTaskFromActiveList(NewTask);
}
// Register it
check(!(PendingTasks.Contains(NewTask) || ActiveTaskStack.Contains(NewTask)));
for (auto& KVP : PendingTasks)
{
check(!KVP.Value.Contains(NewTask));
}
NewTask->MyTaskManager = this;
NewTask->MyParentTask = OptionalParentTask;
// Always append the task to the set array of tasks associated with the parent - which may be null.
TArray<TObjectPtr<UEditorUtilityTask>>& PendingChildTasks = PendingTasks.FindOrAdd(OptionalParentTask);
PendingChildTasks.Add(NewTask);
}
}
void UEditorUtilitySubsystem::RemoveTaskFromActiveList(UEditorUtilityTask* Task)
{
if (Task != nullptr)
{
if (ensure(Task->MyTaskManager == this))
{
PendingTasks.Remove(Task);
ActiveTaskStack.Remove(Task);
// Remove from any child set.
for (auto& KVP : PendingTasks)
{
KVP.Value.Remove(Task);
}
Task->MyTaskManager = nullptr;
UE_LOG(LogEditorUtilityBlueprint, Log, TEXT("Task %s removed"), *GetPathNameSafe(Task));
}
}
}
void UEditorUtilitySubsystem::RegisterReferencedObject(UObject* ObjectToReference)
{
ReferencedObjects.Add(ObjectToReference);
}
void UEditorUtilitySubsystem::UnregisterReferencedObject(UObject* ObjectToReference)
{
ReferencedObjects.Remove(ObjectToReference);
}
UClass* UEditorUtilitySubsystem::FindClassByName(const FString& RawTargetName)
{
FString TargetName = RawTargetName;
// Check native classes and loaded assets first before resorting to the asset registry
bool bIsValidClassName = true;
if (TargetName.IsEmpty() || TargetName.Contains(TEXT(" ")))
{
bIsValidClassName = false;
}
else if (!FPackageName::IsShortPackageName(TargetName))
{
if (TargetName.Contains(TEXT(".")))
{
// Convert type'path' to just path (will return the full string if it doesn't have ' in it)
TargetName = FPackageName::ExportTextPathToObjectPath(TargetName);
FString PackageName;
FString ObjectName;
TargetName.Split(TEXT("."), &PackageName, &ObjectName);
const bool bIncludeReadOnlyRoots = true;
FText Reason;
if (!FPackageName::IsValidLongPackageName(PackageName, bIncludeReadOnlyRoots, &Reason))
{
bIsValidClassName = false;
}
}
else
{
bIsValidClassName = false;
}
}
UClass* ResultClass = nullptr;
if (bIsValidClassName)
{
ResultClass = UClass::TryFindTypeSlow<UClass>(TargetName);
}
// If we still haven't found anything yet, try the asset registry for blueprints that match the requirements
if (ResultClass == nullptr)
{
ResultClass = FindBlueprintClass(TargetName);
}
return ResultClass;
}
UClass* UEditorUtilitySubsystem::FindBlueprintClass(const FString& TargetName)
{
IAssetRegistry& AssetRegistry = IAssetRegistry::GetChecked();
if (AssetRegistry.IsLoadingAssets())
{
AssetRegistry.SearchAllAssets(true);
}
FARFilter Filter;
Filter.bRecursiveClasses = true;
Filter.ClassPaths.Add(UBlueprintCore::StaticClass()->GetClassPathName());
Filter.ClassPaths.Add(UBlueprintGeneratedClass::StaticClass()->GetClassPathName());
// We enumerate all assets to find any blueprints who inherit from native classes directly - or
// from other blueprints.
UClass* FoundClass = nullptr;
AssetRegistry.EnumerateAssets(Filter, [&FoundClass, TargetName](const FAssetData& AssetData)
{
if ((AssetData.AssetName.ToString() == TargetName) || (AssetData.GetObjectPathString() == TargetName))
{
if (UObject* Asset = AssetData.GetAsset())
{
FoundClass = UE::EditorUtility::Private::GetRunnableClassForAsset(Asset);
return false;
}
}
return true;
}, UE::AssetRegistry::EEnumerateAssetsFlags::AllowUnfilteredArAssets);
return FoundClass;
}
void UEditorUtilitySubsystem::HandleOnBeginPIE(const bool bIsSimulating)
{
OnBeginPIE.Broadcast(bIsSimulating);
}
void UEditorUtilitySubsystem::HandleOnEndPIE(const bool bIsSimulating)
{
OnEndPIE.Broadcast(bIsSimulating);
}
TSharedRef<SDockTab> UEditorUtilitySubsystem::SpawnEditorUITabFromGeneratedClass(const FSpawnTabArgs& SpawnTabArgs, UWidgetBlueprintGeneratedClass* InGeneratedWidgetBlueprint)
{
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute());
UEditorUtilityWidget* CreatedUMGWidget = nullptr;
auto CreateUtilityWidgetFromGeneratedClass = [&CreatedUMGWidget](UWidgetBlueprintGeneratedClass* InGeneratedWidgetBlueprint)
{
TSharedRef<SWidget> TabWidget = SNullWidget::NullWidget;
UClass* BlueprintClass = InGeneratedWidgetBlueprint;
TSubclassOf<UEditorUtilityWidget> WidgetClass = BlueprintClass;
if (UWorld* World = GEditor->GetEditorWorldContext().World())
{
CreatedUMGWidget = CreateWidget<UEditorUtilityWidget>(World, WidgetClass);
if (CreatedUMGWidget)
{
// Editor Utility is flagged as transient to prevent from dirty the World it's created in when a property added to the Utility Widget is changed
// Also need to recursively mark nested utility widgets as transient to prevent them from dirtying the world (since they'll be created via CreateWidget and not CreateUtilityWidget)
UEditorUtilityWidgetBlueprint::MarkTransientRecursive(CreatedUMGWidget);
}
}
if (CreatedUMGWidget)
{
TabWidget = SNew(SVerticalBox)
+ SVerticalBox::Slot()
.HAlign(HAlign_Fill)
[
CreatedUMGWidget->TakeWidget()
];
}
return TabWidget;
};
TSharedRef<SWidget> TabWidget = CreateUtilityWidgetFromGeneratedClass(InGeneratedWidgetBlueprint);
SpawnedTab->SetContent(TabWidget);
SpawnedTab->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateUObject(this, &UEditorUtilitySubsystem::OnSpawnedFromGeneratedClassTabClosed));
SpawnedFromGeneratedClassTabs.Add(SpawnedTab, CreatedUMGWidget);
return SpawnedTab;
}
void UEditorUtilitySubsystem::OnSpawnedFromGeneratedClassTabClosed(TSharedRef<SDockTab> TabBeingClosed)
{
if (UEditorUtilityWidget** Widget = SpawnedFromGeneratedClassTabs.Find(TabBeingClosed))
{
(*Widget)->Rename(nullptr, GetTransientPackage());
}
SpawnedFromGeneratedClassTabs.Remove(TabBeingClosed);
}
void UEditorUtilitySubsystem::OnMapChanged(UWorld* World, EMapChangeType MapChangeType)
{
if (MapChangeType != EMapChangeType::SaveMap)
{
// We need to Delete the UMG widget if we are tearing down the World it was built with.
for (TPair<TSharedRef<SDockTab>, UEditorUtilityWidget*> SpawnedFromGeneratedClassTab : SpawnedFromGeneratedClassTabs)
{
TSharedRef<SDockTab> CreatedTab = SpawnedFromGeneratedClassTab.Key;
UEditorUtilityWidget* CreatedUMGWidget = SpawnedFromGeneratedClassTab.Value;
if (CreatedUMGWidget && World == CreatedUMGWidget->GetWorld())
{
CreatedTab->SetContent(SNullWidget::NullWidget);
CreatedUMGWidget->Rename(nullptr, GetTransientPackage());
}
}
SpawnedFromGeneratedClassTabs.Empty();
}
}
#undef LOCTEXT_NAMESPACE