Changes FAssetHeaderPatcherInner to leverage the CoreRedirect system for resolving FNames in a package header, and string paths when context about the string itself is well known (i.e. we know the string data is a stringified ObjectPath/PackagePath, or ObjectName are deducible). We still make use of string scanning replacement mechanism but those are now constrained to only patching strings content specifically (e.g. Tag data)

- Added FAssetHeaderPatcher::FContext for storing data on how to perform a patch (which redirects to follow, string -> string replacement map). Allows for an extensibility point for encapsulating special case patching so we don't need to  rely on string->string mappings exclusively
- Added `virtual FArchive& operator<<(FTopLevelAssetPath& Value)` to `FActorDescArchive` to allow for patching of FTopLevelAssetPath directly. Without this overload the FNames within a FTopLevelAssetPath would be patched one at a time losing all context about what the FNames are.
- Removed IAssetTools::GetAdditionalPatchCopyMappings, GetPatchCopyMappingsForRootRename, and PatchCopyPackageFile as these methods were internal and using the patcher directly should be preferred.
- Added some unit tests for the FAssetHeaderPatcherInner implementation works to codify the assumptions and expectations of the patcher implementation

New patching changes are left disabled by default

#rnx

#rb Dave.Belanger, JeanFrancois.Dube

[CL 36764489 by kevin macaulayvacher in 5.5 branch]
This commit is contained in:
kevin macaulayvacher
2024-10-01 20:42:58 -04:00
parent b72b4725e7
commit 73861a1e58
9 changed files with 2719 additions and 1035 deletions
@@ -0,0 +1,222 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Tasks/Task.h"
#include "Containers/ContainersFwd.h"
#include "UObject/CoreRedirects.h"
/**
* Delegate called on when patch operation completes
* @param SrcFilePath Path of file being read for patching
* @param DstFilePath Path of file being written to after patching
*/
DECLARE_DELEGATE_TwoParams(FAssetHeaderPatcherCompletionDelegate, const FString& /*SrcFilePath*/, const FString& /*DstFilePath*/)
UE_INTERNAL struct ASSETTOOLS_API FAssetHeaderPatcher
{
enum class EResult
{
NotStarted,
Cancelled,
InProgress,
Success,
ErrorFailedToLoadSourceAsset,
ErrorFailedToDeserializeSourceAsset,
ErrorUnexpectedSectionOrder,
ErrorBadOffset,
ErrorUnkownSection,
ErrorFailedToOpenDestinationFile,
ErrorFailedToWriteToDestinationFile,
ErrorEmptyRequireSection,
};
UE_INTERNAL struct ASSETTOOLS_API FContext
{
FContext() = default;
/**
* Context used for patching. Contains all information for how object and package references
* will be changed as part of patching.
* *
* When bInGatherDependentPackages is true, the provided long package name (/Root/Folder/Package) to
* destination long package name mapping will be used to find any dependent packages that must also
* be patched due to internal references. The mapping provided in InSrcAndDstPackagePaths will be used
* to determine the filepath on disk to write when patching.
*
* @param InSrcAndDstPackagePaths Map of all long package names (/Root/Folder/Package) to be patched and to which new name they should be patched to.
* @param bInGatherDependentPackages If true (default), upon creating the context GatherDependentPackages() will be called.
**/
FContext(const TMap<FString, FString>& InSrcAndDstPackagePaths, const bool bInGatherDependentPackages = true);
/**
* Context used for patching. Contains all information for how object and package references
* will be changed as part of patching.
*
* When patching, package paths to patch will be deduced by the filepath mappings provided in InSrcAndDstFilePaths. All assets
* under InSrcRoot will be written as package paths under a mountpoint located at InSrcBaseDir.
*
* e.g. Path "C:/User/Repo/Project/Content/Skeletons/Player.uasset" -> "/InSrcRoot/Skeletons/Player" when InSrcBaseDir=C:/User/Repo/Project (/Content is assumed internally)
*
* @param InSrcRoot The root mount point for assets to be patched
* @param InDstRoot The new root mount point for patched assets to be placed under
* @param InSrcBaseDir Path to the directory holding the /Content/ directory for assets to patch
* @param InSrcAndDstFilePaths Map of filepaths for files to be patched and where to write the patched version to
* @param InMountPointReplacements Map of root mountpoints (name only, no "/" prefix or suffix) to replace when patching
**/
FContext(const FString& InSrcRoot, const FString& InDstRoot, const FString& InSrcBaseDir, const TMap<FString, FString>& InSrcAndDstFilePaths, const TMap<FString, FString>& InMountPointReplacements);
/*
* Returns the mapping of source long package names to destination package paths used when patching.
* This mapping may include more packages than initially supplied to the FContext
* if GatherDependentPackages has already been called.
* Note, this map can be invalidated by calls to GatherDependentPackages()
*/
const TMap<FString, FString>& GetLongPackagePathRemapping() const
{
return PackagePathRenameMap;
};
protected:
friend FAssetHeaderPatcher;
void AddVerseMounts();
void GatherDependentPackages();
void GenerateFilePathsFromPackagePaths();
void GeneratePackagePathsFromFilePaths(const FString& InSrcRoot, const FString& InDstRoot, const FString& InSrcBaseDir);
void GenerateAdditionalRemappings();
TArray<FString> VerseMountPoints;
TMap<FString, FString> PackagePathRenameMap;
TMap<FString, FString> FilePathRenameMap;
// Todo: Make TSet once FCoreRedirect GetTypeHash is implemented
TArray<FCoreRedirect> Redirects;
// String mappings are only used for best-effort replacements. These will be error-prone
// and we should strive for more structured data formats to guard against errors here
TMap<FString, FString> StringReplacements;
TMap<FString, FString> StringMountReplacements;
};
FAssetHeaderPatcher() = default;
FAssetHeaderPatcher(FContext InContext) : Context(InContext) { Reset(); }
/*
* Resets the patcher state and sets a new patching context.
* It is an error to call while patching is already in progress.
*/
void SetContext(FContext InContext);
/*
* Schedules the reading of source files determined by the patcher context, as well as the writing of the patched versions
* of all source files read.
*
* @param InOutNumFilesToPatch Optional value to know how many files are expected to be read/written during patching
* @param InOutNumFilesPatched Optional value used to know how the patcher is progressing (useful for progress bars)
*/
UE::Tasks::FTask PatchAsync(int32* InOutNumFilesToPatch = nullptr, int32* InOutNumFilesPatched = nullptr);
UE::Tasks::FTask PatchAsync(int32* InOutNumFilesToPatch, int32* InOutNumFilesPatched, FAssetHeaderPatcherCompletionDelegate InOnSuccess, FAssetHeaderPatcherCompletionDelegate InOnError);
/*
* Returns the status of any inflight patching operations. In the case of multiple errors, the last seen error will be reported.
* Per file error status codes can be returned with GetErrorFiles().
*/
EResult GetPatchResult() const
{
return Status;
}
/*
* Returns source file -> destination mapping for all files that were patched successfully.
*/
TMap<FString, FString> GetPatchedFiles() const
{
if (IsPatching())
{
return TMap<FString, FString>();
}
return PatchedFiles;
}
/*
* Returns true if the patcher encountered errors (even if patching was cancelled)
*/
bool HasErrors()
{
FScopeLock Lock(&ErroredFilesLock);
return !!ErroredFiles.Num();
}
/*
* Returns a map of all files that had an error during patching with an error code to provide context as to the cause of the error.
*/
TMap<FString, EResult> GetErrorFiles()
{
FScopeLock Lock(&ErroredFilesLock);
return ErroredFiles;
}
/*
* Returns true if the patcher is still in theprocess of patching.
*/
bool IsPatching() const
{
return !PatchingTask.IsCompleted();
}
/*
* Cancels an in-flight patching operation. Patching work on individual files that has already started will run to completion
* however any files that have not started patching will be skipped. Even after cancelling, one must wait for the patcher to complete
* by waiting on the GetPatchingTask() explciitly or until IsPatching returns false.
*
* @return true if an in-flight patching operation was cancelled. If no patching operation is underway, returns false.
*/
bool CancelPatching()
{
if (!IsPatching())
{
return false;
}
bCancelled = true;
Status = EResult::Cancelled;
return true;
}
/*
* Returns the task for all patcher work underway. Waiting on this task will guarantee all patch work is completed.
*/
UE::Tasks::FTask GetPatchingTask() const
{
return PatchingTask;
}
/**
* Patches object and package references contained within InSrcAsset using the mapping provided to InContext. The
* patched asset will be written to InDstAsset.
*
* @param InSrcAsset Long package name (/Root/Folder/Package) to read in to be patched.
* @param InDstAsset Long package name (/Root/Folder/Package) where the patched package will be written to.
* @param InContext Context for how the patching will be performed. Contains all remapping information to the patcher.
* @return Success patching was successful and the InDstAsset package was written. Returns an error status otherwise.
**/
static EResult DoPatch(const FString& InSrcAsset, const FString& InDstAsset, const FContext& InContext);
private:
void Reset();
FContext Context;
TMap<FString, EResult> ErroredFiles;
FCriticalSection ErroredFilesLock;
TMap<FString, FString> PatchedFiles;
UE::Tasks::FTask PatchingTask;
EResult Status = EResult::NotStarted;
std::atomic<bool> bCancelled = false;
};
UE_INTERNAL ASSETTOOLS_API FString LexToString(FAssetHeaderPatcher::EResult InResult);
File diff suppressed because it is too large Load Diff
@@ -1,26 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Tasks/Task.h"
#include "Containers/ContainersFwd.h"
struct FAssetHeaderPatcher
{
enum class EResult
{
Success,
ErrorFailedToLoadSourceAsset,
ErrorFailedToDeserializeSourceAsset,
ErrorUnexpectedSectionOrder,
ErrorBadOffset,
ErrorUnkownSection,
ErrorFailedToOpenDestinationFile,
ErrorFailedToWriteToDestinationFile,
ErrorEmptyRequireSection,
};
static EResult DoPatch(const FString& InSrcAsset, const FString& InDstAsse, const TMap<FString, FString>& InSearchAndReplace, bool bInStarRestrictionInUse = false);
static EResult Test_DoPatch(FArchive& InSrcReader, FArchive& InDstWriter, const TMap<FString, FString>& InSearchAndReplace);
};
File diff suppressed because it is too large Load Diff
@@ -134,9 +134,6 @@ public:
virtual bool ValidateFlattenedAdvancedCopyDestinations(const TMap<FString, FString>& FlattenedPackagesAndDestinations) const override;
virtual void GetAllAdvancedCopySources(FName SelectedPackage, FAdvancedCopyParams& CopyParams, TArray<FName>& OutPackageNamesToCopy, TMap<FName, FName>& DependencyMap, const class UAdvancedCopyCustomization* CopyCustomization) const override;
virtual void InitAdvancedCopyFromCopyParams(FAdvancedCopyParams CopyParams) const override;
virtual bool PatchCopyPackageFile(const FString& SrcFile, const FString& DstFile, const TMap<FString, FString>& SearchForAndReplace) const override;
virtual TMap<FString, FString> GetPatchCopyMappingsForRootRename(const FString& SrcRoot, const FString& DstRoot, const FString& SrcBaseDir, const TArray<TPair<FString, FString>>& SourceAndDestFiles, const TMap<FString, FString>& MountPointReplacements) const override;
virtual TMap<FString, FString> GetAdditionalPatchCopyMappings(const TMap<FString, FString>& SourceAndDestPackages) const override;
virtual void OpenEditorForAssets(const TArray<UObject*>& Assets) override;
virtual void ConvertVirtualTextures(const TArray<UTexture2D*>& Textures, bool bConvertBackToNonVirtual, const TArray<UMaterial*>* RelatedMaterials = nullptr) const override;
virtual bool IsAssetClassSupported(const UClass* AssetClass) const override;
@@ -611,15 +611,6 @@ public:
/* Given a complete set of copy parameters, which includes the selected package set, start the advanced copy process */
virtual void InitAdvancedCopyFromCopyParams(FAdvancedCopyParams CopyParams) const = 0;
/** Copies a file, patching internal references without performing a de-serialization. This is a blocking operation. returns true on successful copy */
UE_INTERNAL virtual bool PatchCopyPackageFile(const FString& SrcFile, const FString& DstFile, const TMap<FString, FString>& SearchForAndReplace) const = 0;
/** Generates the PatchCopyPackageFile SearchForAndReplace parameter if all you are doing is changing the root and not the relative path of assets */
UE_INTERNAL virtual TMap<FString, FString> GetPatchCopyMappingsForRootRename(const FString& SrcRoot, const FString& DstRoot, const FString& SrcBaseDir, const TArray<TPair<FString, FString>>& SourceAndDestFiles, const TMap<FString, FString>& MountPointReplacements) const = 0;
/** Generates additional entries for the PatchCopyPackageFile SearchForAndReplace parameter based on the specified package paths mapping */
UE_INTERNAL virtual TMap<FString, FString> GetAdditionalPatchCopyMappings(const TMap<FString, FString>& SourceAndDestPackages) const = 0;
/** Opens editor for assets */
UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Asset Tools", meta = (DeprecatedFunction, DeprecationMessage = "Please use UAssetEditorSubsystem::OpenEditorForAssets instead."))
virtual void OpenEditorForAssets(const TArray<UObject*>& Assets) = 0;