RealtimeStyleTransferRuntime/Source/LyraEditor/Validation/EditorValidator_Load.cpp

185 lines
6.5 KiB
C++
Raw Normal View History

2022-05-23 18:41:30 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EditorValidator_Load.h"
#include "HAL/FileManager.h"
#include "Engine/Blueprint.h"
#include "Engine/Engine.h"
#include "Blueprint/BlueprintSupport.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Engine/World.h"
#include "ShaderCompiler.h"
#include "UObject/UObjectHash.h"
#define LOCTEXT_NAMESPACE "EditorValidator"
// This list only ignores log messages that occur while we are reloading an asset that is already in memory
// Should only be used for warnings that occur as a result of having the asset in memory in two different packages
TArray<FString> UEditorValidator_Load::InMemoryReloadLogIgnoreList = { TEXT("Enum name collision: '") };
UEditorValidator_Load::UEditorValidator_Load()
: Super()
{
}
bool UEditorValidator_Load::IsEnabled() const
{
// Commandlets do not need this validation step as they loaded the content while running.
return !IsRunningCommandlet() && Super::IsEnabled();
}
bool UEditorValidator_Load::CanValidateAsset_Implementation(UObject* InAsset) const
{
return Super::CanValidateAsset_Implementation(InAsset) && InAsset != nullptr;
}
EDataValidationResult UEditorValidator_Load::ValidateLoadedAsset_Implementation(UObject* InAsset, TArray<FText>& ValidationErrors)
{
check(InAsset);
TArray<FString> WarningsAndErrors;
if (GetLoadWarningsAndErrorsForPackage(InAsset->GetOutermost()->GetName(), WarningsAndErrors))
{
for (const FString& WarningOrError : WarningsAndErrors)
{
AssetFails(InAsset, FText::FromString(WarningOrError), ValidationErrors);
}
}
else
{
AssetFails(InAsset, LOCTEXT("Load_FailedLoad", "Failed to get package load warnings and errors"), ValidationErrors);
}
if (GetValidationResult() != EDataValidationResult::Invalid)
{
AssetPasses(InAsset);
}
return GetValidationResult();
}
bool UEditorValidator_Load::GetLoadWarningsAndErrorsForPackage(const FString& PackageName, TArray<FString>& OutWarningsAndErrors)
{
check(!PackageName.IsEmpty());
check(GEngine);
UPackage* const ExistingPackage = FindPackage(nullptr, *PackageName);
if (ExistingPackage == GetTransientPackage())
{
return true;
}
// Skip World or External Actor packages
if (ExistingPackage && UWorld::IsWorldOrExternalActorPackage(ExistingPackage))
{
return true;
}
// Commandlets shouldnt load the temporary packages since it involves collecting garbage and may destroy objects higher in the callstack. Loading it the one time is probably good enough
// Also since commandlets dont use RF_Standalone, this could greatly increase commandlet execution time when loading the same assets over and over
if (ExistingPackage && !IsRunningCommandlet() && UEditorValidator::ShouldAllowFullValidation() && !ExistingPackage->ContainsMap() && !PackageName.EndsWith(TEXT("_BuiltData")))
{
// Copy the asset file to the temp directory and load it
const FString& SrcPackageName = PackageName;
FString SrcFilename;
const bool bSourceFileExists = FPackageName::DoesPackageExist(SrcPackageName, &SrcFilename);
if (bSourceFileExists)
{
static int32 PackageIdentifier = 0;
FString DestPackageName = FString::Printf(TEXT("/Temp/%s_%d"), *FPackageName::GetLongPackageAssetName(ExistingPackage->GetName()), PackageIdentifier++);
FString DestFilename = FPackageName::LongPackageNameToFilename(DestPackageName, FPaths::GetExtension(SrcFilename, true));
uint32 CopyResult = IFileManager::Get().Copy(*DestFilename, *SrcFilename);
if (ensure(CopyResult == COPY_OK))
{
// Gather all warnings and errors during the process to determine return value
UPackage* LoadedPackage = nullptr;
{
FLyraValidationMessageGatherer::AddIgnorePatterns(InMemoryReloadLogIgnoreList);
FLyraValidationMessageGatherer ScopedMessageGatherer;
// If we are loading a blueprint, compile the original and load the duplicate with DisableCompileOnLoad, since BPs loaded on the side may not compile if there are circular references involving self
int32 LoadFlags = LOAD_ForDiff;
{
TArray<UObject*> AllExistingObjects;
GetObjectsWithPackage(ExistingPackage, AllExistingObjects, false);
TArray<UBlueprint*> AllNonDOBPs;
for (UObject* Obj : AllExistingObjects)
{
UBlueprint* BP = Cast<UBlueprint>(Obj);
if (BP && !FBlueprintEditorUtils::IsDataOnlyBlueprint(BP))
{
AllNonDOBPs.Add(BP);
}
}
if (AllNonDOBPs.Num() > 0)
{
LoadFlags |= LOAD_DisableCompileOnLoad;
for (UBlueprint* BP : AllNonDOBPs)
{
check(BP);
FKismetEditorUtilities::CompileBlueprint(BP);
}
}
}
LoadedPackage = LoadPackage(NULL, *DestPackageName, LoadFlags);
// Make sure what we just loaded has finish compiling otherwise we won't be able
// to reset loaders for the package or verify if errors have been emitted.
FAssetCompilingManager::Get().FinishAllCompilation();
for (const FString& LoadWarningOrError : ScopedMessageGatherer.GetAllWarningsAndErrors())
{
FString SanitizedMessage = LoadWarningOrError.Replace(*DestFilename, *SrcFilename);
SanitizedMessage = SanitizedMessage.Replace(*DestPackageName, *SrcPackageName);
OutWarningsAndErrors.Add(SanitizedMessage);
}
FLyraValidationMessageGatherer::RemoveIgnorePatterns(InMemoryReloadLogIgnoreList);
}
if (LoadedPackage)
{
ResetLoaders(LoadedPackage);
IFileManager::Get().Delete(*DestFilename);
TArray<UObject*> AllLoadedObjects;
GetObjectsWithPackage(LoadedPackage, AllLoadedObjects, true);
for (UObject* Obj : AllLoadedObjects)
{
if (Obj->IsRooted())
{
continue;
}
Obj->ClearFlags(RF_Public | RF_Standalone);
Obj->SetFlags(RF_Transient);
if (UWorld* WorldToDestroy = Cast<UWorld>(Obj))
{
WorldToDestroy->DestroyWorld(true);
}
Obj->MarkAsGarbage();
}
GEngine->ForceGarbageCollection(true);
}
}
else
{
// Failed to copy the file to the temp folder
return false;
}
}
else
{
// It was in memory but not yet saved probably (no source file)
return false;
}
}
else
{
// Not in memory, just load it
FLyraValidationMessageGatherer ScopedMessageGatherer;
LoadPackage(nullptr, *PackageName, LOAD_None);
OutWarningsAndErrors = ScopedMessageGatherer.GetAllWarningsAndErrors();
}
return true;
}
#undef LOCTEXT_NAMESPACE