Files
UnrealEngineUWP/Engine/Source/Developer/AssetTools/Private/AssetRenameManager.cpp
Marc Audy 1d07b2102d Copying //UE4/Dev-Framework to //UE4/Dev-Main (Source: //UE4/Dev-Framework @ 3779049)
#rb none
#lockdown Nick.Penwarden

============================
  MAJOR FEATURES & CHANGES
============================

Change 3626305 by Phillip.Kavan

	#jira UE-49269 - Workaround fix for crash after packaging a nativized QAGame build with all AnimBP assets disabled for nativization by default.

Change 3627162 by Phillip.Kavan

	#jira UE-49239 - Fix an invalid cast emitted to nativized codegen for converted AnimBP types.

	- Regression introduced in CL# 3613358.

Change 3756887 by Ben.Zeigler

	#jira UE-52380 Fix inconsistency with how FSoftObjectPtr case is managed between FLinkerSave and FArchiveSaveTagImports, which would cause a cook ensure under some circumstances
	Copy of CL #3756788

Change 3756888 by Ben.Zeigler

	#jira UE-45505 Fix issue where FCoreUObjectDelegates::OnAssetLoaded was being called from an inner loop inside EndLoad. Maps would register components from that callback, and if those registers started their own loads, those objects would be returned in a partially loaded state. We now defer the asset loaded callback to the very end of the loop so recursive loads work properly
	Copy of CL #3753986
	#thomas.sarkanen

Change 3759254 by Ben.Zeigler

	Disable the duplicate PrimaryAssetId for editor only types like Maps. This can happen if content folk copy maps using the content browser, and does not actually cause a runtime problem. It still ensures for cooked types

Change 3759278 by Ben.Zeigler

	Add IsTempPackage to FPackageName
	Fix issue where temp/memory packages shown in a content browser/asset audit window would spam the log when it failed to find source control info for them

Change 3759613 by Phillip.Kavan

	Add support for casting between mismatched soft pointer types in nativized Blueprint C++ assignment statements and function calls.

	Change summary:
	- Extended FEmitHelper::GenerateAutomaticCast() to consider soft pointer terms and inject C++ code to explicitly cast the RHS when needed.

	#jira UE-52205

Change 3760040 by Dan.Oconnor

	Add Call Stack control for viewing Blueprint call stacks when paused at a breakpoint, available from the Developer Tools menu

	#jira UE-2296

Change 3760955 by Phillip.Kavan

	Fix conditional (SA/CIS issue).

Change 3761356 by Ben.Zeigler

	Fix DLC staging rules to handle metadata correctly and remove debug log I accidentally added. The DLC staging now iterates in a similar way to the normal staging, it just may also excluded Engine

Change 3761859 by Zak.Middleton

	#ue4 - Fix crash in UStaticMesh::GetAssetRegistryTags() when FindObject is used during saving. Added Lex::ToString for physics enums ECollisionTraceFlag, EPhysicsType, and EBodyCollisionResponse.

	#jira UE-52478
	#tests QA game, content browser

Change 3761860 by mason.seay

	Submitting test content for Async Load issue

Change 3762559 by Ben.Zeigler

	#jira UE-52407 Fix it so FText can be specified in blueprint functions as default parameters. The UI showed up before but the data was lost
	Change GetDefaultsAsString on Pin to always return an internal string so it can correctly be import texted, add GetDefaultsAsText for display purposes

Change 3764459 by Marc.Audy

	PR #4224: Fix LoadLevelInstanceBySoftObjectPtr (Contributed by phlknght)
	#jira UE-52415

Change 3764580 by Ben.Zeigler

	Clean up delegates in UObjectGlobals.h, fixing several incorrect comments and moving some editor delegates into WITH_EDITOR

Change 3764602 by Ben.Zeigler

	#jira UE-52487 Fix it so OnAssetLoaded gets correctly called for Assets that were async loaded while in the editor/standalone editor game.
	This is necessary because they would not get registered with various editor systems for the rest of the editor session, even if opened manually

Change 3764603 by Ben.Zeigler

	Related to UE-52487, now that async loading blueprints in the editor properly registers them with the blueprint actions, we need to unregister them when automated tests want them to unload. Add a ClearEditorReferences function to UBlueprint that calls the OnUnloaded editor delegate, so EngineTest doesn't need to include the editor module

Change 3764768 by Ben.Zeigler

	#jira UE-52524 Fix null access crash when pasting an invalid macro instance node

Change 3766415 by Fred.Kimberley

	Removing invalid assets. Most of these are out  of date.

Change 3766417 by Fred.Kimberley

	Add warnings when we try to export a package without a type.

Change 3766514 by Fred.Kimberley

	Added a #include to fix the build.

Change 3766542 by Fred.Kimberley

	Added a #include to fix the build.

Change 3766558 by Fred.Kimberley

	Rename variables to avoid warnings about hiding previous variable declarations.

Change 3767619 by Marc.Audy

	bActorIsBeingDestroyed must be part of transaction history
	#jira UE-51796

