void FPathContextMenu::ExecuteSaveFolder()
{
	// Get a list of package names in the selected paths
	TArray<FString> PackageNames;
	GetPackageNamesInSelectedPaths(PackageNames);

	// Form a list of packages from the assets
	TArray<UPackage*> Packages;
	for (int32 PackageIdx = 0; PackageIdx < PackageNames.Num(); ++PackageIdx)
	{
		UPackage* Package = FindPackage(NULL, *PackageNames[PackageIdx]);

		// Only save loaded and dirty packages
		if ( Package != NULL && Package->IsDirty() )
		{
			Packages.Add(Package);
		}
	}

	// Save all packages that were found
	if ( Packages.Num() )
	{
		ContentBrowserUtils::SavePackages(Packages);
	}
}
Exemplo n.º 2
0
bool FFrontendFilter_Modified::PassesFilter(FAssetFilterType InItem) const
{
	UPackage* Package = FindPackage(NULL, *InItem.PackageName.ToString());

	if ( Package != NULL )
	{
		return Package->IsDirty();
	}

	return false;
}
Exemplo n.º 3
0
/** 
 * Finds the outermost package and marks it dirty
 */
bool UObjectBaseUtility::MarkPackageDirty() const
{
	// since transient objects will never be saved into a package, there is no need to mark a package dirty
	// if we're transient
	if ( !HasAnyFlags(RF_Transient) )
	{
		UPackage* Package = GetOutermost();

		if( Package != NULL	)
		{
			// It is against policy to dirty a map or package during load in the Editor, to enforce this policy
			// we explicitly disable the ability to dirty a package or map during load.  Commandlets can still
			// set the dirty state on load.
			if( IsRunningCommandlet() || 
				(GIsEditor && !GIsEditorLoadingPackage && !GIsPlayInEditorWorld && !IsInAsyncLoadingThread()
#if WITH_HOT_RELOAD
				&& !GIsHotReload
#endif // WITH_HOT_RELOAD
#if WITH_EDITORONLY_DATA
				&& !Package->bIsCookedForEditor // Cooked packages can't be modified nor marked as dirty
#endif
				))
			{
				const bool bIsDirty = Package->IsDirty();

				// We prevent needless re-dirtying as this can be an expensive operation.
				if( !bIsDirty )
				{
					Package->SetDirtyFlag(true);
				}

				// Always call PackageMarkedDirtyEvent, even when the package is already dirty
				Package->PackageMarkedDirtyEvent.Broadcast(Package, bIsDirty);

				return true;
			}
			else
			{
				// notify the caller that the request to mark the package as dirty was suppressed
				return false;
			}
		}
	}
	return true;
}
void USoundClassGraph::RebuildGraph()
{
	check(RootSoundClass);

	// Don't allow initial graph rebuild to affect package dirty state; remember current state...
	UPackage* Package = GetOutermost();
	const bool bIsDirty = Package->IsDirty();

	Modify();

	RemoveAllNodes();

	ConstructNodes(RootSoundClass, 0, 0);

	NotifyGraphChanged();

	// ...and restore it
	Package->SetDirtyFlag(bIsDirty);
}
/** 
 * Finds the outermost package and marks it dirty
 */
