Files
UnrealEngineUWP/Engine/Source/Developer/AssetTools/Private/AssetRenameManager.cpp
Robert Manuszewski 18e2561ceb Copying //UE4/Dev-Core to //UE4/Main
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2836261 on 2016/01/20 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Flush FAsyncPackage cache after pre-load to reduce peak memory usage when async loading (5.5-10x).

Change 2828630 on 2016/01/14 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Adding debug code to catch memory stomps in the async loading buffers that's independent from mallocstomp allocator. Changed the signature of PageProtect functions to be able to read-only protect memory.

Change 2816129 on 2016/01/05 by Steve.Robb@Dev-Core

	Fixes for Realloc and alignment logic which caused redundant reallocations and incorrect binning.

Change 2821054 on 2016/01/08 by Steve.Robb@Dev-Core

	Further Realloc savings when realigning within a block.

Change 2806820 on 2015/12/17 by Steve.Robb@Dev-Core

	New AlignDown function, like Align, but which rounds a value/pointer down to the next alignment instead of up.

Change 2806816 on 2015/12/17 by Steve.Robb@Dev-Core

	Sort UHT modules by type to improve iteration times in conjunction with makefiles.

Change 2823235 on 2016/01/11 by Steve.Robb@Dev-Core

	UHT error messages about missing GENERATED_BODY() macros updated to represent intended use.

Change 2806815 on 2015/12/17 by Steve.Robb@Dev-Core

	Module types split into Game and Engine runtime versions.

Change 2833809 on 2016/01/19 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	StaticLoadObject will now respect LOAD_NoRedirects flag.

Change 2811194 on 2015/12/22 by Bob.Tellez@Z2434_DevCore

	#UE4 Prevent loading packages that have a newer LegacyFileVersion since serialization for FCustomVersion is not forward compatible. UE-24443

Change 2806818 on 2015/12/17 by Steve.Robb@Dev-Core

	Removal of stats from MallocBinned2, to be readded later.

Change 2807069 on 2015/12/17 by Steve.Robb@Dev-Core

	Clarification of some bucket hashing terminology.

Change 2815117 on 2016/01/04 by Steve.Robb@Dev-Core

	Fix for a missing root build path on game modules.

Change 2815673 on 2016/01/05 by Steve.Robb@Dev-Core

	Move FMalloc verification into a proxy object.

Change 2822873 on 2016/01/11 by Steve.Robb@Dev-Core

	Fixes to off-by-one errors and removal of BinnedSizeLimit (assumed to be the same as MAX_POOLED_ALLOCATION_SIZE after OBO fix).

Change 2822923 on 2016/01/11 by Steve.Robb@Dev-Core

	Simplification of MemSizeToPoolTable indexing.

Change 2824974 on 2016/01/12 by Steve.Robb@Dev-Core

	Assert fixed.
	AllocateBlockFromPool's return value made debuggable.

Change 2825241 on 2016/01/12 by Steve.Robb@Dev-Core

	UHT now returns an error code on a warning when -warningsaserrors is specified.

Change 2825291 on 2016/01/12 by Steve.Robb@Dev-Core

	WarningsAsErrors enabled on UHT, after disabling the hardcoded behavior in CL# 2825241.

Change 2829846 on 2016/01/15 by Steve.Robb@Dev-Core

	GitHub #1938 - wrong Max value of enum is used during net serialization

Change 2829914 on 2016/01/15 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Reduce the amount of memory allocated for async cache buffers when guarding against memory stomps.

Change 2829988 on 2016/01/15 by Steve.Robb@Dev-Core

	Generalized large pool allocations.
	More redundancy removed.

Change 2831935 on 2016/01/18 by Chris.Wood@Chris.Wood.StreamB

	Added UserActivity property to crash description in CRP and CR website.
	[OR-12043] - Phone Home where crashes occur - pass context info to Crash Reporter

	DB column added to db-09 by ColinR matching this change.
	Published to server on Jan 18th 2016

