Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/Commandlets/ExternalActorsCommandlet.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

420 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Commandlets/ExternalActorsCommandlet.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "HAL/FileManager.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Editor.h"
#include "Engine/Level.h"
#include "Engine/World.h"
#include "UObject/SavePackage.h"
#include "UObject/UObjectHash.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "PackageHelperFunctions.h"
#include "WorldPartition/WorldPartition.h"
#include "WorldPartition/DataLayer/DataLayerManager.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(ExternalActorsCommandlet)
DEFINE_LOG_CATEGORY_STATIC(LogExternalActorsCommandlet, Log, All);
UExternalActorsCommandlet::UExternalActorsCommandlet(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
UWorld* UExternalActorsCommandlet::LoadWorld(const FString& LevelToLoad)
{
SET_WARN_COLOR(COLOR_WHITE);
UE_LOG(LogExternalActorsCommandlet, Log, TEXT("Loading level %s."), *LevelToLoad);
CLEAR_WARN_COLOR();
UPackage* MapPackage = LoadPackage(nullptr, *LevelToLoad, LOAD_None);
if (!MapPackage)
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Error loading %s."), *LevelToLoad);
return nullptr;
}
return UWorld::FindWorldInPackage(MapPackage);
}
int32 UExternalActorsCommandlet::Main(const FString& Params)
{
FPackageSourceControlHelper PackageHelper;
TArray<FString> Tokens, Switches;
ParseCommandLine(*Params, Tokens, Switches);
ApplyCommandLineSwitches(this, Switches);
if (bDisable)
{
if (bRepair)
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Bad parameters: -repair cannot be used with -disable"));
return 1;
}
bListMaps |= !DumpCSVFile.IsEmpty();
}
else if (bForce || bReport || bListMaps || DumpCSVFile.Len())
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Bad parameters: -force, -report, -listmaps and -dumpcsv can only be used along with -disable"));
return 1;
}
if (bDisable && bListMaps)
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName);
UE_LOG(LogExternalActorsCommandlet, Display, TEXT("Waiting for asset registry..."));
AssetRegistryModule.Get().SearchAllAssets(true);
TArray<FAssetData> WorldAssets;
AssetRegistryModule.Get().GetAssetsByClass(UWorld::StaticClass()->GetClassPathName(), WorldAssets, true);
WorldAssets.Sort();
struct FWorldEntry
{
FString Path;
int32 NumExternalActors;
FString DateModified;
inline bool operator<(const FWorldEntry& Other) const
{
return NumExternalActors > Other.NumExternalActors;
}
};
TArray<FWorldEntry> WorldEntries;
for (const FAssetData& WorldAsset : WorldAssets)
{
if (ULevel::GetIsLevelUsingExternalActorsFromAsset(WorldAsset))
{
const FString ExternalActorsPath = ULevel::GetExternalActorsPath(WorldAsset.PackageName.ToString());
TArray<FAssetData> WorldExternalActors;
AssetRegistryModule.Get().GetAssetsByPath(*ExternalActorsPath, WorldExternalActors, true, true);
if (!ULevel::GetIsLevelPartitionedFromAsset(WorldAsset) || ULevel::GetIsStreamingDisabledFromAsset(WorldAsset))
{
FString DateModified;
WorldAsset.GetTagValue(TEXT("DateModified"), DateModified);
WorldEntries.Add(
{
.Path = WorldAsset.PackageName.ToString(),
.NumExternalActors = WorldExternalActors.Num(),
.DateModified = DateModified
});
UE_LOG(LogExternalActorsCommandlet, Display, TEXT("Level '%s' is a potential candidate to have external actors disabled (%d)."), *WorldAsset.GetSoftObjectPath().ToString(), WorldExternalActors.Num());
}
else
{
UE_LOG(LogExternalActorsCommandlet, Verbose, TEXT("Level '%s' is not a potential candidate to have external actors disabled (%d)."), *WorldAsset.GetSoftObjectPath().ToString(), WorldExternalActors.Num());
}
}
}
if (DumpCSVFile.Len())
{
FArchive* LogFile = IFileManager::Get().CreateFileWriter(*DumpCSVFile);
if (!LogFile)
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Cannot create csv file '%s'"), *DumpCSVFile);
return 1;
}
FString LineEntry = TEXT("Map,NumOFPA,DateModified") LINE_TERMINATOR;
LogFile->Serialize(TCHAR_TO_ANSI(*LineEntry), LineEntry.Len());
WorldEntries.Sort();
for (const FWorldEntry& WorldEntry : WorldEntries)
{
LineEntry = FString::Printf(TEXT("%s,%d,%s" LINE_TERMINATOR), *WorldEntry.Path, WorldEntry. NumExternalActors, *WorldEntry.DateModified);
LogFile->Serialize(TCHAR_TO_ANSI(*LineEntry), LineEntry.Len());
}
LogFile->Close();
delete LogFile;
}
return 0;
}
TArray<FString> MapList;
if (!MapListFile.IsEmpty())
{
if (!FFileHelper::LoadFileToStringArray(MapList, *MapListFile))
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Invalid map list filename"));
return 1;
}
}
else
{
if (Tokens.Num() < 1)
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Missing map name"));
return 1;
}
MapList.Add(Tokens[0]);
}
int32 Result = 1;
for (const FString& MapName : MapList)
{
FString FullMapName;
if (!FPackageName::SearchForPackageOnDisk(MapName, &FullMapName))
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Unknown level '%s'"), *MapName);
Result = 0;
continue;
}
// Load world
UWorld* MainWorld = LoadWorld(FullMapName);
if (!MainWorld)
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Unknown world '%s'"), *FullMapName);
Result = 0;
continue;
}
TSet<UPackage*> PackagesToSave;
TArray<FString> PackagesToDelete;
if (bDisable)
{
if (!MainWorld->PersistentLevel->IsUsingExternalActors())
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Cannot disable external actors for partitioned level '%s' (already disabled)"), *FullMapName);
Result = 0;
continue;
}
if (UWorldPartition* WorldPartition = MainWorld->GetWorldPartition())
{
int32 NumErrors = 0;
if (WorldPartition->IsStreamingEnabled())
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Cannot disable external actors for partitioned level '%s' with streaming enabled"), *FullMapName);
NumErrors++;
if (bForce)
{
WorldPartition->SetEnableStreaming(false);
}
}
if (UDataLayerManager* DataLayerManager = WorldPartition->GetDataLayerManager())
{
if (DataLayerManager->GetDataLayerInstances().Num())
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Cannot disable external actors for partitioned level '%s' with data layers"), *FullMapName);
NumErrors++;
}
}
if (!NumErrors)
{
UE_LOG(LogExternalActorsCommandlet, Display, TEXT("External actors for partitioned level '%s' can be disabled"), *FullMapName);
}
else if (bForce)
{
UE_LOG(LogExternalActorsCommandlet, Display, TEXT("External actors for partitioned level '%s' will be forcibly disabled"), *FullMapName);
}
else
{
Result = 0;
continue;
}
if (!bReport)
{
WorldPartition->Initialize(MainWorld, FTransform::Identity);
if (!UWorldPartition::RemoveWorldPartition(WorldPartition->GetWorld()->GetWorldSettings()))
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Error disabling external actors for partitioned level '%s'"), *FullMapName);
Result = 0;
continue;
}
WorldPartition->Uninitialize();
}
}
if (!bReport)
{
for (AActor* Actor : MainWorld->PersistentLevel->Actors)
{
if (IsValid(Actor) && Actor->IsMainPackageActor())
{
PackagesToDelete.Add(Actor->GetPackage()->GetLoadedPath().GetLocalFullPath());
Actor->SetPackageExternal(false);
}
}
if (MainWorld->PersistentLevel->IsUsingExternalObjects())
{
ForEachObjectWithOuter(MainWorld->PersistentLevel, [MainWorld, &PackagesToDelete](UObject* Object)
{
if (Object->IsPackageExternal() && !Object->IsA<AActor>())
{
PackagesToDelete.Add(Object->GetPackage()->GetLoadedPath().GetLocalFullPath());
FExternalPackageHelper::SetPackagingMode(Object, MainWorld->PersistentLevel, false);
}
return true;
}, true, RF_MirroredGarbage);
}
if (PackagesToDelete.Num())
{
PackagesToSave.Add(MainWorld->GetPackage());
}
MainWorld->PersistentLevel->SetUseActorFolders(false);
MainWorld->PersistentLevel->SetUseExternalActors(false);
}
}
else
{
// Validate external actors
FString ExternalActorsPath = ULevel::GetExternalActorsPath(FullMapName);
FString ExternalActorsFilePath = FPackageName::LongPackageNameToFilename(ExternalActorsPath);
// Look for duplicated actor GUIDs
TMap<FGuid, AActor*> ActorGuids;
if (IFileManager::Get().DirectoryExists(*ExternalActorsFilePath))
{
bool bResult = IFileManager::Get().IterateDirectoryRecursively(*ExternalActorsFilePath, [this, &PackagesToSave, &PackagesToDelete, &ActorGuids](const TCHAR* FilenameOrDirectory, bool bIsDirectory)
{
if (!bIsDirectory)
{
FString Filename(FilenameOrDirectory);
if (Filename.EndsWith(FPackageName::GetAssetPackageExtension()))
{
AActor* MainPackageActor = nullptr;
AActor* PotentialMainPackageActor = nullptr;
const FString PackageName = FPackageName::FilenameToLongPackageName(*Filename);
if (UPackage* Package = LoadPackage(nullptr, *Filename, LOAD_None, nullptr, nullptr))
{
ForEachObjectWithPackage(Package, [&MainPackageActor, &PotentialMainPackageActor](UObject* Object)
{
if (AActor* Actor = Cast<AActor>(Object))
{
if (Actor->IsMainPackageActor())
{
MainPackageActor = Actor;
PotentialMainPackageActor = nullptr;
}
else if (!MainPackageActor)
{
if (!Actor->IsChildActor())
{
PotentialMainPackageActor = Actor;
}
}
}
return true;
});
}
if (!MainPackageActor)
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Missing main actor for file '%s'"), *Filename);
if (bRepair)
{
if (PotentialMainPackageActor)
{
PotentialMainPackageActor->SetPackageExternal(false);
PotentialMainPackageActor->SetPackageExternal(true);
UPackage* PackageToSave = PotentialMainPackageActor->GetPackage();
PackagesToSave.Add(PackageToSave);
MainPackageActor = PotentialMainPackageActor;
}
PackagesToDelete.Add(Filename);
}
}
if (MainPackageActor)
{
if (ActorGuids.Contains(MainPackageActor->GetActorGuid()))
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Duplicated actor guid for file '%s'"), *Filename);
if (bRepair)
{
FSetActorGuid SetActorGuid(MainPackageActor, FGuid::NewGuid());
UPackage* PackageToSave = MainPackageActor->GetPackage();
PackagesToSave.Add(PackageToSave);
}
}
else
{
ActorGuids.Add(MainPackageActor->GetActorGuid(), MainPackageActor);
}
}
}
else
{
UE_LOG(LogExternalActorsCommandlet, Error, TEXT("Invalid actor file '%s'"), *Filename);
if (bRepair)
{
PackagesToDelete.Add(Filename);
}
}
}
return true;
});
}
}
FSavePackageArgs SaveArgs;
SaveArgs.TopLevelFlags = RF_Standalone;
for (UPackage* PackageToSave : PackagesToSave)
{
const FString PackageFileName = SourceControlHelpers::PackageFilename(PackageToSave);
UE_LOG(LogExternalActorsCommandlet, Display, TEXT("Saving package '%s'"), *PackageFileName);
if (PackageHelper.Checkout(PackageToSave))
{
if (UPackage::SavePackage(PackageToSave, nullptr, *PackageFileName, SaveArgs))
{
PackageHelper.AddToSourceControl(PackageToSave);
}
}
}
CollectGarbage(RF_NoFlags);
PackagesToDelete.Sort();
for (const FString& PackageToDelete : PackagesToDelete)
{
const FString PackageFileName = SourceControlHelpers::PackageFilename(PackageToDelete);
UE_LOG(LogExternalActorsCommandlet, Display, TEXT("Deleting package '%s'"), *PackageFileName);
PackageHelper.Delete(*PackageToDelete);
}
}
return Result;
}