Change 3767993 by Dan.Oconnor

	Preserve graph editor controls when clicking on a hyper link, this speeds up navigation via the debugger  'step' command and Find in Blueprints control
	#jira UE-52596

Change 3768146 by Marc.Audy

	Fix material instance dynamic not correctly finding object in details panel customization as a result soft path changes
	#jira UE-52488

Change 3769586 by Marc.Audy

	Fix expose on spawn related error messages

Change 3769863 by Dan.Oconnor

	Blueprint call stack now has access to frame offsets and can highlight nodes that are active on previous stack frames

	#jira UE-52452

Change 3771200 by Dan.Oconnor

	CIS fix - add missing DO_BLUEPRINT_GUARD

Change 3771555 by Ben.Zeigler

	Add transactions for several pin class changing actions which were missing them

Change 3771589 by Ben.Zeigler

	#jira UE-52665 Fix it so changing the type of a create widget or spawn actor node will correctly propagate the type change to reroute/wildcard nodes instead of disconnecting

Change 3771683 by Dan.Oconnor

	Call Stack polish: background color no longer changes when undocked, prettify-ing "ExecuteUbergraph_blahblah" in to "Event Graph", resizing works correctly, added overlay message when no call stack is available

	#jira UE-52567

Change 3771734 by Dan.Oconnor

	Add entries for native code in the blueprint call stack view, clarifying re-entrancy

Change 3774293 by Ben.Zeigler

	#jira UE-52665 Minimal fix for making sure type changes propagate through multiple rerout nodes, going to make a larger refactor in a second checkin

Change 3774328 by Ben.Zeigler

	#jira UE-52665 Refactor knot nodes so there is one type propagation function that takes a direction, this fixes an issue where the second knot node in a chain would not have it's type changed when input type changed

Change 3774342 by Ben.Zeigler

	#jira UE-52661 Fix crash when using blueprinted components created by a specialized subclass of UBlueprint, from PR #4249

Change 3774476 by Fred.Kimberley

	Add class and function info to pin names for async nodes. This fixes a problem where redirectors for async node pins did not work.

	https://udn.unrealengine.com/questions/402882/propertyredirect-fails-with-uk2node-latentgameplay.html?childToView=403808

Change 3774645 by Ben.Zeigler

	#jira UE-41743 Fix it so struct split pins handle renames correctly, both for user structs and native structs
	Refactor the variable rename checking in make/break struct to use the generic one I just added

Change 3775412 by Phillip.Kavan

	UX improvements for Blueprint single-step debugging and breakpoints. Also added Step Out and Step Over debugging commands.

	Change summary:
	- Remapped the existing Step In command from F10 to F11 hotkey.
	- Mapped existing Step Over command to F10 and existing Step Out command to ALT-SHIFT-F11 hotkeys.
	- Added new (repurposed) icon assets for single-step debugging toolbar commands.
	- Modified FPlayWorldCommands::BuildToolbar() to add new Step Over and Step Out commands to the toolbar.
	- Modified FCompilerResultsLog::CalculateStableIdentifierForLatentActionManager() to remove special-case code for intermediate Tunnel Instance nodes, as these are now reverse-mapped through FullSourceBacktrackMap.
	- Modified FKismetDebugUtilities::CheckBreakConditions() to more generally manage the current graph stack (i.e. not just for Blueprint Function graphs). Also fixed a bug where we had failed to reset the target graph stack depth after completing a Step Out/Over iteration.
	- Modified FBlueprintDebugData::FindAllCodeLocationsFromSourceNode() to remove the additional iteration for the special Macro source node table (no longer required).
	- Modified FBlueprintDebugData::RegisterNodeToCodeAssociation() to remove the Macro-specific parameters and the additional insertions into the special Macro tables (no longer required).
	- Modified UK2Node_MathExpression::ValidateNodeDuringCompilation() to remove the special-case for Macro Instance source nodes, as Macro source nodes are now being mapped through the same table.
	- Added FindMatchingTunnelInstanceNode() as a utility method for now in BlueprintConnectionDrawingPolicy.cpp in order to match up Macro/Composite graph source nodes with nested Tunnel Instance nodes at the current graph level. *** TODO: For 4.19 we probably should revert back to using a secondary table in the debug data to map Tunnel Instance node hierarchies to code offsets in order to result in a faster lookup time here. ***
	- Modified FKismetConnectionDrawingPolicy::BuilldExecutionRoadmap() to replace the special-case for Macro Instance source nodes with a more general check for Tunnel Instance nodes that also handles Composite source nodes.
	- Revised UK2Node_TunnelBoundary to strip out most of what was being used to support the profiler, while keeping its basic compiled goto behavior in order to still function as a NOP node.
	- Added FKismetCompilerContext::SpawnIntermediateTunnelBoundaryNodes().
	- Modified FKismetCompilerContext::ExpandTunnelsAndMacros() to no longer overwrite intermediate Macro source node mappings in the lookup table with the Macro Instance source node that triggered the Macro graph expansion. Also revised the TunnelNode case to spawn intermediate TunnelBoundary (NOP) nodes around Macro and Composite gateways; this allows breakpoints to hit on the Tunnel nodes around a source graph expansion.
	- Modified FScriptBuilderBase::EmitInstrumentation() to remove special-case handling for Macro and Tunnel source nodes. These are now being mapped directly through the SourceBacktrackMap instead.
	- Removed alternate breakpoint icon assets for Macro Instance and Composite nodes (no longer needed).
	- Removed UK2Node::GetActiveBreakpointToolTipText() and its UK2Node_MacroInstance override (no longer required).
	- Removed special case in SGraphNodeK2Base::GetOverlayBrushes() for Macro Instance and Composite nodes (no longer needed).
	- Removed special-case mappings and interface methods for Tunnel nodes in FCompilerResultsLog (no longer required).
	- Removed the LineNumberToMacroSourceNodeMap and LineNumberToMacroInstanceNodeMap members from the FDebuggingInfoForSingleFunction struct (no longer in use).
	- Removed FBlueprintDebugData::FindMacroSourceNodeFromCodeLocation() and FindMacroInstanceNodesFromCodeLocation().
	- Removed FKismetDebugUtilities::FindMacroSourceNodeForCodeLocation() (no longer in use).
	- Removed special-case handling for Macro Instance nodes in FKismetDebugUtilities::OnScriptException() (no longer required). Macro source nodes are no longer being mapped to code offsets through a separate table, and we don't need to worry about saving/restoring the Active Object when debugging with a Macro Graph in the active tab.

	#jira UE-2880
	#jira UE-16817

