2022-05-23 18:41:30 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "EditorValidator_Load.h"
2022-09-13 07:18:28 +00:00
# include "AssetCompilingManager.h"
# include "CoreGlobals.h"
2022-05-23 18:41:30 +00:00
# include "Engine/Blueprint.h"
# include "Engine/Engine.h"
2022-09-13 07:18:28 +00:00
# include "Engine/World.h"
# include "HAL/FileManager.h"
# include "HAL/PlatformMath.h"
# include "Internationalization/Internationalization.h"
# include "Internationalization/Text.h"
2022-05-23 18:41:30 +00:00
# include "Kismet2/BlueprintEditorUtils.h"
# include "Kismet2/KismetEditorUtilities.h"
2022-09-13 07:18:28 +00:00
# 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"
2022-05-23 18:41:30 +00:00
# include "UObject/UObjectHash.h"
2022-09-13 07:18:28 +00:00
# include "Validation/EditorValidator.h"
2022-05-23 18:41:30 +00:00
# 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