void UObjectBaseUtility::MarkPackageDirty() const
{
	// since transient objects will never be saved into a package, there is no need to mark a package dirty
	// if we're transient
	if ( !HasAnyFlags(RF_Transient) )
	{
		UPackage* Package = GetOutermost();

		if( Package != NULL )
		{
			// It is against policy to dirty a map or package during load in the Editor, to enforce this policy
			// we explicitly disable the ability to dirty a package or map during load.  Commandlets can still
			// set the dirty state on load.
			// We also prevent needless re-dirtying as this can be an expensive operation.
			if( !Package->IsDirty() &&
				(!GIsEditor || IsRunningCommandlet() || 
				(GIsEditor && !GIsEditorLoadingPackage)))
			{
				Package->SetDirtyFlag(true);
			}
		}
	}
}
void UWorldComposition::RestoreDirtyTilesInfo(const FTilesList& TilesPrevState)
{
	if (!TilesPrevState.Num())
	{
		return;
	}
	
	for (FWorldCompositionTile& Tile : Tiles)
	{
		UPackage* LevelPackage = Cast<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), NULL, Tile.PackageName));
		if (LevelPackage && LevelPackage->IsDirty())
		{
			auto* FoundTile = TilesPrevState.FindByPredicate([=](const FWorldCompositionTile& TilePrev)
			{
				return TilePrev.PackageName == Tile.PackageName;
			});
			
			if (FoundTile)
			{
				Tile.Info = FoundTile->Info;
			}
		}
	}
}
int32 UGatherTextFromAssetsCommandlet::Main(const FString& Params)
{
	// Parse command line.
	TArray<FString> Tokens;
	TArray<FString> Switches;
	TMap<FString, FString> ParamVals;
	UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamVals);

	//Set config file
	const FString* ParamVal = ParamVals.Find(FString(TEXT("Config")));
	FString GatherTextConfigPath;

	if ( ParamVal )
	{
		GatherTextConfigPath = *ParamVal;
	}
	else
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Error, TEXT("No config specified."));
		return -1;
	}

	//Set config section
	ParamVal = ParamVals.Find(FString(TEXT("Section")));
	FString SectionName;

	if ( ParamVal )
	{
		SectionName = *ParamVal;
	}
	else
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Error, TEXT("No config section specified."));
		return -1;
	}

	//Modules to Preload
	TArray<FString> ModulesToPreload;
	GetStringArrayFromConfig(*SectionName, TEXT("ModulesToPreload"), ModulesToPreload, GatherTextConfigPath);

	for (const FString& ModuleName : ModulesToPreload)
	{
		FModuleManager::Get().LoadModule(*ModuleName);
	}

	// IncludePathFilters
	TArray<FString> IncludePathFilters;
	GetPathArrayFromConfig(*SectionName, TEXT("IncludePathFilters"), IncludePathFilters, GatherTextConfigPath);

	// IncludePaths (DEPRECATED)
	{
		TArray<FString> IncludePaths;
		GetPathArrayFromConfig(*SectionName, TEXT("IncludePaths"), IncludePaths, GatherTextConfigPath);
		if (IncludePaths.Num())
		{
			IncludePathFilters.Append(IncludePaths);
			UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("IncludePaths detected in section %s. IncludePaths is deprecated, please use IncludePathFilters."), *SectionName);
		}
	}

	if (IncludePathFilters.Num() == 0)
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Error, TEXT("No include path filters in section %s."), *SectionName);
		return -1;
	}

	// ExcludePathFilters
	TArray<FString> ExcludePathFilters;
	GetPathArrayFromConfig(*SectionName, TEXT("ExcludePathFilters"), ExcludePathFilters, GatherTextConfigPath);

	// ExcludePaths (DEPRECATED)
	{
		TArray<FString> ExcludePaths;
		GetPathArrayFromConfig(*SectionName, TEXT("ExcludePaths"), ExcludePaths, GatherTextConfigPath);
		if (ExcludePaths.Num())
		{
			ExcludePathFilters.Append(ExcludePaths);
			UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("ExcludePaths detected in section %s. ExcludePaths is deprecated, please use ExcludePathFilters."), *SectionName);
		}
	}

	// PackageNameFilters
	TArray<FString> PackageFileNameFilters;
	GetStringArrayFromConfig(*SectionName, TEXT("PackageFileNameFilters"), PackageFileNameFilters, GatherTextConfigPath);

	// PackageExtensions (DEPRECATED)
	{
		TArray<FString> PackageExtensions;
		GetStringArrayFromConfig(*SectionName, TEXT("PackageExtensions"), PackageExtensions, GatherTextConfigPath);
		if (PackageExtensions.Num())
		{
			PackageFileNameFilters.Append(PackageExtensions);
			UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("PackageExtensions detected in section %s. PackageExtensions is deprecated, please use PackageFileNameFilters."), *SectionName);
		}
	}

	if (PackageFileNameFilters.Num() == 0)
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Error, TEXT("No package file name filters in section %s."), *SectionName);
		return -1;
	}

	//asset class exclude
	TArray<FString> ExcludeClasses;
	GetStringArrayFromConfig(*SectionName, TEXT("ExcludeClasses"), ExcludeClasses, GatherTextConfigPath);

	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
	AssetRegistryModule.Get().SearchAllAssets( true );
	FARFilter Filter;

	for(const auto& ExcludeClass : ExcludeClasses)
	{
		UClass* FilterClass = FindObject<UClass>(ANY_PACKAGE, *ExcludeClass);
		if(FilterClass)
		{
			Filter.ClassNames.Add( FilterClass->GetFName() );
		}
		else
		{
			UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("Invalid exclude class %s"), *ExcludeClass);
		}
	}

	TArray<FAssetData> AssetDataArray;
	AssetRegistryModule.Get().GetAssets(Filter, AssetDataArray);

	FString UAssetPackageExtension = FPackageName::GetAssetPackageExtension();
	TSet< FString > LongPackageNamesToExclude;
	for (int Index = 0; Index < AssetDataArray.Num(); Index++)
	{
		LongPackageNamesToExclude.Add( FPackageName::LongPackageNameToFilename( AssetDataArray[Index].PackageName.ToString(), UAssetPackageExtension ) );
	}

	//Get whether we should fix broken properties that we find.
	if (!GetBoolFromConfig(*SectionName, TEXT("bFixBroken"), bFixBroken, GatherTextConfigPath))
	{
		bFixBroken = false;
	}

	// Get whether we should gather editor-only data. Typically only useful for the localization of UE4 itself.
	if (!GetBoolFromConfig(*SectionName, TEXT("ShouldGatherFromEditorOnlyData"), ShouldGatherFromEditorOnlyData, GatherTextConfigPath))
	{
		ShouldGatherFromEditorOnlyData = false;
	}

	// Add any manifest dependencies if they were provided
	TArray<FString> ManifestDependenciesList;
	GetPathArrayFromConfig(*SectionName, TEXT("ManifestDependencies"), ManifestDependenciesList, GatherTextConfigPath);

	if( !ManifestInfo->AddManifestDependencies( ManifestDependenciesList ) )
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Error, TEXT("The GatherTextFromAssets commandlet couldn't find all the specified manifest dependencies."));
		return -1;
	}

	//The main array of files to work from.
	TArray< FString > PackageFileNamesToProcess;

	TArray<FString> PackageFilesNotInIncludePath;
	TArray<FString> PackageFilesInExcludePath;
	TArray<FString> PackageFilesExcludedByClass;

	//Fill the list of packages to work from.
	uint8 PackageFilter = NORMALIZE_DefaultFlags;
	TArray<FString> Unused;	
	for ( int32 PackageFilenameWildcardIdx = 0; PackageFilenameWildcardIdx < PackageFileNameFilters.Num(); PackageFilenameWildcardIdx++ )
	{
		const bool IsAssetPackage = PackageFileNameFilters[PackageFilenameWildcardIdx] == ( FString( TEXT("*") )+ FPackageName::GetAssetPackageExtension() );

		TArray<FString> PackageFiles;
		if ( !NormalizePackageNames( Unused, PackageFiles, PackageFileNameFilters[PackageFilenameWildcardIdx], PackageFilter) )
		{
			UE_LOG(LogGatherTextFromAssetsCommandlet, Display, TEXT("No packages found with extension %i: '%s'"), PackageFilenameWildcardIdx, *PackageFileNameFilters[PackageFilenameWildcardIdx]);
			continue;
		}
		else
		{
			UE_LOG(LogGatherTextFromAssetsCommandlet, Display, TEXT("Found %i packages with extension %i: '%s'"), PackageFiles.Num(), PackageFilenameWildcardIdx, *PackageFileNameFilters[PackageFilenameWildcardIdx]);
		}

		//Run through all the files found and add any that pass the include, exclude and filter constraints to OrderedPackageFilesToLoad
		for (FString& PackageFile : PackageFiles)
		{
			PackageFile = FPaths::ConvertRelativePathToFull(PackageFile);

			bool bExclude = false;
			//Ensure it matches the include paths if there are some.
			for (FString& IncludePath : IncludePathFilters)
			{
				bExclude = true;
				if( PackageFile.MatchesWildcard(IncludePath) )
				{
					bExclude = false;
					break;
				}
			}

			if ( bExclude )
			{
				PackageFilesNotInIncludePath.Add(PackageFile);
			}

			//Ensure it does not match the exclude paths if there are some.
			for (const FString& ExcludePath : ExcludePathFilters)
			{
				if (PackageFile.MatchesWildcard(ExcludePath))
				{
					bExclude = true;
					PackageFilesInExcludePath.Add(PackageFile);
					break;
				}
			}

			//Check that this is not on the list of packages that we don't care about e.g. textures.
			if ( !bExclude && IsAssetPackage && LongPackageNamesToExclude.Contains( PackageFile ) )
			{
				bExclude = true;
				PackageFilesExcludedByClass.Add(PackageFile);
			}

			//If we haven't failed one of the above checks, add it to the array of packages to process.
			if(!bExclude)
			{
				PackageFileNamesToProcess.Add(PackageFile);
			}
		}
	}

	if ( PackageFileNamesToProcess.Num() == 0 )
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("No files found or none passed the include/exclude criteria."));
	}

	bool bSkipGatherCache = FParse::Param(FCommandLine::Get(), TEXT("SkipGatherCache"));
	if (!bSkipGatherCache)
	{
		GetBoolFromConfig(*SectionName, TEXT("SkipGatherCache"), bSkipGatherCache, GatherTextConfigPath);
	}
	UE_LOG(LogGatherTextFromAssetsCommandlet, Log, TEXT("SkipGatherCache: %s"), bSkipGatherCache ? TEXT("true") : TEXT("false"));

	TArray< FString > PackageFileNamesToLoad;
	for (FString& PackageFile : PackageFileNamesToProcess)
	{
		TScopedPointer< FArchive > FileReader( IFileManager::Get().CreateFileReader( *PackageFile ) );
		if( FileReader )
		{
			// Read package file summary from the file
			FPackageFileSummary PackageFileSummary;
			(*FileReader) << PackageFileSummary;

			bool MustLoadForGather = false;

			// Have we been asked to skip the cache of text that exists in the header of newer packages?
			if (bSkipGatherCache && PackageFileSummary.GetFileVersionUE4() >= VER_UE4_SERIALIZE_TEXT_IN_PACKAGES)
			{
				// Fallback on the old package flag check.
				if (PackageFileSummary.PackageFlags & PKG_RequiresLocalizationGather)
				{
					MustLoadForGather = true;
				}
			}

			const FCustomVersion* const EditorVersion = PackageFileSummary.GetCustomVersionContainer().GetVersion(FEditorObjectVersion::GUID);

			// Packages not resaved since localization gathering flagging was added to packages must be loaded.
			if (PackageFileSummary.GetFileVersionUE4() < VER_UE4_PACKAGE_REQUIRES_LOCALIZATION_GATHER_FLAGGING)
			{
				MustLoadForGather = true;
			}
			// Package not resaved since gatherable text data was added to package headers must be loaded, since their package header won't contain pregathered text data.
			else if (PackageFileSummary.GetFileVersionUE4() < VER_UE4_SERIALIZE_TEXT_IN_PACKAGES)
			{
				// Fallback on the old package flag check.
				if (PackageFileSummary.PackageFlags & PKG_RequiresLocalizationGather)
				{
					MustLoadForGather = true;
				}
			}
			else if (PackageFileSummary.GetFileVersionUE4() < VER_UE4_DIALOGUE_WAVE_NAMESPACE_AND_CONTEXT_CHANGES)
			{
				IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
				TArray<FAssetData> AssetDataInPackage;
				AssetRegistry.GetAssetsByPackageName(*FPackageName::FilenameToLongPackageName(PackageFile), AssetDataInPackage);
				for (const FAssetData& AssetData : AssetDataInPackage)
				{
					if (AssetData.AssetClass == UDialogueWave::StaticClass()->GetFName())
					{
						MustLoadForGather = true;
					}
				}
			}
				 
			// Add package to list of packages to load fully and process.
			if (MustLoadForGather)
			{
				PackageFileNamesToLoad.Add(PackageFile);
			}
			// Process immediately packages that don't require loading to process.
			else if (PackageFileSummary.GatherableTextDataOffset > 0)
			{
				FileReader->Seek(PackageFileSummary.GatherableTextDataOffset);

				TArray<FGatherableTextData> GatherableTextDataArray;
				GatherableTextDataArray.SetNum(PackageFileSummary.GatherableTextDataCount);

				for (int32 GatherableTextDataIndex = 0; GatherableTextDataIndex < PackageFileSummary.GatherableTextDataCount; ++GatherableTextDataIndex)
				{
					(*FileReader) << GatherableTextDataArray[GatherableTextDataIndex];
				}

				ProcessGatherableTextDataArray(PackageFile, GatherableTextDataArray);
			}
		}
	}

	CollectGarbage(RF_NoFlags);

	//Now go through the remaining packages in the main array and process them in batches.
	int32 PackagesPerBatchCount = 100;
	TArray< UPackage* > LoadedPackages;
	TArray< FString > LoadedPackageFileNames;
	TArray< FString > FailedPackageFileNames;
	TArray< UPackage* > LoadedPackagesToProcess;

	const int32 PackageCount = PackageFileNamesToLoad.Num();
	const int32 BatchCount = PackageCount / PackagesPerBatchCount + (PackageCount % PackagesPerBatchCount > 0 ? 1 : 0); // Add an extra batch for any remainder if necessary
	if(PackageCount > 0)
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Log, TEXT("Loading %i packages in %i batches of %i."), PackageCount, BatchCount, PackagesPerBatchCount);
	}

	FLoadPackageLogOutputRedirector LogOutputRedirector;

	//Load the packages in batches
	int32 PackageIndex = 0;
	for( int32 BatchIndex = 0; BatchIndex < BatchCount; ++BatchIndex )
	{
		int32 PackagesInThisBatch = 0;
		int32 FailuresInThisBatch = 0;
		for( ; PackageIndex < PackageCount && PackagesInThisBatch < PackagesPerBatchCount; ++PackageIndex )
		{
			FString PackageFileName = PackageFileNamesToLoad[PackageIndex];

			UE_LOG(LogGatherTextFromAssetsCommandlet, Verbose, TEXT("Loading package: '%s'."), *PackageFileName);

			UPackage *Package = nullptr;
			{
				FString LongPackageName;
				if (!FPackageName::TryConvertFilenameToLongPackageName(PackageFileName, LongPackageName))
				{
					LongPackageName = FPaths::GetCleanFilename(PackageFileName);
				}

				FLoadPackageLogOutputRedirector::FScopedCapture ScopedCapture(&LogOutputRedirector, LongPackageName);
				Package = LoadPackage( NULL, *PackageFileName, LOAD_NoWarn | LOAD_Quiet );
			}

			if( Package )
			{
				LoadedPackages.Add(Package);
				LoadedPackageFileNames.Add(PackageFileName);

				// Because packages may not have been resaved after this flagging was implemented, we may have added packages to load that weren't flagged - potential false positives.
				// The loading process should have reflagged said packages so that only true positives will have this flag.
				if( Package->RequiresLocalizationGather() )
				{
					LoadedPackagesToProcess.Add( Package );
				}
			}
			else
			{
				FailedPackageFileNames.Add( PackageFileName );
				++FailuresInThisBatch;
				continue;
			}

			++PackagesInThisBatch;
		}

		UE_LOG(LogGatherTextFromAssetsCommandlet, Log, TEXT("Loaded %i packages in batch %i of %i. %i failed."), PackagesInThisBatch, BatchIndex + 1, BatchCount, FailuresInThisBatch);

		ProcessPackages(LoadedPackagesToProcess);
		LoadedPackagesToProcess.Empty(PackagesPerBatchCount);

		if( bFixBroken )
		{
			for( int32 LoadedPackageIndex=0; LoadedPackageIndex < LoadedPackages.Num() ; ++LoadedPackageIndex )
			{
				UPackage *Package = LoadedPackages[LoadedPackageIndex];
				const FString PackageName = LoadedPackageFileNames[LoadedPackageIndex];

				//Todo - link with source control.
				if( Package )
				{
					if( Package->IsDirty() )
					{
						if( SavePackageHelper( Package, *PackageName ) )
						{
							UE_LOG(LogGatherTextFromAssetsCommandlet, Log, TEXT("Saved Package %s."),*PackageName);
						}
						else
						{
							//TODO - Work out how to integrate with source control. The code from the source gatherer doesn't work.
							UE_LOG(LogGatherTextFromAssetsCommandlet, Log, TEXT("Could not save package %s. Probably due to source control. "),*PackageName);
						}
					}
				}
				else
				{
					UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("Failed to find one of the loaded packages."));
				}
			}
		}

		CollectGarbage(RF_NoFlags);
		LoadedPackages.Empty(PackagesPerBatchCount);	
		LoadedPackageFileNames.Empty(PackagesPerBatchCount);
	}

	return 0;
}
Exemplo n.º 8
0
void UUnrealEdEngine::OnPackageDirtyStateUpdated( UPackage* Pkg)
{
	// The passed in object should never be NULL
	check(Pkg);

	UPackage* Package = Pkg->GetOutermost();
	const FString PackageName = Package->GetName();

	// Alert the user if they have modified a package that won't be able to be saved because
	// it's already been saved with an engine version that is newer than the current one.
	if (!FUObjectThreadContext::Get().IsRoutingPostLoad && Package->IsDirty() && !PackagesCheckedForEngineVersion.Contains(PackageName))
	{
		EWriteDisallowedWarningState WarningStateToSet = WDWS_WarningUnnecessary;
				
		FString PackageFileName;
		if ( FPackageName::DoesPackageExist( Package->GetName(), NULL, &PackageFileName ) )
		{
			// If a package has never been loaded, a file reader is necessary to find the package file summary for its saved engine version.
			FArchive* PackageReader = IFileManager::Get().CreateFileReader( *PackageFileName );
			if ( PackageReader )
			{
				FPackageFileSummary Summary;
				*PackageReader << Summary;

				if ( Summary.GetFileVersionUE4() > GPackageFileUE4Version || !GEngineVersion.IsCompatibleWith(Summary.CompatibleWithEngineVersion) )
				{
					WarningStateToSet = WDWS_PendingWarn;
					bNeedWarningForPkgEngineVer = true;
				}
			}
			delete PackageReader;
		}
		PackagesCheckedForEngineVersion.Add( PackageName, WarningStateToSet );
	}

	// Alert the user if they have modified a package that they do not have sufficient permission to write to disk.
	// This can be due to the content being in the "Program Files" folder and the user does not have admin privileges.
	if (!FUObjectThreadContext::Get().IsRoutingPostLoad && Package->IsDirty() && !PackagesCheckedForWritePermission.Contains(PackageName))
	{
		EWriteDisallowedWarningState WarningStateToSet = GetWarningStateForWritePermission(PackageName);

		if ( WarningStateToSet == WDWS_PendingWarn )
		{
			bNeedWarningForWritePermission = true;
		}
		
		PackagesCheckedForWritePermission.Add( PackageName, WarningStateToSet );
	}

	if( Package->IsDirty() )
	{
		// Find out if we have already asked the user to modify this package
		const uint8* PromptState = PackageToNotifyState.Find( Package );
		const bool bAlreadyAsked = PromptState != NULL;

		// During an autosave, packages are saved in the autosave directory which switches off their dirty flags.
		// To preserve the pre-autosave state, any saved package is then remarked as dirty because it wasn't saved in the normal location where it would be picked up by source control.
		// Any callback that happens during an autosave is bogus since a package wasn't marked dirty due to a user modification.
		const bool bIsAutoSaving = PackageAutoSaver.Get() && PackageAutoSaver->IsAutoSaving();

		const UEditorLoadingSavingSettings* Settings = GetDefault<UEditorLoadingSavingSettings>();

		if( !bIsAutoSaving && 
			!GIsEditorLoadingPackage && // Don't ask if the package was modified as a result of a load
			!bAlreadyAsked && // Don't ask if we already asked once!
			(Settings->bPromptForCheckoutOnAssetModification || Settings->bAutomaticallyCheckoutOnAssetModification) )
		{
			// Force source control state to be updated
			ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();

			TArray<FString> Files;
			Files.Add(SourceControlHelpers::PackageFilename(Package));
			SourceControlProvider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), SourceControlHelpers::AbsoluteFilenames(Files), EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateUObject(this, &UUnrealEdEngine::OnSourceControlStateUpdated, TWeakObjectPtr<UPackage>(Package)));
		}
	}
	else
	{
		// This package was saved, the user should be prompted again if they checked in the package
		PackageToNotifyState.Remove( Package );
	}
}
int32 UGatherTextFromAssetsCommandlet::Main(const FString& Params)
{
	// Parse command line.
	TArray<FString> Tokens;
	TArray<FString> Switches;
	TMap<FString, FString> ParamVals;
	UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamVals);

	//Set config file
	const FString* ParamVal = ParamVals.Find(FString(TEXT("Config")));
	FString GatherTextConfigPath;

	if ( ParamVal )
	{
		GatherTextConfigPath = *ParamVal;
	}
	else
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Error, TEXT("No config specified."));
		return -1;
	}

	//Set config section
	ParamVal = ParamVals.Find(FString(TEXT("Section")));
	FString SectionName;

	if ( ParamVal )
	{
		SectionName = *ParamVal;
	}
	else
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Error, TEXT("No config section specified."));
		return -1;
	}

	//Include paths
	TArray<FString> IncludePaths;
	GetConfigArray(*SectionName, TEXT("IncludePaths"), IncludePaths, GatherTextConfigPath);

	if (IncludePaths.Num() == 0)
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Error, TEXT("No include paths in section %s"), *SectionName);
		return -1;
	}

	//Exclude paths
	TArray<FString> ExcludePaths;
	GetConfigArray(*SectionName, TEXT("ExcludePaths"), ExcludePaths, GatherTextConfigPath);

	//package extensions
	TArray<FString> PackageExts;
	GetConfigArray(*SectionName, TEXT("PackageExtensions"), PackageExts, GatherTextConfigPath);

	if (PackageExts.Num() == 0)
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("No package extensions specified in section %s, using defaults"), *SectionName);

		PackageExts.Add(FString("*") + FPackageName::GetAssetPackageExtension());
		PackageExts.Add(FString("*") + FPackageName::GetMapPackageExtension());
	}

	//asset class exclude
	TArray<FString> ExcludeClasses;
	GetConfigArray(*SectionName, TEXT("ExcludeClasses"), ExcludeClasses, GatherTextConfigPath);

	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
	AssetRegistryModule.Get().SearchAllAssets( true );
	FARFilter Filter;

	for(int32 i = 0; i < ExcludeClasses.Num(); i++)
	{
		UClass* FilterClass = FindObject<UClass>(ANY_PACKAGE, *ExcludeClasses[i]);
		if(FilterClass)
		{
			Filter.ClassNames.Add( FilterClass->GetFName() );
		}
		else
		{
			UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("Invalid exclude class %s"), *ExcludeClasses[i]);
		}
	}

	TArray<FAssetData> AssetData;
	AssetRegistryModule.Get().GetAssets(Filter, AssetData);

	FString UAssetPackageExtension = FPackageName::GetAssetPackageExtension();
	TSet< FString > LongPackageNamesToExclude;
	for (int Index = 0; Index < AssetData.Num(); Index++)
	{
		LongPackageNamesToExclude.Add( FPackageName::LongPackageNameToFilename( AssetData[Index].PackageName.ToString(), UAssetPackageExtension ) );
	}

	//Get whether we should fix broken properties that we find.
	GetConfigBool(*SectionName, TEXT("bFixBroken"), bFixBroken, GatherTextConfigPath);

	// Add any manifest dependencies if they were provided
	TArray<FString> ManifestDependenciesList;
	GetConfigArray(*SectionName, TEXT("ManifestDependencies"), ManifestDependenciesList, GatherTextConfigPath);
	
	if( !ManifestInfo->AddManifestDependencies( ManifestDependenciesList ) )
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Error, TEXT("The GatherTextFromAssets commandlet couldn't find all the specified manifest dependencies."));
		return -1;
	}

	//The main array of files to work from.
	TArray< FString > PackageFileNamesToLoad;
	TSet< FString > LongPackageNamesToProcess;

	TArray<FString> PackageFilesNotInIncludePath;
	TArray<FString> PackageFilesInExcludePath;
	TArray<FString> PackageFilesExcludedByClass;

	//Fill the list of packages to work from.
	uint8 PackageFilter = NORMALIZE_DefaultFlags;
	TArray<FString> Unused;	
	for ( int32 PackageFilenameWildcardIdx = 0; PackageFilenameWildcardIdx < PackageExts.Num(); PackageFilenameWildcardIdx++ )
	{
		const bool IsAssetPackage = PackageExts[PackageFilenameWildcardIdx] == ( FString( TEXT("*") )+ FPackageName::GetAssetPackageExtension() );

		TArray<FString> PackageFiles;
		if ( !NormalizePackageNames( Unused, PackageFiles, PackageExts[PackageFilenameWildcardIdx], PackageFilter) )
		{
			UE_LOG(LogGatherTextFromAssetsCommandlet, Display, TEXT("No packages found with extension %i: '%s'"), PackageFilenameWildcardIdx, *PackageExts[PackageFilenameWildcardIdx]);
			continue;
		}
		else
		{
			UE_LOG(LogGatherTextFromAssetsCommandlet, Display, TEXT("Found %i packages with extension %i: '%s'"), PackageFiles.Num(), PackageFilenameWildcardIdx, *PackageExts[PackageFilenameWildcardIdx]);
		}

		//Run through all the files found and add any that pass the include, exclude and filter constraints to OrderedPackageFilesToLoad
		for( int32 PackageFileIdx=0; PackageFileIdx<PackageFiles.Num(); ++PackageFileIdx )
		{
			bool bExclude = false;
			//Ensure it matches the include paths if there are some.
			for( int32 IncludePathIdx=0; IncludePathIdx<IncludePaths.Num() ; ++IncludePathIdx )
			{
				bExclude = true;
				if( PackageFiles[PackageFileIdx].MatchesWildcard(IncludePaths[IncludePathIdx]) )
				{
					bExclude = false;
					break;
				}
			}

			if ( bExclude )
			{
				PackageFilesNotInIncludePath.Add(PackageFiles[PackageFileIdx]);
			}

			//Ensure it does not match the exclude paths if there are some.
			for( int32 ExcludePathIdx=0; !bExclude && ExcludePathIdx<ExcludePaths.Num() ; ++ExcludePathIdx )
			{
				if( PackageFiles[PackageFileIdx].MatchesWildcard(ExcludePaths[ExcludePathIdx]) )
				{
					bExclude = true;
					PackageFilesInExcludePath.Add(PackageFiles[PackageFileIdx]);
					break;
				}
			}

			//Check that this is not on the list of packages that we don't care about e.g. textures.
			if ( !bExclude && IsAssetPackage && LongPackageNamesToExclude.Contains( PackageFiles[PackageFileIdx] ) )
			{
				bExclude = true;
				PackageFilesExcludedByClass.Add(PackageFiles[PackageFileIdx]);
			}

			//If we haven't failed one of the above checks, add it to the array of packages to process.
			if(!bExclude)
			{
				TScopedPointer< FArchive > FileReader( IFileManager::Get().CreateFileReader( *PackageFiles[PackageFileIdx] ) );
				if( FileReader )
				{
					// Read package file summary from the file
					FPackageFileSummary PackageSummary;
					(*FileReader) << PackageSummary;

					// Early out check if the package has been flagged as needing localization gathering
					if( PackageSummary.PackageFlags & PKG_RequiresLocalizationGather || PackageSummary.GetFileVersionUE4() < VER_UE4_PACKAGE_REQUIRES_LOCALIZATION_GATHER_FLAGGING )
					{
						PackageFileNamesToLoad.Add( PackageFiles[PackageFileIdx] );
					}
				}
			}
		}
	}

	if ( PackageFileNamesToLoad.Num() == 0 )
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("No files found. Or none passed the include/exclude criteria."));
	}

	CollectGarbage( RF_Native );

	//Now go through the remaining packages in the main array and process them in batches.
	int32 PackagesPerBatchCount = 100;
	TArray< UPackage* > LoadedPackages;
	TArray< FString > LoadedPackageFileNames;
	TArray< FString > FailedPackageFileNames;
	TArray< UPackage* > PackagesToProcess;

	const int32 PackageCount = PackageFileNamesToLoad.Num();
	const int32 BatchCount = PackageCount / PackagesPerBatchCount + (PackageCount % PackagesPerBatchCount > 0 ? 1 : 0); // Add an extra batch for any remainder if necessary
	if(PackageCount > 0)
	{
		UE_LOG(LogGatherTextFromAssetsCommandlet, Log, TEXT("Loading %i packages in %i batches of %i."), PackageCount, BatchCount, PackagesPerBatchCount);
	}

	//Load the packages in batches
	int32 PackageIndex = 0;
	for( int32 BatchIndex = 0; BatchIndex < BatchCount; ++BatchIndex )
	{
		int32 PackagesInThisBatch = 0;
		for( PackageIndex; PackageIndex < PackageCount && PackagesInThisBatch < PackagesPerBatchCount; ++PackageIndex )
		{
			FString PackageFileName = PackageFileNamesToLoad[PackageIndex];

			UPackage *Package = LoadPackage( NULL, *PackageFileName, LOAD_None );
			if( Package )
			{
				LoadedPackages.Add(Package);
				LoadedPackageFileNames.Add(PackageFileName);

				// Because packages may not have been resaved after this flagging was implemented, we may have added packages to load that weren't flagged - potential false positives.
				// The loading process should have reflagged said packages so that only true positives will have this flag.
				if( Package->RequiresLocalizationGather() )
				{
					PackagesToProcess.Add( Package );
				}
			}
			else
			{
				FailedPackageFileNames.Add( PackageFileName );
				continue;
			}

			++PackagesInThisBatch;
		}

		UE_LOG(LogGatherTextFromAssetsCommandlet, Log, TEXT("Loaded %i packages in batch %i of %i."), PackagesInThisBatch, BatchIndex + 1, BatchCount);

		ProcessPackages(PackagesToProcess);
		PackagesToProcess.Empty(PackagesPerBatchCount);

		if( bFixBroken )
		{
			for( int32 LoadedPackageIndex=0; LoadedPackageIndex < LoadedPackages.Num() ; ++LoadedPackageIndex )
			{
				UPackage *Package = LoadedPackages[LoadedPackageIndex];
				const FString PackageName = LoadedPackageFileNames[LoadedPackageIndex];

				//Todo - link with source control.
				if( Package )
				{
					if( Package->IsDirty() )
					{
						if( SavePackageHelper( Package, *PackageName ) )
						{
							UE_LOG(LogGatherTextFromAssetsCommandlet, Log, TEXT("Saved Package %s."),*PackageName);
						}
						else
						{
							//TODO - Work out how to integrate with source control. The code from the source gatherer doesn't work.
							UE_LOG(LogGatherTextFromAssetsCommandlet, Log, TEXT("Could not save package %s. Probably due to source control. "),*PackageName);
						}
					}
				}
				else
				{
					UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("Failed to find one of the loaded packages."));
				}
			}
		}

		CollectGarbage( RF_Native );
		LoadedPackages.Empty(PackagesPerBatchCount);	
		LoadedPackageFileNames.Empty(PackagesPerBatchCount);
	}

	for(auto i = ConflictTracker.Namespaces.CreateConstIterator(); i; ++i)
	{
		const FString& NamespaceName = i.Key();
		const FConflictTracker::FKeyTable& KeyTable = i.Value();
		for(auto j = KeyTable.CreateConstIterator(); j; ++j)
		{
			const FString& KeyName = j.Key();
			const FConflictTracker::FEntryArray& EntryArray = j.Value();

			for(int k = 0; k < EntryArray.Num(); ++k)
			{
				const FConflictTracker::FEntry& Entry = EntryArray[k];
				switch(Entry.Status)
				{
				case EAssetTextGatherStatus::MissingKey:
					{
						UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("Detected missing key on asset \"%s\"."), *Entry.ObjectPath);
					}
					break;
				case EAssetTextGatherStatus::MissingKey_Resolved:
					{
						UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("Fixed missing key on asset \"%s\"."), *Entry.ObjectPath);
					}
					break;
				case EAssetTextGatherStatus::IdentityConflict:
					{
						UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("Detected duplicate identity with differing source on asset \"%s\"."), *Entry.ObjectPath);
					}
					break;
				case EAssetTextGatherStatus::IdentityConflict_Resolved:
					{
						UE_LOG(LogGatherTextFromAssetsCommandlet, Warning, TEXT("Fixed duplicate identity with differing source on asset \"%s\"."), *Entry.ObjectPath);
					}
					break;
				}
			}
		}
	}

	return 0;
}
Exemplo n.º 10
0
bool ULevelStreaming::RequestLevel(UWorld* PersistentWorld, bool bAllowLevelLoadRequests, bool bBlockOnLoad)
{
	// Quit early in case load request already issued
	if (bHasLoadRequestPending)
	{
		return true;
	}

	// Previous attempts have failed, no reason to try again
	if (bFailedToLoad)
	{
		return false;
	}
	
	QUICK_SCOPE_CYCLE_COUNTER(STAT_ULevelStreaming_RequestLevel);
	FScopeCycleCounterUObject Context(PersistentWorld);
	// Package name we want to load
	FName DesiredPackageName = PersistentWorld->IsGameWorld() ? GetLODPackageName() : GetWorldAssetPackageFName();
	FName DesiredPackageNameToLoad = PersistentWorld->IsGameWorld() ? GetLODPackageNameToLoad() : PackageNameToLoad;

	// Check if currently loaded level is what we want right now
	if (LoadedLevel != NULL && 
		LoadedLevel->GetOutermost()->GetFName() == DesiredPackageName)
	{
		return true;
	}

	// Can not load new level now, there is still level pending unload
	if (PendingUnloadLevel != NULL)
	{
		return false;
	}
		
	// Try to find the [to be] loaded package.
	UPackage* LevelPackage = (UPackage*) StaticFindObjectFast(UPackage::StaticClass(), NULL, DesiredPackageName, 0, 0, RF_PendingKill);

	// Package is already or still loaded.
	if (LevelPackage)
	{
		// Find world object and use its PersistentLevel pointer.
		UWorld* World = UWorld::FindWorldInPackage(LevelPackage);

		// Check for a redirector. Follow it, if found.
		if ( !World )
		{
			World = UWorld::FollowWorldRedirectorInPackage(LevelPackage);
			if ( World )
			{
				LevelPackage = World->GetOutermost();
			}
		}

		if (World != NULL)
		{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
			if (World->PersistentLevel == NULL)
			{
				UE_LOG(LogLevelStreaming, Log, TEXT("World exists but PersistentLevel doesn't for %s, most likely caused by reference to world of unloaded level and GC setting reference to NULL while keeping world object"), *World->GetOutermost()->GetName());
				// print out some debug information...
				StaticExec(World, *FString::Printf(TEXT("OBJ REFS CLASS=WORLD NAME=%s shortest"), *World->GetPathName()));
				TMap<UObject*,UProperty*> Route = FArchiveTraceRoute::FindShortestRootPath( World, true, GARBAGE_COLLECTION_KEEPFLAGS );
				FString ErrorString = FArchiveTraceRoute::PrintRootPath( Route, World );
				UE_LOG(LogLevelStreaming, Log, TEXT("%s"), *ErrorString);
				// before asserting
				checkf(World->PersistentLevel,TEXT("Most likely caused by reference to world of unloaded level and GC setting reference to NULL while keeping world object"));
				return false;
			}
#endif
			if (World->PersistentLevel != LoadedLevel)
			{
				SetLoadedLevel(World->PersistentLevel);
				// Broadcast level loaded event to blueprints
				OnLevelLoaded.Broadcast(this);
			}
			
			return true;
		}
	}

	EPackageFlags PackageFlags = PKG_None;
	int32 PIEInstanceID = INDEX_NONE;

	// copy streaming level on demand if we are in PIE
	// (the world is already loaded for the editor, just find it and copy it)
	if ( PersistentWorld->IsPlayInEditor() )
	{
#if WITH_EDITOR
		if (PersistentWorld->GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor))
		{
			PackageFlags |= PKG_PlayInEditor;
		}
		PIEInstanceID = PersistentWorld->GetOutermost()->PIEInstanceID;
#endif
		const FString NonPrefixedLevelName = UWorld::StripPIEPrefixFromPackageName(DesiredPackageName.ToString(), PersistentWorld->StreamingLevelsPrefix);
		UPackage* EditorLevelPackage = FindObjectFast<UPackage>(nullptr, FName(*NonPrefixedLevelName));
		
		bool bShouldDuplicate = EditorLevelPackage && (bBlockOnLoad || EditorLevelPackage->IsDirty() || !GEngine->PreferToStreamLevelsInPIE());
		if (bShouldDuplicate)
		{
			// Do the duplication
			UWorld* PIELevelWorld = UWorld::DuplicateWorldForPIE(NonPrefixedLevelName, PersistentWorld);
			if (PIELevelWorld)
			{
				PIELevelWorld->PersistentLevel->bAlreadyMovedActors = true; // As we have duplicated the world, the actors will already have been transformed
				check(PendingUnloadLevel == NULL);
				SetLoadedLevel(PIELevelWorld->PersistentLevel);

				// Broadcast level loaded event to blueprints
				{
					QUICK_SCOPE_CYCLE_COUNTER(STAT_OnLevelLoaded_Broadcast);
					OnLevelLoaded.Broadcast(this);
				}

				return true;
			}
			else if (PersistentWorld->WorldComposition == NULL) // In world composition streaming levels are not loaded by default
			{
				if ( bAllowLevelLoadRequests )
				{
					UE_LOG(LogLevelStreaming, Log, TEXT("World to duplicate for PIE '%s' not found. Attempting load."), *NonPrefixedLevelName);
				}
				else
				{
					UE_LOG(LogLevelStreaming, Warning, TEXT("Unable to duplicate PIE World: '%s'"), *NonPrefixedLevelName);
				}
			}
		}
	}

	// Async load package if world object couldn't be found and we are allowed to request a load.
	if (bAllowLevelLoadRequests)
	{
		FString PackageNameToLoadFrom = DesiredPackageName.ToString();
		if (DesiredPackageNameToLoad != NAME_None)
		{
			PackageNameToLoadFrom = DesiredPackageNameToLoad.ToString();
		}

		if (GUseSeekFreeLoading)
		{
			// Only load localized package if it exists as async package loading doesn't handle errors gracefully.
			FString LocalizedPackageName = PackageNameToLoadFrom + LOCALIZED_SEEKFREE_SUFFIX;
			FString LocalizedFileName;
			if (FPackageName::DoesPackageExist(LocalizedPackageName, NULL, &LocalizedFileName))
			{
				// Load localized part of level first in case it exists. We don't need to worry about GC or completion 
				// callback as we always kick off another async IO for the level below.
				LoadPackageAsync(*(GetWorldAssetPackageName() + LOCALIZED_SEEKFREE_SUFFIX), nullptr, *LocalizedPackageName, FLoadPackageAsyncDelegate(), PackageFlags, PIEInstanceID);
			}
		}

		if (FPackageName::DoesPackageExist(PackageNameToLoadFrom, NULL, NULL))
		{
			bHasLoadRequestPending = true;
			
			ULevel::StreamedLevelsOwningWorld.Add(DesiredPackageName, PersistentWorld);
			UWorld::WorldTypePreLoadMap.FindOrAdd(DesiredPackageName) = PersistentWorld->WorldType;

			// Kick off async load request.
			STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, *(FString( TEXT( "RequestLevel - " ) + DesiredPackageName.ToString() )) );
			LoadPackageAsync(DesiredPackageName.ToString(), nullptr, *PackageNameToLoadFrom, FLoadPackageAsyncDelegate::CreateUObject(this, &ULevelStreaming::AsyncLevelLoadComplete), PackageFlags, PIEInstanceID);

			// streamingServer: server loads everything?
			// Editor immediately blocks on load and we also block if background level streaming is disabled.
			if (bBlockOnLoad || ShouldBeAlwaysLoaded())
			{
				// Finish all async loading.
				FlushAsyncLoading();
			}
		}
		else
		{
			UE_LOG(LogStreaming, Error,TEXT("Couldn't find file for package %s."), *PackageNameToLoadFrom);
			bFailedToLoad = true;
			return false;
		}
	}

	return true;
}
bool IsAssetDirty(UObject* Asset)
{
	UPackage* Package = Asset ? Asset->GetOutermost() : nullptr;
	return Package ? Package->IsDirty() : false;
}
/** 
 * Runs compile-on-load test against all unloaded, and optionally loaded, blueprints
 * See the TestAllBlueprints config key in the [Automation.Blueprint] config sections
 */