Change 3776606 by mason.seay

	Updated content to prevent warning from appearing

Change 3777051 by Dan.Oconnor

	ComponentTemplate references in UBlueprint can no be cleared after compiling the (blueprint defined) component
	#jira UE-52484

Change 3777108 by Dan.Oconnor

	Look up call stack frame source name when caching a script call stack for display. This relies on debug data being generated for event stubs

	#jira UE-52717, UE-52719

Change 3778277 by Marc.Audy

	Fixed potential null material reference causing crash.
	#jira UE-52803

Change 3778288 by Marc.Audy

	PR #3957: Making FAlphaBlend BlueprintType in order to fix a bunch of broken UPROPERTY's as of 4.17 (Contributed by ill)
	#jira UE-49082

Change 3778321 by Phillip.Kavan

	Fix for a regression in BP script execution behavior related to misidentified latent node expansions from a macro source graph.

	Change summary:
	- Removed FCompilerResultsLog::FullSourceBacktrackMap (no longer in use).
	- Restored FCompilerResultsLog::IntermediateTunnelNodeToTunnelInstanceMap (which was in place prior to CL# 37754112); this table was being used to map intermediate nodes resulting from a tunnel instance node expansion back to the outer tunnel instance node that triggered the expansion. Its once again being used for that reason, but I reduced the scope a bit to only include the execution path within the expansion, as that's the only mapping that we need.
	- Restored FCompilerResultsLog::RegisterIntermediateTunnelNode(), but renamed it to NotifyIntermediateTunnelNode() to be consistent with the other parts of the MessageLog interface, and also removed the part of the implementation that was adding to a secondary macro expansion-to-source backtrack map (since macro expansion node lookup is now done through the main source backtrack map).
	- Restored FCompilerResultsLog::GetIntermediateTunnelInstance().
	- Modified FCompilerResultsLog::NotifyIntermediateObjectCreation() to remove the part of the implementation that was adding to the secondary node-only-to-source backtrack map (it was previously just a redundant copy of the main one except in the case of macro expansions).
	- Modified FCompilerResultsLog::CalculateStableIdentifierForLatentActionManager() to restore the calculation of a stable UUID for nodes sourced from a macro expansion, where we had incorporated the outer intermediate tunnel instance node chain.

	#jira UE-52872

Change 3778329 by Marc.Audy

	PR #4241: Enforce calling superclass on ActorComponent::BeginPlay (Contributed by rlefebvre)
	#jira UE-52574

Change 3778349 by Marc.Audy

	Minor cleanup

Change 3759702 by Ben.Zeigler

	#jira UE-52287 Prevent cook metadata like DevelopmentAssetRegistry.bin from being packed into a shipping game, by moving it into a Metadata subdirectory and updating deployment scripts to avoid that directory.
	Right now it doesn't package them at all, we could change it to package them as Debug Non-UFS if desired
	Change it so the asset audit UI will only load DevelopmentAssetRegistry.bin files, the cooked registry files don't have enough information any more to be useful
	Remove ability for runtime game to load DevelopmentAssetRegistry.bin, this ended up not being useful
	#jira UE-52158 Fix it to refresh the list of possible asset audit platforms when the refresh button is pushed

Change 3766414 by Fred.Kimberley

	Data validation plugin

Change 3769923 by Ben.Zeigler

	#jira UE-30347 Change ResourceSize mode enum from Inclusive to EstimatedTotal, which includes UObject serialization data as well as data for any subobjects. It now does NOT include externally referenced assets, which it did for some assets but not others
	Fix Texture EstimatedTotal memory to handle LOD bias, it now reports the largest possible size in a cooked game of any platform
	Fix many GetResourceSizeEx calls to match the new definition and improve accuracy
	Switched several editor tools to use EstimatedTotal now that it is more useful, and removed some unused memory stats
	Remove ResourceSize from UObject asset registry tags as it was misleading and inaccurate, for now it is only possible to get this for loaded objects
	Remove MapFileSize from Worlds as it redundant with the generic file size. Fixed the generic file size to display using the Size format
	Several UI fixes for Asset Audit and Size Map to deal with this change. Asset Audit no longer has the memory size columns, and the memory size drop down in Size Map is disabled for cooked builds

Change 3771365 by Ben.Zeigler

	#jira UE-52670 Add project setting bValidateUnloadedSoftActorReferences that is true by default to match current behavior. If you set it to false it will no longer load packages to look for soft actor references when deleting/renaming actors.

[CL 3779057 by Marc Audy in Main branch]
2017-11-29 16:03:05 -05:00

1137 lines
37 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "AssetRenameManager.h"
#include "Serialization/ArchiveUObject.h"
#include "UObject/Class.h"
#include "Misc/PackageName.h"
#include "Misc/MessageDialog.h"
#include "HAL/FileManager.h"
#include "Misc/FeedbackContext.h"
#include "Modules/ModuleManager.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "UObject/UnrealType.h"
#include "Layout/Margin.h"
#include "Input/Reply.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SWindow.h"
#include "Layout/WidgetPath.h"
#include "SlateOptMacros.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Views/STableViewBase.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Views/SListView.h"
#include "EditorStyleSet.h"
#include "ISourceControlOperation.h"
#include "SourceControlOperations.h"
#include "ISourceControlModule.h"
#include "FileHelpers.h"
#include "SDiscoveringAssetsDialog.h"
#include "AssetRegistryModule.h"
#include "CollectionManagerTypes.h"
#include "ICollectionManager.h"
#include "CollectionManagerModule.h"
#include "ObjectTools.h"
#include "Interfaces/IMainFrameModule.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Misc/RedirectCollector.h"
#include "AssetToolsLog.h"
#include "Settings/EditorProjectSettings.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)
{
if (Asset.IsValid() && !OldObjectPath.IsValid())
{
OldObjectPath = FSoftObjectPath(Asset.Get());
}
else if (OldObjectPath.IsValid() && !Asset.IsValid())
{
Asset = OldObjectPath.ResolveObject();
}
if (!NewName.IsEmpty() && !NewObjectPath.IsValid())
{
NewObjectPath = FSoftObjectPath(FString::Printf(TEXT("%s/%s.%s"), *NewPackagePath, *NewName, *NewName));
}
else if (NewObjectPath.IsValid() && NewName.IsEmpty())
{
NewName = NewObjectPath.GetAssetName();
NewPackagePath = FPackageName::GetLongPackagePath(NewObjectPath.GetLongPackageName());
}
}
};
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 (const FText& RenameText : InArgs._FailedRenames)
{
FailedRenames.Add( MakeShareable( new FText(RenameText) ) );
}
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, bool bAutoCheckout) 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, bAutoCheckout)
);
}
else
{
// No need to wait, attempt to fix references and rename now.
FixReferencesAndRename(AssetsAndNames, bAutoCheckout);
}
}
void FAssetRenameManager::FindSoftReferencesToObject(FSoftObjectPath TargetObject, TArray<UObject*>& ReferencingObjects) const
{
TArray<FAssetRenameDataWithReferencers> AssetsToRename;
AssetsToRename.Emplace(FAssetRenameDataWithReferencers(FAssetRenameData(TargetObject, TargetObject, true)));
// Fill out referencers from asset registry
PopulateAssetReferencers(AssetsToRename);
// Load all referencing objects and find for referencing objects
TArray<UPackage*> ReferencingPackagesToSave;
LoadReferencingPackages(AssetsToRename, true, false, ReferencingPackagesToSave, ReferencingObjects);
}
void FAssetRenameManager::FixReferencesAndRename(TArray<FAssetRenameData> AssetsAndNames, bool bAutoCheckout) const
{
bool bSoftReferencesOnly = true;
// 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));
if (!AssetRenameData.bOnlyFixSoftReferences)
{
bSoftReferencesOnly = false;
}
}
// Warn the user if they are about to rename an asset that is referenced by a CDO
TArray<TWeakObjectPtr<UObject>> 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 (bSoftReferencesOnly || 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;
TArray<UObject*> SoftReferencingObjects;
LoadReferencingPackages(AssetsToRename, bSoftReferencesOnly, true, ReferencingPackagesToSave, SoftReferencingObjects);
// 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, bAutoCheckout);
if (bUserAcceptedCheckout || bSoftReferencesOnly)
{
// 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);
if (bSoftReferencesOnly)
{
if (ReferencingPackagesToSave.Num() > 0)
{
// Only do the rename if there are actually packages with references
PerformAssetRename(AssetsToRename);
for (const FAssetRenameDataWithReferencers& RenameData : AssetsToRename)
{
// Add source and destination packages so those get saved at the same time
UPackage* OldPackage = FindPackage(nullptr, *RenameData.OldObjectPath.GetLongPackageName());
UPackage* NewPackage = FindPackage(nullptr, *RenameData.NewObjectPath.GetLongPackageName());
ReferencingPackagesToSave.AddUnique(OldPackage);
ReferencingPackagesToSave.AddUnique(NewPackage);
}
FString AssetNames;
for (UPackage* PackageToSave : ReferencingPackagesToSave)
{
AssetNames += FString("\n") + PackageToSave->GetName();
}
// Warn user before saving referencing packages
bool bAgreedToSaveReferencingPackages = bAutoCheckout;
if (!bAgreedToSaveReferencingPackages)
{
const FText MessageText = FText::Format(LOCTEXT("SoftReferenceFixedUp", "The following packages were fixed up because they have soft references to a renamed object: \n{0}\n\nDo you want to save them now?\nIf you quit without saving references will be broken!"), FText::FromString(AssetNames));
bAgreedToSaveReferencingPackages = FMessageDialog::Open(EAppMsgType::YesNo, MessageText) == EAppReturnType::Yes;
}
if (bAgreedToSaveReferencingPackages)
{
SaveReferencingPackages(ReferencingPackagesToSave);
}
}
}
else
{
// 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 (const FAssetRenameDataWithReferencers& AssetToRename : AssetsToRename)
{
if (AssetToRename.Asset.IsValid())
{
LocalAssetsToRename.Push(AssetToRename.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 != nullptr)
{
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 (const TWeakObjectPtr<UObject> Asset : LocalAssetsToRename)
{
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 (FAssetRenameDataWithReferencers& AssetToRename : AssetsToPopulate)
{
// If we're only fixing soft references we want to check for references inside the original package as we don't save the original package automatically
if (!AssetToRename.bOnlyFixSoftReferences)
{
RenamingAssetPackageNames.Add(FName(*AssetToRename.OldObjectPath.GetLongPackageName()));
}
}
// Gather all referencing packages for all assets that are being renamed
for (FAssetRenameDataWithReferencers& AssetToRename : AssetsToPopulate)
{
AssetToRename.ReferencingPackageNames.Empty();
FName OldPackageName = FName(*AssetToRename.OldObjectPath.GetLongPackageName());
TArray<FName> Referencers;
AssetRegistryModule.Get().GetReferencers(OldPackageName, Referencers, AssetToRename.bOnlyFixSoftReferences ? EAssetRegistryDependencyType::Soft : EAssetRegistryDependencyType::Packages);
for (const FName& ReferencingPackageName : Referencers)
{
if (!RenamingAssetPackageNames.Contains(ReferencingPackageName))
{
AssetToRename.ReferencingPackageNames.AddUnique(ReferencingPackageName);
}
}
if (AssetToRename.bOnlyFixSoftReferences)
{
AssetToRename.ReferencingPackageNames.AddUnique(FName(*AssetToRename.OldObjectPath.GetLongPackageName()));
AssetToRename.ReferencingPackageNames.AddUnique(FName(*AssetToRename.NewObjectPath.GetLongPackageName()));
// Add dirty packages and the package that owns the reference. They will get filtered out in LoadReferencingPackages if they aren't valid
TArray<UPackage*> ExtraPackagesToCheckForSoftReferences;
FEditorFileUtils::GetDirtyWorldPackages(ExtraPackagesToCheckForSoftReferences);
FEditorFileUtils::GetDirtyContentPackages(ExtraPackagesToCheckForSoftReferences);
for (UPackage* Package : ExtraPackagesToCheckForSoftReferences)
{
AssetToRename.ReferencingPackageNames.AddUnique(Package->GetFName());
}
}
}
}
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, bool bLoadAllPackages, bool bCheckStatus, TArray<UPackage*>& OutReferencingPackagesToSave, TArray<UObject*>& OutSoftReferencingObjects) const
{
const UBlueprintEditorProjectSettings* EditorProjectSettings = GetDefault<UBlueprintEditorProjectSettings>();
bool bLoadPackagesForSoftReferences = EditorProjectSettings->bValidateUnloadedSoftActorReferences;
bool bStartedSlowTask = false;
const FText ReferenceUpdateSlowTask = LOCTEXT("ReferenceUpdateSlowTask", "Updating Asset References");
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
for (int32 AssetIdx = 0; AssetIdx < AssetsToRename.Num(); ++AssetIdx)
{
if (bStartedSlowTask)
{
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
if (bCheckStatus)
{
FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(Asset->GetOutermost(), EStateCacheUsage::ForceUpdate);
const bool bLocalFile = !SourceControlState.IsValid() || SourceControlState->IsAdded() || !SourceControlState->IsSourceControlled() || SourceControlState->IsIgnored();
if (!bLocalFile)
{
// If this asset is locked or not current, mark it failed to prevent it from being renamed
if (SourceControlState->IsCheckedOutOther())
{
RenameData.bRenameFailed = true;
RenameData.FailureReason = LOCTEXT("RenameFailedCheckedOutByOther", "Checked out by another user.");
}
else if (!SourceControlState->IsCurrent())
{
RenameData.bRenameFailed = true;
RenameData.FailureReason = LOCTEXT("RenameFailedNotCurrent", "Out of date.");
}
// This asset is not local. It is not safe to rename it without leaving a redirector
RenameData.bCreateRedirector = true;
if (!bLoadAllPackages)
{
continue;
}
}
}
}
else
{
// The asset for this rename must have been GCed or is otherwise invalid. Skip it unless this is a soft reference only fix
if (!bLoadAllPackages)
{
continue;
}
}
TMap<FSoftObjectPath, FSoftObjectPath> ModifiedPaths;
ModifiedPaths.Add(RenameData.OldObjectPath, RenameData.NewObjectPath);
TArray<UPackage*> PackagesToSaveForThisAsset;
bool bAllPackagesLoadedForThisAsset = true;
for (int32 i = 0; i < RenameData.ReferencingPackageNames.Num(); i++)
{
FName PackageName = RenameData.ReferencingPackageNames[i];
// Check if the package is a map before loading it!
if (!bLoadAllPackages && FEditorFileUtils::IsMapPackageAsset(PackageName.ToString()))
{
// This reference was a map package, don't load it and leave a redirector for this asset
// For subobjects we want to load maps packages and treat them normally
RenameData.bCreateRedirector = true;
bAllPackagesLoadedForThisAsset = false;
break;
}
UPackage* Package = FindPackage(nullptr, *PackageName.ToString());
// Don't load package if this is a soft reference fix and the project settings say not to
if (!Package && (!RenameData.bOnlyFixSoftReferences || bLoadPackagesForSoftReferences))
{
if(!bStartedSlowTask)
{
bStartedSlowTask = true;
GWarn->BeginSlowTask(ReferenceUpdateSlowTask, true);
}
Package = LoadPackage(nullptr, *PackageName.ToString(), LOAD_None);
}
if (Package)
{
bool bFoundSoftReference = CheckPackageForSoftObjectReferences(Package, ModifiedPaths, OutSoftReferencingObjects);
// Only add to list if we're doing a hard reference fixup or we found a soft reference
bool bAdd = !RenameData.bOnlyFixSoftReferences || bFoundSoftReference;
if (bAdd)
{
PackagesToSaveForThisAsset.Add(Package);
}
else
{
// This package does not actually reference the asset, so remove it
RenameData.ReferencingPackageNames.RemoveAt(i);
i--;
}
}
else
{
RenameData.bCreateRedirector = true;
if (!bLoadAllPackages)
{
bAllPackagesLoadedForThisAsset = false;
break;
}
}
}
if (bAllPackagesLoadedForThisAsset)
{
for (UPackage* Package : PackagesToSaveForThisAsset)
{
OutReferencingPackagesToSave.AddUnique(Package);
}
}
}
if (bStartedSlowTask)
{
GWarn->EndSlowTask();
}
}
bool FAssetRenameManager::CheckOutPackages(TArray<FAssetRenameDataWithReferencers>& AssetsToRename, TArray<UPackage*>& InOutReferencingPackagesToSave, bool bAutoCheckout) 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 FAssetRenameDataWithReferencers& AssetToRename : AssetsToRename)
{
if (!AssetToRename.bRenameFailed && 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 = bAutoCheckout ? AutoCheckOut(PackagesToCheckOut) : 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 (UPackage* Package : PackagesCheckedOutOrMadeWritable)
{
PackagesThatCouldNotBeCheckedOut.Remove(Package);
}
for (UPackage* Package : PackagesNotNeedingCheckout)
{
PackagesThatCouldNotBeCheckedOut.Remove(Package);
}
// If there's anything which couldn't be checked out, abort the operation.
if (PackagesThatCouldNotBeCheckedOut.Num() > 0)
{
bUserAcceptedCheckout = false;
}
}
}
return bUserAcceptedCheckout;
}
bool FAssetRenameManager::AutoCheckOut(TArray<UPackage*>& PackagesToCheckOut) const
{
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
TArray<TSharedRef<ISourceControlState, ESPMode::ThreadSafe>> SourceControlStates;
ECommandResult::Type GetStateResult = SourceControlProvider.GetState(PackagesToCheckOut, SourceControlStates, EStateCacheUsage::ForceUpdate);
if (GetStateResult != ECommandResult::Succeeded || SourceControlStates.Num() != PackagesToCheckOut.Num())
{
UE_LOG(LogAssetTools, Warning, TEXT("FAssetRenameManager::AutoCheckOut: could not get source control state for packages"));
return false;
}
bool bSomethingFailed = false;
for (const TSharedRef<ISourceControlState, ESPMode::ThreadSafe>& PackageState : SourceControlStates)
{
if (PackageState->IsCheckedOutOther())
{
UE_LOG(LogAssetTools, Warning, TEXT("FAssetRenameManager::AutoCheckOut: package %s is already checked out by someone, will not check out"), *PackageState->GetFilename());
bSomethingFailed = true;
}
else if (!PackageState->IsCurrent())
{
UE_LOG(LogAssetTools, Warning, TEXT("FAssetRenameManager::AutoCheckOut: package %s is not at head, will not check out"), *PackageState->GetFilename());
bSomethingFailed = true;
}
}
if (!bSomethingFailed)
{
if (SourceControlProvider.Execute(ISourceControlOperation::Create<FCheckOut>(), PackagesToCheckOut) == ECommandResult::Succeeded)
{
PackagesToCheckOut.Empty();
}
else
{
bSomethingFailed = true;
}
}
return !bSomethingFailed;
}
void FAssetRenameManager::DetectReferencingCollections(TArray<FAssetRenameDataWithReferencers>& AssetsToRename) const
{
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
for (FAssetRenameDataWithReferencers& 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(), nullptr, &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 (FAssetRenameDataWithReferencers& RenameData : AssetsToRename)
{
if (RenameData.ReferencingPackageNames.Contains(PackageName))
{
RenameData.bCreateRedirector = true;
}
}
// Remove the package from the save list
InOutReferencingPackagesToSave.RemoveAt(PackageIdx);
}
}
}
}
}
struct FSoftObjectPathRenameSerializer : public FArchiveUObject
{
void StartSerializingObject(UObject* InCurrentObject)
{
CurrentObject = InCurrentObject;
bFoundReference = false;
}
bool HasFoundReference() const
{
return bFoundReference;
}
FSoftObjectPathRenameSerializer(const TMap<FSoftObjectPath, FSoftObjectPath>& InRedirectorMap, bool bInCheckOnly, TSet<FSoftObjectPath>* InCachedObjectPaths)
: RedirectorMap(InRedirectorMap)
, CachedObjectPaths(InCachedObjectPaths)
, CurrentObject(nullptr)
, bSearchOnly(bInCheckOnly)
, bFoundReference(false)
{
// Mark it as saving to correctly process all references
ArIsSaving = true;
}
FArchive& operator<<(FSoftObjectPath& Value)
{
FString SubPath = Value.GetSubPathString();
for (const TPair<FSoftObjectPath, FSoftObjectPath>& Pair : RedirectorMap)
{
if (CachedObjectPaths)
{
CachedObjectPaths->Add(Value);
}
if (Pair.Key.GetAssetPathName() == Value.GetAssetPathName())
{
// Same asset, fix sub path. Asset will be fixed by normal serializePath call below
FString CheckSubPath = Pair.Key.GetSubPathString();
if (CheckSubPath.IsEmpty() || SubPath == CheckSubPath || SubPath.StartsWith(CheckSubPath + TEXT(".")))
{
bFoundReference = true;
if (!bSearchOnly)
{
if (CurrentObject)
{
check(!CachedObjectPaths); // Modify can invalidate the object paths map, not allowed to be modifying and using the cache at the same time
CurrentObject->Modify(true);
}
SubPath.ReplaceInline(*CheckSubPath, *Pair.Value.GetSubPathString());
Value = FSoftObjectPath(Pair.Value.GetAssetPathName(), SubPath);
}
break;
}
}
}
return *this;
}
private:
const TMap<FSoftObjectPath, FSoftObjectPath>& RedirectorMap;
TSet<FSoftObjectPath>* CachedObjectPaths;
UObject* CurrentObject;
bool bSearchOnly;
bool bFoundReference;
};
void FAssetRenameManager::RenameReferencingSoftObjectPaths(const TArray<UPackage *> PackagesToCheck, const TMap<FSoftObjectPath, FSoftObjectPath>& AssetRedirectorMap) const
{
// Add redirects as needed
for (const TPair<FSoftObjectPath, FSoftObjectPath>& Pair : AssetRedirectorMap)
{
if (Pair.Key.IsAsset())
{
GRedirectCollector.AddAssetPathRedirection(Pair.Key.GetAssetPathName(), Pair.Value.GetAssetPathName());
}
}
FSoftObjectPathRenameSerializer RenameSerializer(AssetRedirectorMap, false, nullptr);
for (UPackage* Package : PackagesToCheck)
{
TArray<UObject*> ObjectsInPackage;
GetObjectsWithOuter(Package, ObjectsInPackage);
for (UObject* Object : ObjectsInPackage)
{
if (Object->IsPendingKill())
{
continue;
}
RenameSerializer.StartSerializingObject(Object);
Object->Serialize(RenameSerializer);
if (UBlueprint* Blueprint = Cast<UBlueprint>(Object))
{
// Serialize may have dirtied the BP bytecode in some way
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
// Invalidate the soft object tag as we have created new valid paths
FSoftObjectPath::InvalidateTag();
}
void FAssetRenameManager::OnMarkPackageDirty(UPackage* Pkg, bool bWasDirty)
{
// Remove from cache
CachedSoftReferences.Remove(Pkg->GetFName());
}
bool FAssetRenameManager::CheckPackageForSoftObjectReferences(UPackage* Package, const TMap<FSoftObjectPath, FSoftObjectPath>& AssetRedirectorMap, TArray<UObject*>& OutReferencingObjects) const
{
bool bFoundReference = false;
// First check cache
TSet<FSoftObjectPath>* CachedReferences = CachedSoftReferences.Find(Package->GetFName());
if (CachedReferences)
{
for (const TPair<FSoftObjectPath, FSoftObjectPath>& Pair : AssetRedirectorMap)
{
for (FSoftObjectPath& Value : *CachedReferences)
{
FString SubPath = Value.GetSubPathString();
if (Pair.Key.GetAssetPathName() == Value.GetAssetPathName())
{
FString CheckSubPath = Pair.Key.GetSubPathString();
if (CheckSubPath.IsEmpty() || SubPath == CheckSubPath || SubPath.StartsWith(CheckSubPath + TEXT(".")))
{
bFoundReference = true;
}
}
}
}
if (!bFoundReference)
{
return false;
}
}
else
{
// Bind to dirty callback if we aren't already
if (!DirtyDelegateHandle.IsValid())
{
DirtyDelegateHandle = UPackage::PackageMarkedDirtyEvent.AddSP(this, &FAssetRenameManager::OnMarkPackageDirty);
}
CachedReferences = &CachedSoftReferences.Add(Package->GetFName());
}
FSoftObjectPathRenameSerializer CheckSerializer(AssetRedirectorMap, true, CachedReferences);
TArray<UObject*> ObjectsInPackage;
GetObjectsWithOuter(Package, ObjectsInPackage);
for (UObject* Object : ObjectsInPackage)
{
if (Object->IsPendingKill())
{
continue;
}
CheckSerializer.StartSerializingObject(Object);
Object->Serialize(CheckSerializer);
if (CheckSerializer.HasFoundReference())
{
bFoundReference = true;
OutReferencingObjects.AddUnique(Object);
}
}
return bFoundReference;
}
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 *> DirtyPackagesToCheckForSoftReferences;
FEditorFileUtils::GetDirtyWorldPackages(DirtyPackagesToCheckForSoftReferences);
FEditorFileUtils::GetDirtyContentPackages(DirtyPackagesToCheckForSoftReferences);
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();
TArray<UPackage *> PackagesToCheckForSoftReferences;
if (!RenameData.bOnlyFixSoftReferences)
{
// If bOnlyFixSoftReferences was set these got appended in find references
PackagesToCheckForSoftReferences.Append(DirtyPackagesToCheckForSoftReferences);
if (!Asset)
{
// This asset was invalid or GCed before the rename could occur
RenameData.bRenameFailed = true;
continue;
}
ObjectTools::FPackageGroupName PGN;
PGN.ObjectName = RenameData.NewName;
PGN.GroupName = TEXT("");
PGN.PackageName = RenameData.NewPackagePath / 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, nullptr, 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;
}
}
for (FName PackageName : RenameData.ReferencingPackageNames)
{
UPackage* PackageToCheck = FindPackage(nullptr, *PackageName.ToString());
if (PackageToCheck)
{
PackagesToCheckForSoftReferences.Add(PackageToCheck);
}
}
TMap<FSoftObjectPath, FSoftObjectPath> RedirectorMap;
RedirectorMap.Add(RenameData.OldObjectPath, RenameData.NewObjectPath);
if (UBlueprint* Blueprint = Cast<UBlueprint>(Asset))
{
// Add redirect for class and default as well
RedirectorMap.Add(FString::Printf(TEXT("%s_C"), *RenameData.OldObjectPath.ToString()), FString::Printf(TEXT("%s_C"), *RenameData.NewObjectPath.ToString()));
RedirectorMap.Add(FString::Printf(TEXT("%s.Default__%s_C"), *RenameData.OldObjectPath.GetLongPackageName(), *RenameData.OldObjectPath.GetAssetName()), FString::Printf(TEXT("%s.Default__%s_C"), *RenameData.NewObjectPath.GetLongPackageName(), *RenameData.NewObjectPath.GetAssetName()));
}
RenameReferencingSoftObjectPaths(PackagesToCheckForSoftReferences, RedirectorMap);
}
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 FAssetRenameDataWithReferencers& RenameData : AssetsToRename)
{
UPackage* OldPackage = FindPackage(nullptr, *RenameData.OldObjectPath.GetLongPackageName());
UPackage* NewPackage = FindPackage(nullptr, *RenameData.NewObjectPath.GetLongPackageName());
// 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 (!RenameData.bOnlyFixSoftReferences && NewPackage && FPackageName::DoesPackageExist(NewPackage->GetName()))
{
SourceControlHelpers::BranchPackage(NewPackage, OldPackage);
}
}
// Clean up all packages that were left empty
if (PotentialPackagesToDelete.Num() > 0)
{
for (UPackage* Package : PotentialPackagesToDelete)
{
Package->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 (const FAssetRenameDataWithReferencers& RenameData : AssetsToRename)
{
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