Change 2834003 on 2016/01/19 by Chris.Wood@Chris.Wood.StreamB

	Added Linux to normal callstack parsing code on CR website
	[UE-25527] - Linux CrashReporter is missing information

	Published to server on Jan 19th 2016

Change 2835466 on 2016/01/20 by Joe.Conley@Joe.Conley_EGJWD5708_Dev-Core-Minimal

	Fix issue for cancelling package loads when there are still packages queued.  Call their PackageLoadedDelegate with a "Cancelled" result.

	Should solve remaining issue with UE-24062 - "Calling CancelAsyncLoading triggers an assert in FAsyncPackage::DetachLinker()"
	- (ULevelStreaming::AsyncLevelLoadComplete was not being called if packages were still queued when cancel was issued)

Change 2836803 on 2016/01/20 by Chris.Wood@Chris.Wood.StreamB

	CrashReportWebsite - fix exception thrown when parsing certain callstack formats

Change 2837952 on 2016/01/21 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

	Changing FAsyncIORequest to be stored as reference when cancelling IO requests to improve performance.

Change 2838289 on 2016/01/21 by Robert.Manuszewski@Robert.Manuszewski_NCL_Stream1

[CL 2845588 by Robert Manuszewski in Main branch]
2016-01-27 12:09:53 -05:00

