// 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(Asset)) { return Class; } if (UBlueprint* Blueprint = Cast(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("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("LevelEditor")) { LevelEditor->OnMapChanged().RemoveAll(this); } FKismetEditorUtilities::OnBlueprintGeneratedClassUnloaded.RemoveAll(this); } void UEditorUtilitySubsystem::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { Super::AddReferencedObjects(InThis, Collector); UEditorUtilitySubsystem* This = static_cast(InThis); for (auto& KVP : This->PendingTasks) { Collector.AddReferencedObjects(KVP.Value); } } void UEditorUtilitySubsystem::MainFrameCreationFinished(TSharedPtr 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 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 InAssets) { for (const FAssetData& Asset : InAssets) { if (const UClass* AssetClass = Asset.GetClass(); AssetClass && (AssetClass->IsChildOf() || AssetClass->IsChildOf())) { if (bool bRunOnStartup = false; Asset.GetTagValue(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()) { // Unregister any menus related to this BPGC TArray MenuInstanceObjects; GetObjectsOfClass(BPGC, MenuInstanceObjects, /*bIncludeDerivedClasses*/false, RF_NewerVersionExists, EInternalObjectFlags::Garbage); for (UObject* MenuInstanceObject : MenuInstanceObjects) { UEditorUtilityToolMenuEntry* MenuInstance = CastChecked(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(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(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("Blutility"); FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); if (TSharedPtr 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(TEXT("LevelEditor")); if (TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager()) { if (TSharedPtr 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(); if (EditorUtilityWidget && !EditorUtilityWidget->GetTabDisplayName().IsEmpty()) { DisplayName = EditorUtilityWidget->GetTabDisplayName(); } else { DisplayName = FText::FromString(FName::NameToDisplayString(InGeneratedWidgetBlueprint->GetName(), false)); } FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); if (TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager()) { if (!LevelEditorTabManager->HasTabSpawner(RegistrationName)) { IBlutilityModule* BlutilityModule = FModuleManager::GetModulePtr("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(TEXT("LevelEditor")); if (TSharedPtr 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 NewDockTab = LevelEditorTabManager->TryInvokeTab(NewTabID)) { NewDockTab->SetEnabled(FSlateApplication::Get().GetNormalExecutionAttribute()); IBlutilityModule* BlutilityModule = FModuleManager::GetModulePtr("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(TEXT("LevelEditor")); if (TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager()) { TSharedPtr FoundTab = LevelEditorTabManager->FindExistingLiveTab(NewTabID); if (FoundTab) { return true; } } if (TSharedPtr FoundNomadTab = FGlobalTabmanager::Get()->FindExistingLiveTab(NewTabID)) { return true; } } return false; } bool UEditorUtilitySubsystem::CloseTabByID(FName NewTabID) { if (!IsRunningCommandlet()) { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); if (TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager()) { TSharedPtr FoundTab = LevelEditorTabManager->FindExistingLiveTab(NewTabID); if (FoundTab) { FoundTab->RequestCloseTab(); return true; } } if (TSharedPtr FoundNomadTab = FGlobalTabmanager::Get()->FindExistingLiveTab(NewTabID)) { FoundNomadTab->RequestCloseTab(); return true; } } return false; } bool UEditorUtilitySubsystem::UnregisterTabByID(FName TabID) { if (!IsRunningCommandlet()) { const FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); const TSharedPtr LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager(); TSharedPtr 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>* 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& Params, UWorld* InWorld, FOutputDevice& Ar) { if (Params.Num() >= 1) { FString TaskName = Params[0]; if (UClass* FoundClass = FindClassByName(TaskName)) { if (!FoundClass->IsChildOf()) { 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(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 ")); } } void UEditorUtilitySubsystem::CancelAllTasksCommand(const TArray& 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>& 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(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 UEditorUtilitySubsystem::SpawnEditorUITabFromGeneratedClass(const FSpawnTabArgs& SpawnTabArgs, UWidgetBlueprintGeneratedClass* InGeneratedWidgetBlueprint) { TSharedRef SpawnedTab = SNew(SDockTab) .IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute()); UEditorUtilityWidget* CreatedUMGWidget = nullptr; auto CreateUtilityWidgetFromGeneratedClass = [&CreatedUMGWidget](UWidgetBlueprintGeneratedClass* InGeneratedWidgetBlueprint) { TSharedRef TabWidget = SNullWidget::NullWidget; UClass* BlueprintClass = InGeneratedWidgetBlueprint; TSubclassOf WidgetClass = BlueprintClass; if (UWorld* World = GEditor->GetEditorWorldContext().World()) { CreatedUMGWidget = CreateWidget(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 TabWidget = CreateUtilityWidgetFromGeneratedClass(InGeneratedWidgetBlueprint); SpawnedTab->SetContent(TabWidget); SpawnedTab->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateUObject(this, &UEditorUtilitySubsystem::OnSpawnedFromGeneratedClassTabClosed)); SpawnedFromGeneratedClassTabs.Add(SpawnedTab, CreatedUMGWidget); return SpawnedTab; } void UEditorUtilitySubsystem::OnSpawnedFromGeneratedClassTabClosed(TSharedRef 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, UEditorUtilityWidget*> SpawnedFromGeneratedClassTab : SpawnedFromGeneratedClassTabs) { TSharedRef 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