// Copyright Epic Games, Inc. All Rights Reserved. #include "EditorValidator_Load.h" #include "AssetCompilingManager.h" #include "CoreGlobals.h" #include "Engine/Blueprint.h" #include "Engine/Engine.h" #include "Engine/World.h" #include "HAL/FileManager.h" #include "HAL/PlatformMath.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "Misc/AssertionMacros.h" #include "Misc/PackageName.h" #include "Misc/Paths.h" #include "Templates/Casts.h" #include "UObject/Linker.h" #include "UObject/Object.h" #include "UObject/Package.h" #include "UObject/UObjectHash.h" #include "Validation/EditorValidator.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 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& ValidationErrors) { check(InAsset); TArray 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& 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 AllExistingObjects; GetObjectsWithPackage(ExistingPackage, AllExistingObjects, false); TArray AllNonDOBPs; for (UObject* Obj : AllExistingObjects) { UBlueprint* BP = Cast(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 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(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