764 lines
24 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "AssetToolsPrivatePCH.h"
#include "AssetRegistryModule.h"
#include "AssetToolsModule.h"
#include "CollectionManagerModule.h"
#include "ISourceControlModule.h"
#include "FileHelpers.h"
#include "ObjectTools.h"
#include "MainFrame.h"
#include "Kismet2/KismetEditorUtilities.h"
#define LOCTEXT_NAMESPACE "AssetRenameManager"
struct FAssetRenameDataWithReferencers : public FAssetRenameData
{
TArray<FName> ReferencingPackageNames;
FText FailureReason;
bool bCreateRedirector;
bool bRenameFailed;
FAssetRenameDataWithReferencers(const FAssetRenameData& InRenameData)
: FAssetRenameData(InRenameData)
, bCreateRedirector(false)
, bRenameFailed(false)
{}
};
class SRenameFailures : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SRenameFailures){}
SLATE_ARGUMENT(TArray<FText>, FailedRenames)
SLATE_END_ARGS()
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void Construct( const FArguments& InArgs )
{
for ( auto RenameIt = InArgs._FailedRenames.CreateConstIterator(); RenameIt; ++RenameIt )
{
FailedRenames.Add( MakeShareable( new FText(*RenameIt) ) );
}
ChildSlot
[
SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("Docking.Tab.ContentAreaBrush") )
.Padding(FMargin(4, 8, 4, 4))
[
SNew(SVerticalBox)
// Title text
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock) .Text( LOCTEXT("RenameFailureTitle", "The following assets could not be renamed.") )
]
// Failure list
+SVerticalBox::Slot()
.Padding(0, 8)
.FillHeight(1.f)
[
SNew(SBorder)
.BorderImage( FEditorStyle::GetBrush("ToolPanel.GroupBorder") )
[
SNew(SListView<TSharedRef<FText>>)
.ListItemsSource(&FailedRenames)
.SelectionMode(ESelectionMode::None)
.OnGenerateRow(this, &SRenameFailures::MakeListViewWidget)
]
]
// Close button
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 4)
.HAlign(HAlign_Right)
[
SNew(SButton)
.OnClicked(this, &SRenameFailures::CloseClicked)
.Text(LOCTEXT("RenameFailuresCloseButton", "Close"))
]
]
];
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
static void OpenRenameFailuresDialog(const TArray<FText>& InFailedRenames)
{
TSharedRef<SWindow> RenameWindow = SNew(SWindow)
.Title(LOCTEXT("FailedRenamesDialog", "Failed Renames"))
.ClientSize(FVector2D(800,400))
.SupportsMaximize(false)
.SupportsMinimize(false)
[
SNew(SRenameFailures).FailedRenames(InFailedRenames)
];
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
if ( MainFrameModule.GetParentWindow().IsValid() )
{
FSlateApplication::Get().AddWindowAsNativeChild(RenameWindow, MainFrameModule.GetParentWindow().ToSharedRef());
}
else
{
FSlateApplication::Get().AddWindow(RenameWindow);
}
}
private:
TSharedRef<ITableRow> MakeListViewWidget(TSharedRef<FText> Item, const TSharedRef<STableViewBase>& OwnerTable)
{
return
SNew(STableRow< TSharedRef<FText> >, OwnerTable)
[
SNew(STextBlock) .Text( Item.Get() )
];
}
FReply CloseClicked()
{
FWidgetPath WidgetPath;
TSharedPtr<SWindow> Window = FSlateApplication::Get().FindWidgetWindow(AsShared(), WidgetPath);
if ( Window.IsValid() )
{
Window->RequestDestroyWindow();
}
return FReply::Handled();
}
private:
TArray< TSharedRef<FText> > FailedRenames;
};
///////////////////////////
// FAssetRenameManager
///////////////////////////
void FAssetRenameManager::RenameAssets(const TArray<FAssetRenameData>& AssetsAndNames) const
{
// If the asset registry is still loading assets, we cant check for referencers, so we must open the rename dialog
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
if ( AssetRegistryModule.Get().IsLoadingAssets() )
{
// Open a dialog asking the user to wait while assets are being discovered
SDiscoveringAssetsDialog::OpenDiscoveringAssetsDialog(
SDiscoveringAssetsDialog::FOnAssetsDiscovered::CreateSP(this, &FAssetRenameManager::FixReferencesAndRename, AssetsAndNames)
);
}
else
{
// No need to wait, attempt to fix references and rename now.
FixReferencesAndRename(AssetsAndNames);
}
}
void FAssetRenameManager::FixReferencesAndRename(TArray<FAssetRenameData> AssetsAndNames) const
{
// Prep a list of assets to rename with an extra boolean to determine if they should leave a redirector or not
TArray<FAssetRenameDataWithReferencers> AssetsToRename;
AssetsToRename.Reset(AssetsAndNames.Num());
for (const FAssetRenameData& AssetRenameData : AssetsAndNames)
{
AssetsToRename.Emplace(FAssetRenameDataWithReferencers(AssetRenameData));
}
// Warn the user if they are about to rename an asset that is referenced by a CDO
auto CDOAssets = FindCDOReferencedAssets(AssetsToRename);
// Warn the user if there were any references
if (CDOAssets.Num())
{
FString AssetNames;
for (auto AssetIt = CDOAssets.CreateConstIterator(); AssetIt; ++AssetIt)
{
UObject* Asset = (*AssetIt).Get();
if (Asset)
{
AssetNames += FString("\n") + Asset->GetName();
}
}
const FText MessageText = FText::Format(LOCTEXT("RenameCDOReferences", "The following assets are referenced by one or more Class Default Objects: \n{0}\n\nContinuing with the rename may require code changes to fix these references. Do you wish to continue?"), FText::FromString(AssetNames) );
if (FMessageDialog::Open(EAppMsgType::YesNo, MessageText) == EAppReturnType::No)
{
return;
}
}
// Fill out the referencers for the assets we are renaming
PopulateAssetReferencers(AssetsToRename);
// Update the source control state for the packages containing the assets we are renaming if source control is enabled. If source control is enabled and this fails we can not continue.
if ( UpdatePackageStatus(AssetsToRename) )
{
// Detect whether the assets are being referenced by a collection. Assets within a collection must leave a redirector to avoid the collection losing its references.
DetectReferencingCollections(AssetsToRename);
// Load all referencing packages and mark any assets that must have redirectors.
TArray<UPackage*> ReferencingPackagesToSave;
LoadReferencingPackages(AssetsToRename, ReferencingPackagesToSave);
// Prompt to check out source package and all referencing packages, leave redirectors for assets referenced by packages that are not checked out and remove those packages from the save list.
const bool bUserAcceptedCheckout = CheckOutPackages(AssetsToRename, ReferencingPackagesToSave);
if ( bUserAcceptedCheckout )
{
// If any referencing packages are left read-only, the checkout failed or SCC was not enabled. Trim them from the save list and leave redirectors.
DetectReadOnlyPackages(AssetsToRename, ReferencingPackagesToSave);
// Perform the rename, leaving redirectors only for assets which need them
PerformAssetRename(AssetsToRename);
// Save all packages that were referencing any of the assets that were moved without redirectors
SaveReferencingPackages(ReferencingPackagesToSave);
// Issue post rename event
AssetPostRenameEvent.Broadcast(AssetsAndNames);
}
}
// Finally, report any failures that happened during the rename
ReportFailures(AssetsToRename);
}
TArray<TWeakObjectPtr<UObject>> FAssetRenameManager::FindCDOReferencedAssets(const TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const
{
TArray<TWeakObjectPtr<UObject>> CDOAssets, LocalAssetsToRename;
for (auto AssetIt = AssetsToRename.CreateConstIterator(); AssetIt; ++AssetIt)
{
LocalAssetsToRename.Push((*AssetIt).Asset);
}
// Run over all CDOs and check for any references to the assets
for ( TObjectIterator<UClass> ClassDefaultObjectIt; ClassDefaultObjectIt; ++ClassDefaultObjectIt)
{
UClass* Cls = (*ClassDefaultObjectIt);
UObject* CDO = Cls->ClassDefaultObject;
if (!CDO || !CDO->HasAllFlags(RF_ClassDefaultObject) || Cls->ClassGeneratedBy != NULL)
{
continue;
}
// Ignore deprecated and temporary trash classes.
if (Cls->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists) || FKismetEditorUtilities::IsClassABlueprintSkeleton(Cls))
{
continue;
}
for (TFieldIterator<UObjectProperty> PropertyIt(Cls); PropertyIt; ++PropertyIt)
{
const UObject* Object = PropertyIt->GetPropertyValue(PropertyIt->ContainerPtrToValuePtr<UObject>(CDO));
for (auto AssetIt = LocalAssetsToRename.CreateConstIterator(); AssetIt; ++AssetIt)
{
auto Asset = *AssetIt;
if (Object == Asset.Get())
{
CDOAssets.Push(Asset);
LocalAssetsToRename.Remove(Asset);
if (LocalAssetsToRename.Num() == 0)
{
// No more assets to check
return MoveTemp(CDOAssets);
}
else
{
break;
}
}
}
}
}
return MoveTemp(CDOAssets);
}
void FAssetRenameManager::PopulateAssetReferencers(TArray<FAssetRenameDataWithReferencers>& AssetsToPopulate) const
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
TSet<FName> RenamingAssetPackageNames;
// Get the names of all the packages containing the assets we are renaming so they arent added to the referencing packages list
for ( auto AssetIt = AssetsToPopulate.CreateConstIterator(); AssetIt; ++AssetIt )
{
UObject* Asset = (*AssetIt).Asset.Get();
if ( Asset )
{
RenamingAssetPackageNames.Add( FName(*Asset->GetOutermost()->GetName()) );
}
}
// Gather all referencing packages for all assets that are being renamed
for ( auto AssetIt = AssetsToPopulate.CreateIterator(); AssetIt; ++AssetIt )
{
(*AssetIt).ReferencingPackageNames.Empty();
UObject* Asset = (*AssetIt).Asset.Get();
if ( Asset )
{
TArray<FName> Referencers;
AssetRegistryModule.Get().GetReferencers(Asset->GetOutermost()->GetFName(), Referencers);
for ( auto ReferenceIt = Referencers.CreateConstIterator(); ReferenceIt; ++ReferenceIt )
{
const FName& ReferencingPackageName = *ReferenceIt;
if ( !RenamingAssetPackageNames.Contains(ReferencingPackageName) )
{
(*AssetIt).ReferencingPackageNames.AddUnique( ReferencingPackageName );
}
}
}
}
}
bool FAssetRenameManager::UpdatePackageStatus(const TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const
{
if ( ISourceControlModule::Get().IsEnabled() )
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
// Update the source control server availability to make sure we can do the rename operation
SourceControlProvider.Login();
if ( !SourceControlProvider.IsAvailable() )
{
FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "SourceControl_ServerUnresponsive", "Source Control is unresponsive. Please check your connection and try again.") );
return false;
}
// Gather asset package names to update SCC states in a single SCC request
TArray<UPackage*> PackagesToUpdate;
for ( auto AssetIt = AssetsToRename.CreateConstIterator(); AssetIt; ++AssetIt )
{
UObject* Asset = (*AssetIt).Asset.Get();
if ( Asset )
{
PackagesToUpdate.AddUnique(Asset->GetOutermost());
}
}
SourceControlProvider.Execute(ISourceControlOperation::Create<FUpdateStatus>(), PackagesToUpdate);
}
return true;
}
void FAssetRenameManager::LoadReferencingPackages(TArray<FAssetRenameDataWithReferencers>& AssetsToRename, TArray<UPackage*>& OutReferencingPackagesToSave) const
{
const FText ReferenceUpdateSlowTask = LOCTEXT("ReferenceUpdateSlowTask", "Updating Asset References");
GWarn->BeginSlowTask( ReferenceUpdateSlowTask, true );
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
for ( int32 AssetIdx = 0; AssetIdx < AssetsToRename.Num(); ++AssetIdx )
{
GWarn->StatusUpdate( AssetIdx, AssetsToRename.Num(), ReferenceUpdateSlowTask );
FAssetRenameDataWithReferencers& RenameData = AssetsToRename[AssetIdx];
UObject* Asset = RenameData.Asset.Get();
if ( Asset )
{
// Make sure this asset is local. Only local assets should be renamed without a redirector
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(Asset->GetOutermost(), EStateCacheUsage::ForceUpdate);
const bool bLocalFile = !SourceControlState.IsValid() || SourceControlState->IsAdded() || !SourceControlState->IsSourceControlled() || SourceControlState->IsIgnored();
if ( !bLocalFile )
{
// This asset is not local. It is not safe to rename it without leaving a redirector
RenameData.bCreateRedirector = true;
continue;
}
}
else
{
// The asset for this rename must have been GCed or is otherwise invalid. Skip it
continue;
}
TArray<UPackage*> PackagesToSaveForThisAsset;
bool bAllPackagesLoadedForThisAsset = true;
for ( auto PackageNameIt = RenameData.ReferencingPackageNames.CreateConstIterator(); PackageNameIt; ++PackageNameIt )
{
const FString PackageName = (*PackageNameIt).ToString();
// Check if the package is a map before loading it!
if ( FEditorFileUtils::IsMapPackageAsset(PackageName) )
{
// This reference was a map package, don't load it and leave a redirector for this asset
RenameData.bCreateRedirector = true;
bAllPackagesLoadedForThisAsset = false;
break;
}
UPackage* Package = LoadPackage(NULL, *PackageName, LOAD_None);
if ( Package )
{
PackagesToSaveForThisAsset.Add(Package);
}
else
{
RenameData.bCreateRedirector = true;
bAllPackagesLoadedForThisAsset = false;
break;
}
}
if ( bAllPackagesLoadedForThisAsset )
{
OutReferencingPackagesToSave.Append(PackagesToSaveForThisAsset);
}
}
GWarn->EndSlowTask();
}
bool FAssetRenameManager::CheckOutPackages(TArray<FAssetRenameDataWithReferencers>& AssetsToRename, TArray<UPackage*>& InOutReferencingPackagesToSave) const
{
bool bUserAcceptedCheckout = true;
// Build list of packages to check out: the source package and any referencing packages (in the case that we do not create a redirector)
TArray<UPackage*> PackagesToCheckOut;
PackagesToCheckOut.Reset(AssetsToRename.Num() + InOutReferencingPackagesToSave.Num());
for (const auto& AssetToRename : AssetsToRename)
{
if (AssetToRename.Asset.IsValid())
{
PackagesToCheckOut.Add(AssetToRename.Asset->GetOutermost());
}
}
for (UPackage* ReferencingPackage : InOutReferencingPackagesToSave)
{
PackagesToCheckOut.Add(ReferencingPackage);
}
// Check out the packages
if (PackagesToCheckOut.Num() > 0)
{
TArray<UPackage*> PackagesCheckedOutOrMadeWritable;
TArray<UPackage*> PackagesNotNeedingCheckout;
bUserAcceptedCheckout = FEditorFileUtils::PromptToCheckoutPackages( false, PackagesToCheckOut, &PackagesCheckedOutOrMadeWritable, &PackagesNotNeedingCheckout );
if ( bUserAcceptedCheckout )
{
// Make a list of any packages in the list which weren't checked out for some reason
TArray<UPackage*> PackagesThatCouldNotBeCheckedOut = PackagesToCheckOut;
for ( auto PackageIt = PackagesCheckedOutOrMadeWritable.CreateConstIterator(); PackageIt; ++PackageIt )
{
PackagesThatCouldNotBeCheckedOut.Remove(*PackageIt);
}
for ( auto PackageIt = PackagesNotNeedingCheckout.CreateConstIterator(); PackageIt; ++PackageIt )
{
PackagesThatCouldNotBeCheckedOut.Remove(*PackageIt);
}
// If there's anything which couldn't be checked out, abort the operation.
if (PackagesThatCouldNotBeCheckedOut.Num() > 0)
{
bUserAcceptedCheckout = false;
}
}
}
return bUserAcceptedCheckout;
}
void FAssetRenameManager::DetectReferencingCollections(TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const
{
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
for (auto& AssetToRename : AssetsToRename)
{
if (AssetToRename.Asset.IsValid())
{
TArray<FCollectionNameType> ReferencingCollections;
CollectionManagerModule.Get().GetCollectionsContainingObject(*AssetToRename.Asset->GetPathName(), ReferencingCollections);
if (ReferencingCollections.Num() > 0)
{
AssetToRename.bCreateRedirector = true;
}
}
}
}
void FAssetRenameManager::DetectReadOnlyPackages(TArray<FAssetRenameDataWithReferencers>& AssetsToRename, TArray<UPackage*>& InOutReferencingPackagesToSave) const
{
// For each valid package...
for ( int32 PackageIdx = InOutReferencingPackagesToSave.Num() - 1; PackageIdx >= 0; --PackageIdx )
{
UPackage* Package = InOutReferencingPackagesToSave[PackageIdx];
if ( Package )
{
// Find the package filename
FString Filename;
if ( FPackageName::DoesPackageExist(Package->GetName(), NULL, &Filename) )
{
// If the file is read only
if ( IFileManager::Get().IsReadOnly(*Filename) )
{
FName PackageName = Package->GetFName();
// Find all assets that were referenced by this package to create a redirector when named
for ( auto RenameDataIt = AssetsToRename.CreateIterator(); RenameDataIt; ++RenameDataIt )
{
FAssetRenameDataWithReferencers& RenameData = *RenameDataIt;
if ( RenameData.ReferencingPackageNames.Contains(PackageName) )
{
RenameData.bCreateRedirector = true;
}
}
// Remove the package from the save list
InOutReferencingPackagesToSave.RemoveAt(PackageIdx);
}
}
}
}
}
/**
* Function that renames all FStringAssetReference object with the old asset path to the new one.
*
* @param PackagesToCheck Packages to check for referencing FStringAssetReference.
* @param OldAssetPath Old path.
* @param NewAssetPath New path.
*/
void FAssetRenameManager::RenameReferencingStringAssetReferences(const TArray<UPackage *> PackagesToCheck, const FString& OldAssetPath, const FString& NewAssetPath)
{
struct FStringAssetReferenceRenameSerializer : public FArchiveUObject
{
FStringAssetReferenceRenameSerializer(const FString& InOldAssetPath, const FString& InNewAssetPath)
: OldAssetPath(InOldAssetPath), NewAssetPath(InNewAssetPath)
{
// Mark it as saving to correctly process all references
ArIsSaving = true;
}
FArchive& operator<<(FStringAssetReference& Reference) override
{
if (Reference.ToString() == OldAssetPath)
{
Reference.SetPath(NewAssetPath);
}
// Generated class path support.
if (Reference.ToString() == OldAssetPath + "_C")
{
Reference.SetPath(NewAssetPath + "_C");
}
return *this;
}
private:
const FString& OldAssetPath;
const FString& NewAssetPath;
};
FStringAssetReferenceRenameSerializer RenameSerializer(OldAssetPath, NewAssetPath);
for (auto* Package : PackagesToCheck)
{
TArray<UObject*> ObjectsInPackage;
GetObjectsWithOuter(Package, ObjectsInPackage);
for (auto* Object : ObjectsInPackage)
{
Object->Serialize(RenameSerializer);
}
}
}
void FAssetRenameManager::PerformAssetRename(TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const
{
const FText AssetRenameSlowTask = LOCTEXT("AssetRenameSlowTask", "Renaming Assets");
GWarn->BeginSlowTask( AssetRenameSlowTask, true );
/**
* We need to collect and check those cause dependency graph is only
* representing on-disk state and we want to support rename for in-memory
* objects. It is only needed for string references as in memory references
* for other objects are pointers, so renames doesn't apply to those.
*/
TArray<UPackage *> DirtyPackagesToCheckForStringReferences;
FEditorFileUtils::GetDirtyWorldPackages(DirtyPackagesToCheckForStringReferences);
FEditorFileUtils::GetDirtyContentPackages(DirtyPackagesToCheckForStringReferences);
TArray<UPackage*> PackagesToSave;
TArray<UPackage*> PotentialPackagesToDelete;
for ( int32 AssetIdx = 0; AssetIdx < AssetsToRename.Num(); ++AssetIdx )
{
GWarn->StatusUpdate( AssetIdx, AssetsToRename.Num(), AssetRenameSlowTask );
FAssetRenameDataWithReferencers& RenameData = AssetsToRename[AssetIdx];
if ( RenameData.bRenameFailed )
{
// The rename failed at some earlier step, skip this asset
continue;
}
UObject* Asset = RenameData.Asset.Get();
if ( !Asset )
{
// This asset was invalid or GCed before the rename could occur
RenameData.bRenameFailed = true;
continue;
}
FString OldAssetPath = Asset->GetPathName();
ObjectTools::FPackageGroupName PGN;
PGN.ObjectName = RenameData.NewName;
PGN.GroupName = TEXT("");
PGN.PackageName = RenameData.PackagePath / PGN.ObjectName;
const bool bLeaveRedirector = RenameData.bCreateRedirector;
UPackage* OldPackage = Asset->GetOutermost();
bool bOldPackageAddedToRootSet = false;
if ( !bLeaveRedirector && !OldPackage->IsRooted() )
{
bOldPackageAddedToRootSet = true;
OldPackage->AddToRoot();
}
TSet<UPackage*> ObjectsUserRefusedToFullyLoad;
FText ErrorMessage;
if ( ObjectTools::RenameSingleObject(Asset, PGN, ObjectsUserRefusedToFullyLoad, ErrorMessage, NULL, bLeaveRedirector) )
{
PackagesToSave.AddUnique( Asset->GetOutermost() );
// Automatically save renamed assets
if ( bLeaveRedirector )
{
PackagesToSave.AddUnique( OldPackage );
}
else if ( bOldPackageAddedToRootSet )
{
// Since we did not leave a redirector and the old package wasnt already rooted, attempt to delete it when we are done.
PotentialPackagesToDelete.AddUnique(OldPackage);
}
}
else
{
// No need to keep the old package rooted, the asset was never renamed out of it
if ( bOldPackageAddedToRootSet )
{
OldPackage->RemoveFromRoot();
}
// Mark the rename as a failure to report it later
RenameData.bRenameFailed = true;
RenameData.FailureReason = ErrorMessage;
}
TArray<UPackage *> PackagesToCheck(DirtyPackagesToCheckForStringReferences);
for (auto PackageNameIt = RenameData.ReferencingPackageNames.CreateConstIterator(); PackageNameIt; ++PackageNameIt)
{
UPackage* PackageToCheck = FindPackage(NULL, *PackageNameIt->ToString());
if (PackageToCheck)
{
PackagesToCheck.Add(PackageToCheck);
}
}
RenameReferencingStringAssetReferences(PackagesToCheck, OldAssetPath, Asset->GetPathName());
}
GWarn->EndSlowTask();
// Save all renamed assets and any redirectors that were left behind
if ( PackagesToSave.Num() > 0 )
{
const bool bCheckDirty = false;
const bool bPromptToSave = false;
const bool bAlreadyCheckedOut = true;
FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave, nullptr, bAlreadyCheckedOut);
ISourceControlModule::Get().QueueStatusUpdate(PackagesToSave);
}
// Now branch the files in source control if possible
for (const auto& AssetToRename : AssetsToRename)
{
// If something went wrong when saving and the new asset does not exist on disk, don't branch it
// as it will just create a copy and any attempt to load it will result in crashes.
if (FPackageName::DoesPackageExist(AssetToRename.Asset->GetOutermost()->GetFName().ToString()))
{
SourceControlHelpers::BranchPackage(AssetToRename.Asset->GetOutermost(), FindPackage(nullptr, *AssetToRename.OriginalAssetPath));
}
}
// Clean up all packages that were left empty
if ( PotentialPackagesToDelete.Num() > 0 )
{
for ( auto PackageIt = PotentialPackagesToDelete.CreateConstIterator(); PackageIt; ++PackageIt )
{
(*PackageIt)->RemoveFromRoot();
}
ObjectTools::CleanupAfterSuccessfulDelete(PotentialPackagesToDelete);
}
}
void FAssetRenameManager::SaveReferencingPackages(const TArray<UPackage*>& ReferencingPackagesToSave) const
{
if ( ReferencingPackagesToSave.Num() > 0 )
{
const bool bCheckDirty = false;
const bool bPromptToSave = false;
FEditorFileUtils::PromptForCheckoutAndSave(ReferencingPackagesToSave, bCheckDirty, bPromptToSave);
ISourceControlModule::Get().QueueStatusUpdate(ReferencingPackagesToSave);
}
}
void FAssetRenameManager::ReportFailures(const TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const
{
TArray<FText> FailedRenames;
for ( auto RenameIt = AssetsToRename.CreateConstIterator(); RenameIt; ++RenameIt )
{
const FAssetRenameDataWithReferencers& RenameData = *RenameIt;
if ( RenameData.bRenameFailed )
{
UObject* Asset = RenameData.Asset.Get();
if ( Asset )
{
FFormatNamedArguments Args;
Args.Add(TEXT("FailureReason"), RenameData.FailureReason);
Args.Add(TEXT("AssetName"), FText::FromString(Asset->GetOutermost()->GetName()));
FailedRenames.Add(FText::Format(LOCTEXT("AssetRenameFailure", "{AssetName} - {FailureReason}"), Args));
}
else
{
FailedRenames.Add(LOCTEXT("InvalidAssetText", "Invalid Asset"));
}
}
}
if ( FailedRenames.Num() > 0 )
{
SRenameFailures::OpenRenameFailuresDialog(FailedRenames);
}
}
#undef LOCTEXT_NAMESPACE