bool FBlueprintCompileOnLoadTest::RunTest(const FString& BlueprintAssetPath)
{
	FCompilerResultsLog Results;

	UBlueprint* ExistingBP = nullptr;
	// if this blueprint was already loaded, then these tests are invalidated 
	// (because dependencies have already been loaded)
	if (FBlueprintAutomationTestUtilities::IsBlueprintLoaded(BlueprintAssetPath, &ExistingBP))
	{
		if (FBlueprintAutomationTestUtilities::IsAssetUnsaved(BlueprintAssetPath))
		{
			AddError(FString::Printf(TEXT("You have unsaved changes made to '%s', please save them before running this test."), *BlueprintAssetPath));
			return false;
		}
		else
		{
			AddWarning(FString::Printf(TEXT("Test may be invalid (the blueprint is already loaded): '%s'"), *BlueprintAssetPath));
			FBlueprintAutomationTestUtilities::UnloadBlueprint(ExistingBP);
		}
	}

	// tracks blueprints that were already loaded (and cleans up any that were 
	// loaded in its lifetime, once it is destroyed)
	FScopedBlueprintUnloader NewBlueprintUnloader(/*bAutoOpenScope =*/true, /*bRunGCOnCloseIn =*/true);

	// We load the blueprint twice and compare the two for discrepancies. This is 
	// to bring dependency load issues to light (among other things). If a blueprint's
	// dependencies are loaded too late, then this first object is the degenerate one.
	UBlueprint* InitialBlueprint = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), NULL, *BlueprintAssetPath));

	// if we failed to load it the first time, then there is no need to make a 
	// second attempt, leave them to fix up this issue first
	if (InitialBlueprint == NULL)
	{
		AddError(*FString::Printf(TEXT("Unable to load blueprint for: '%s'"), *BlueprintAssetPath));
		return false;
	}

	if (!InitialBlueprint->SkeletonGeneratedClass || !InitialBlueprint->GeneratedClass)
	{
		AddError(*FString::Printf(TEXT("Unable to load blueprint for: '%s'. Probably it derives from an invalid class."), *BlueprintAssetPath));
		return false;
	}

	// GATHER SUBOBJECTS
	TArray<TWeakObjectPtr<UObject>> InitialBlueprintSubobjects;
	{
		TArray<UObject*> InitialBlueprintSubobjectsPtr;
		GetObjectsWithOuter(InitialBlueprint, InitialBlueprintSubobjectsPtr);
		for (auto Obj : InitialBlueprintSubobjectsPtr)
		{
			InitialBlueprintSubobjects.Add(Obj);
		}
	}

	// GATHER DEPENDENCIES
	TSet<TWeakObjectPtr<UBlueprint>> BlueprintDependencies;
	{
		TArray<UBlueprint*> DependentBlueprints;
		FBlueprintEditorUtils::GetDependentBlueprints(InitialBlueprint, DependentBlueprints);
		for (auto BP : DependentBlueprints)
		{
			BlueprintDependencies.Add(BP);
		}
	}
	BlueprintDependencies.Add(InitialBlueprint);

	// GATHER DEPENDENCIES PERSISTENT DATA
	struct FReplaceInnerData
	{
		TWeakObjectPtr<UClass> Class;
		FStringAssetReference BlueprintAsset;
	};
	TArray<FReplaceInnerData> ReplaceInnerData;
	for (auto BPToUnloadWP : BlueprintDependencies)
	{
		auto BPToUnload = BPToUnloadWP.Get();
		auto OldClass = BPToUnload ? *BPToUnload->GeneratedClass : NULL;
		if (OldClass)
		{
			FReplaceInnerData Data;
			Data.Class = OldClass;
			Data.BlueprintAsset = FStringAssetReference(BPToUnload);
			ReplaceInnerData.Add(Data);
		}
	}

	// store off data for the initial blueprint so we can unload it (and reconstruct 
	// later to compare it with a second one)
	TArray<uint8> InitialLoadData;
	FObjectWriter(InitialBlueprint, InitialLoadData);

	// grab the name before we unload the blueprint
	FName const BlueprintName = InitialBlueprint->GetFName();
	// unload the blueprint so we can reload it (to catch any differences, now  
	// that all its dependencies should be loaded as well)

	//UNLOAD DEPENDENCIES, all circular dependencies will be loaded again 
	// unload the blueprint so we can reload it (to catch any differences, now  
	// that all its dependencies should be loaded as well)
	for (auto BPToUnloadWP : BlueprintDependencies)
	{
		if (auto BPToUnload = BPToUnloadWP.Get())
		{
			FBlueprintAutomationTestUtilities::UnloadBlueprint(BPToUnload);
		}
	}

	// this blueprint is now dead (will be destroyed next garbage-collection pass)
	UBlueprint* UnloadedBlueprint = InitialBlueprint;
	InitialBlueprint = NULL;

	// load the blueprint a second time; if the two separately loaded blueprints 
	// are different, then this one is most likely the choice one (it has all its 
	// dependencies loaded)

	UBlueprint* ReloadedBlueprint = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), NULL, *BlueprintAssetPath));

	UPackage* TransientPackage = GetTransientPackage();
	FName ReconstructedName = MakeUniqueObjectName(TransientPackage, UBlueprint::StaticClass(), BlueprintName);
	// reconstruct the initial blueprint (using the serialized data from its initial load)
	EObjectFlags const StandardBlueprintFlags = RF_Public | RF_Standalone | RF_Transactional;
	InitialBlueprint = ConstructObject<UBlueprint>(UBlueprint::StaticClass(), TransientPackage, ReconstructedName, StandardBlueprintFlags | RF_Transient);
	FObjectReader(InitialBlueprint, InitialLoadData);
	{
		TMap<UObject*, UObject*> ClassRedirects;
		for (auto& Data : ReplaceInnerData)
		{
			UClass* OriginalClass = Data.Class.Get();
			UBlueprint* NewBlueprint = Cast<UBlueprint>(Data.BlueprintAsset.ResolveObject());
			UClass* NewClass = NewBlueprint ? *NewBlueprint->GeneratedClass : NULL;
			if (OriginalClass && NewClass)
			{
				ClassRedirects.Add(OriginalClass, NewClass);
			}
		}
		// REPLACE OLD DATA
		FArchiveReplaceObjectRef<UObject>(InitialBlueprint, ClassRedirects, /*bNullPrivateRefs=*/false, /*bIgnoreOuterRef=*/true, /*bIgnoreArchetypeRef=*/false);
		for (auto SubobjWP : InitialBlueprintSubobjects)
		{
			if (auto Subobj = SubobjWP.Get())
			{
				FArchiveReplaceObjectRef<UObject>(Subobj, ClassRedirects, /*bNullPrivateRefs=*/false, /*bIgnoreOuterRef=*/true, /*bIgnoreArchetypeRef=*/false);
			}
		}

		UPackage* AssetPackage = ReloadedBlueprint->GetOutermost();
		bool bHasUnsavedChanges = AssetPackage->IsDirty();
		FBlueprintEditorUtils::RefreshAllNodes(ReloadedBlueprint);
		AssetPackage->SetDirtyFlag(bHasUnsavedChanges);
	}

	// look for diffs between subsequent loads and log them as errors
	TArray<FDiffSingleResult> BlueprintDiffs;
	bool bDiffsFound = FBlueprintAutomationTestUtilities::DiffBlueprints(InitialBlueprint, ReloadedBlueprint, BlueprintDiffs);
	if (bDiffsFound)
	{
		FBlueprintAutomationTestUtilities::ResolveCircularDependencyDiffs(ReloadedBlueprint, BlueprintDiffs);
		// if there are still diffs after resolving any the could have been from unloaded circular dependencies
		if (BlueprintDiffs.Num() > 0)
		{
			AddError(FString::Printf(TEXT("Inconsistencies between subsequent blueprint loads for: '%s' (was a dependency not preloaded?)"), *BlueprintAssetPath));
		}
		else 
		{
			bDiffsFound = false;
		}
		
		// list all the differences (so as to help identify what dependency was missing)
		for (auto DiffIt(BlueprintDiffs.CreateIterator()); DiffIt; ++DiffIt)
		{
			// will be presented in the context of "what changed between the initial load and the second?"
			FString DiffDescription = DiffIt->ToolTip;
			if (DiffDescription != DiffIt->DisplayString)
			{
				DiffDescription = FString::Printf(TEXT("%s (%s)"), *DiffDescription, *DiffIt->DisplayString);
			}

			const UEdGraphNode* NodeFromPin = DiffIt->Pin1 ? Cast<const UEdGraphNode>(DiffIt->Pin1->GetOuter()) : NULL;
			const UEdGraphNode* Node = DiffIt->Node1 ? DiffIt->Node1 : NodeFromPin;
			const UEdGraph* Graph = Node ? Node->GetGraph() : NULL;
			const FString GraphName = Graph ? Graph->GetName() : FString(TEXT("Unknown Graph"));
			AddError(FString::Printf(TEXT("%s.%s differs between subsequent loads: %s"), *BlueprintName.ToString(), *GraphName, *DiffDescription));
		}
	}

	// At the close of this function, the FScopedBlueprintUnloader should prep 
	// for following tests by unloading any blueprint dependencies that were 
	// loaded for this one (should catch InitialBlueprint and ReloadedBlueprint) 
	// 
	// The FScopedBlueprintUnloader should also run garbage-collection after,
	// in hopes that the imports for this blueprint get destroyed so that they 
	// don't invalidate other tests that share the same dependencies
	return !bDiffsFound;
}