Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Public/DiffUtils.h
2025-05-18 13:04:45 +08:00

432 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/Array.h"
#include "Containers/Set.h"
#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_5
#include "Containers/SparseArray.h"
#include "CoreMinimal.h"
#endif // UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_5
#include "Containers/UnrealString.h"
#include "Delegates/Delegate.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "Internationalization/Text.h"
#include "Math/Color.h"
#include "Misc/AssertionMacros.h"
#include "PropertyPath.h"
#include "Templates/SharedPointer.h"
#include "Templates/TypeHash.h"
#include "UObject/NameTypes.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakFieldPtr.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SWidget.h"
#include "Widgets/Views/STreeView.h"
#include "PropertyHandle.h"
class FBlueprintDifferenceTreeEntry;
class ISourceControlRevision;
class SWidget;
class UBlueprint;
class UObject;
class UStruct;
struct FRevisionInfo;
template <typename ItemType> class STreeView;
struct FResolvedProperty
{
explicit FResolvedProperty()
: Object(nullptr)
, Property(nullptr)
{
}
FResolvedProperty(const void* InObject, const FProperty* InProperty)
: Object(InObject)
, Property(InProperty)
{
}
inline bool operator==(const FResolvedProperty& RHS) const
{
return Object == RHS.Object && Property == RHS.Property;
}
inline bool operator!=(const FResolvedProperty& RHS) const { return !(*this == RHS); }
const void* Object;
const FProperty* Property;
};
/**
* FPropertySoftPath is a string of identifiers used to identify a single member of a UObject. It is primarily
* used when comparing unrelated UObjects for Diffing and Merging, but can also be used as a key select
* a property in a SDetailsView.
*/
struct FPropertySoftPath
{
UNREALED_API FPropertySoftPath();
UNREALED_API FPropertySoftPath(const FProperty* Property);
UNREALED_API FPropertySoftPath(TArray<FName> InPropertyChain);
UNREALED_API FPropertySoftPath(FPropertyPath InPropertyPath);
UNREALED_API FPropertySoftPath(const FPropertySoftPath& MainPropertyPath, const FPropertySoftPath& SubPropertyPath);
UNREALED_API FPropertySoftPath(const FPropertySoftPath& SubPropertyPath, const FProperty* LeafProperty);
UNREALED_API FPropertySoftPath(const FPropertySoftPath& SubPropertyPath, int32 ContainerIndex);
UNREALED_API FResolvedProperty Resolve(const UObject* Object) const;
UNREALED_API FResolvedProperty Resolve(const UStruct* Struct, const void* StructData) const;
UNREALED_API FPropertyPath ResolvePath(const UObject* Object) const;
UNREALED_API FPropertySoftPath GetRootProperty(int32 Depth) const;
UNREALED_API int32 TryReadIndex(int32 Index) const;
/**
* Provides a string of all elements in the property chain using the following format
* e.g., "X[3] Y Z"
* @param NumberOfElements Optional argument to return a string containing only the last 'NumberOfElements' elements of the path
* @return String of all elements in the property chain
*/
UNREALED_API FString ToDisplayName(const int32 NumberOfElements = INDEX_NONE) const;
/**
* Indicates whether a given path is a base path of the current path.
* @param PotentialBasePropertyPath The path to look for as a base common path
* @return Whether the path has the provided path as base path or not
*/
bool IsSubPropertyMatch(const FPropertySoftPath& PotentialBasePropertyPath) const
{
if (PropertyChain.Num() <= PotentialBasePropertyPath.PropertyChain.Num())
{
return false;
}
for (int32 CurChainElement = 0; CurChainElement < PotentialBasePropertyPath.PropertyChain.Num(); CurChainElement++)
{
if (PotentialBasePropertyPath.PropertyChain[CurChainElement] != PropertyChain[CurChainElement])
{
return false;
}
}
return true;
}
/**
* Indicates whether a given list of property names is a base path of the current path.
* @param PotentialBasePropertyNameArray The list of property names to look for as a base common path
* @return Whether the path has the provided list of names as base path or not
*/
bool IsSubPropertyMatch(TConstArrayView<FName> PotentialBasePropertyNameArray) const
{
if (PropertyChain.Num() <= PotentialBasePropertyNameArray.Num())
{
return false;
}
for (int32 CurChainElement = 0; CurChainElement < PotentialBasePropertyNameArray.Num(); CurChainElement++)
{
if (PotentialBasePropertyNameArray[CurChainElement] != PropertyChain[CurChainElement].PropertyName)
{
return false;
}
}
return true;
}
/**
* Indicates whether a given property name is a base path of the current path.
* @param PotentialBasePropertyName Name of the single property to match
* @return Whether the path contains a single element with the name provided or not
*/
bool IsSubPropertyMatch(const FName PotentialBasePropertyName) const
{
return IsSubPropertyMatch(MakeArrayView({PotentialBasePropertyName}));
}
inline bool operator==(FPropertySoftPath const& RHS) const
{
return PropertyChain == RHS.PropertyChain;
}
inline bool operator!=(FPropertySoftPath const& RHS) const
{
return !(*this == RHS);
}
inline FPropertySoftPath& operator+=(FPropertySoftPath const& RHS)
{
PropertyChain.Append(RHS.PropertyChain);
return *this;
}
private:
struct FChainElement
{
// FName of the property
FName PropertyName;
// Display string of the property
FString DisplayString;
FChainElement(FName InPropertyName, const FString& InDisplayString = FString())
: PropertyName(InPropertyName)
, DisplayString(InDisplayString)
{
if (DisplayString.IsEmpty())
{
DisplayString = PropertyName.ToString();
}
}
FChainElement(const FProperty* Property)
{
if (Property)
{
PropertyName = Property->GetFName();
DisplayString = Property->GetAuthoredName();
}
}
inline bool operator==(FChainElement const& RHS) const
{
return PropertyName == RHS.PropertyName;
}
inline bool operator!=(FChainElement const& RHS) const
{
return !(*this == RHS);
}
};
static int32 TryReadIndex(const TArray<FChainElement>& LocalPropertyChain, int32& OutIndex);
friend uint32 GetTypeHash( FPropertySoftPath const& Path );
TArray<FChainElement> PropertyChain;
const UStruct* RootTypeHint;
};
struct FSCSIdentifier
{
FName Name;
TArray< int32 > TreeLocation;
};
struct FSCSResolvedIdentifier
{
FSCSIdentifier Identifier;
const UObject* Object;
};
FORCEINLINE bool operator==( const FSCSIdentifier& A, const FSCSIdentifier& B )
{
return A.Name == B.Name && A.TreeLocation == B.TreeLocation;
}
FORCEINLINE bool operator!=(const FSCSIdentifier& A, const FSCSIdentifier& B)
{
return !(A == B);
}
FORCEINLINE uint32 GetTypeHash( FPropertySoftPath const& Path )
{
uint32 Ret = 0;
for (const FPropertySoftPath::FChainElement& PropertyElement : Path.PropertyChain)
{
Ret = Ret ^ GetTypeHash(PropertyElement.PropertyName);
}
return Ret;
}
// Trying to restrict us to this typedef because I'm a little skeptical about hashing FPropertySoftPath safely
typedef TSet< FPropertySoftPath > FPropertySoftPathSet;
namespace EPropertyDiffType
{
enum Type
{
Invalid,
PropertyAddedToA,
PropertyAddedToB,
PropertyValueChanged,
};
}
struct FSingleObjectDiffEntry
{
FSingleObjectDiffEntry()
: Identifier()
, DiffType(EPropertyDiffType::Invalid)
{
}
FSingleObjectDiffEntry(const FPropertySoftPath& InIdentifier, EPropertyDiffType::Type InDiffType )
: Identifier(InIdentifier)
, DiffType(InDiffType)
{
}
FPropertySoftPath Identifier;
EPropertyDiffType::Type DiffType;
};
namespace ETreeDiffType
{
enum Type
{
NODE_ADDED,
NODE_REMOVED,
NODE_TYPE_CHANGED,
NODE_PROPERTY_CHANGED,
NODE_MOVED,
NODE_CORRUPTED,
NODE_FIXED
/** We could potentially try to identify hierarchy reorders separately from add/remove */
};
}
struct FSCSDiffEntry
{
FSCSDiffEntry( const FSCSIdentifier& InIdentifier, ETreeDiffType::Type InDiffType, const FSingleObjectDiffEntry& InPropertyDiff )
: TreeIdentifier(InIdentifier)
, DiffType(InDiffType)
, PropertyDiff(InPropertyDiff)
{
}
FSCSIdentifier TreeIdentifier;
ETreeDiffType::Type DiffType;
FSingleObjectDiffEntry PropertyDiff;
};
struct FSCSDiffRoot
{
// use indices in FSCSIdentifier::TreeLocation to find hierarchy..
TArray< FSCSDiffEntry > Entries;
};
namespace DiffUtils
{
DECLARE_DELEGATE_OneParam(FOnGenerateCustomDiffEntries, TArray<FSingleObjectDiffEntry>&)
DECLARE_DELEGATE_RetVal_TwoParams(TSharedRef<SWidget>, FOnGenerateCustomDiffEntryWidget, const FSingleObjectDiffEntry&, FText&)
DECLARE_DELEGATE_FourParams(FOnOrganizeDiffEntries,
TArray<TSharedPtr<FBlueprintDifferenceTreeEntry>>&,
const TArray<FSingleObjectDiffEntry>&,
TFunctionRef<TSharedPtr<FBlueprintDifferenceTreeEntry>(const FSingleObjectDiffEntry&)>,
TFunctionRef<TSharedPtr<FBlueprintDifferenceTreeEntry>(FText&)>)
struct FDiffParameters
{
FDiffParameters() = default;
explicit FDiffParameters(const FPropertySoftPath& RootPath)
: RootPath(RootPath)
{
}
FPropertySoftPath RootPath;
/** Predicate evaluated on visited properties to determine if they should be part of the comparison. */
TFunction<bool(const FProperty&)> ShouldIgnorePropertyPredicate;
/** Indicates if the elements of a static array should be compared. Otherwise, only the size is compared. */
bool bShouldDiffArrayElements = true;
};
UNREALED_API const UObject* GetCDO(const UBlueprint* ForBlueprint);
UE_DEPRECATED(5.3, "DiffUtils now requires root objects so that object topology can be meaningfully compared.")
UNREALED_API void CompareUnrelatedStructs(const UStruct* StructA, const void* A, const UStruct* StructB, const void* B, TArray<FSingleObjectDiffEntry>& OutDifferingProperties);
UNREALED_API void CompareUnrelatedStructs(const UStruct* StructA, const void* A, const UObject* OwningOuterA, const UStruct* StructB, const void* B, const
UObject* OwningOuterB, TArray<FSingleObjectDiffEntry>& OutDifferingProperties);
UNREALED_API void CompareUnrelatedObjects(const UObject* A, const UObject* B, TArray<FSingleObjectDiffEntry>& OutDifferingProperties);
UNREALED_API void CompareUnrelatedSCS(const UBlueprint* Old, const TArray< FSCSResolvedIdentifier >& OldHierarchy, const UBlueprint* New, const TArray< FSCSResolvedIdentifier >& NewHierarchy, FSCSDiffRoot& OutDifferingEntries );
UE_DEPRECATED(5.3, "DiffUtils now requires root objects so that object topology can be meaningfully compared.")
UNREALED_API bool Identical(const FResolvedProperty& AProp, const FResolvedProperty& BProp, const FPropertySoftPath& RootPath, TArray<FPropertySoftPath>& DifferingProperties);
UE_DEPRECATED(5.5, "Use version with FDiffParameters instead.")
UNREALED_API bool Identical(const FResolvedProperty& AProp, const FResolvedProperty& BProp, const UObject* OwningOuterA, const UObject* OwningOuterB, const FPropertySoftPath& RootPath, TArray<FPropertySoftPath>& DifferingProperties);
/**
* DiffUtils now requires root objects so that object topology can be meaningfully compared.
* DiffUtils::Identical works similar to FProperty::Identical except when a UObject is found, that is in OwningOuter*,
* it's compared by topology instead. This allows sub-objects to diff correctly.
*/
UNREALED_API bool Identical(const FResolvedProperty& AProp, const FResolvedProperty& BProp, const UObject* OwningOuterA, const UObject* OwningOuterB);
UNREALED_API bool Identical(const FResolvedProperty& AProp, const FResolvedProperty& BProp, const UObject* OwningOuterA, const UObject* OwningOuterB, FDiffParameters DiffParameters, TArray<FPropertySoftPath>& DifferingProperties);
UNREALED_API bool Identical(const TSharedPtr<IPropertyHandle>& PropertyHandleA, const TSharedPtr<IPropertyHandle>& PropertyHandleB, const TArray<TWeakObjectPtr<UObject>>& OwningOutersA = {}, const TArray<TWeakObjectPtr<UObject>>& OwningOutersB = {});
UNREALED_API bool Identical(TArray<FPropertySoftPath>& OutDifferingProperties, const TSharedPtr<IPropertyHandle>& PropertyHandleA, const TSharedPtr<IPropertyHandle>& PropertyHandleB, const TArray<TWeakObjectPtr<UObject>>& OwningOutersA = {}, const TArray<TWeakObjectPtr<UObject>>& OwningOutersB = {});
UNREALED_API TArray<FPropertySoftPath> GetVisiblePropertiesInOrderDeclared(const UStruct* ForStruct, const FPropertySoftPath& Scope = FPropertySoftPath());
UNREALED_API TArray<FPropertyPath> ResolveAll(const UObject* Object, const TArray<FPropertySoftPath>& InSoftProperties);
UNREALED_API TArray<FPropertyPath> ResolveAll(const UObject* Object, const TArray<FSingleObjectDiffEntry>& InDifferences);
/**
* @param InTempPackagePath - filepath of the temporary uasset version (likely in /Saved/Temp/SourceControl)
* @param InOriginalPackagePath - filepath of the original uasset in the content directory. Strictly speaking most assets don't need this parameter set
* but it's needed to instantiate OFPA Actors properly.
*/
UNREALED_API UPackage* LoadPackageForDiff(const FPackagePath& InTempPackagePath, const FPackagePath& InOriginalPackagePath);
UNREALED_API UPackage* LoadPackageForDiff(TSharedPtr<ISourceControlRevision> Revision);
}
DECLARE_DELEGATE(FOnDiffEntryFocused);
DECLARE_DELEGATE_RetVal(TSharedRef<SWidget>, FGenerateDiffEntryWidget);
class FBlueprintDifferenceTreeEntry
{
public:
FBlueprintDifferenceTreeEntry(FOnDiffEntryFocused InOnFocus, FGenerateDiffEntryWidget InGenerateWidget, TArray< TSharedPtr<FBlueprintDifferenceTreeEntry> > InChildren = TArray< TSharedPtr<FBlueprintDifferenceTreeEntry> >())
: OnFocus(InOnFocus)
, GenerateWidget(InGenerateWidget)
, Children(InChildren)
{
check( InGenerateWidget.IsBound() );
}
virtual ~FBlueprintDifferenceTreeEntry() = default;
/** Displays message to user saying there are no differences */
static UNREALED_API TSharedPtr<FBlueprintDifferenceTreeEntry> NoDifferencesEntry();
/** Displays message to user warning that there may be undetected differences */
static UNREALED_API TSharedPtr<FBlueprintDifferenceTreeEntry> UnknownDifferencesEntry();
/** Create category message for the diff UI */
static UNREALED_API TSharedPtr<FBlueprintDifferenceTreeEntry> CreateCategoryEntry(const FText& LabelText, const FText& ToolTipText, FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr<FBlueprintDifferenceTreeEntry> >& Children, bool bHasDifferences);
/** Create category message for the merge UI */
static UNREALED_API TSharedPtr<FBlueprintDifferenceTreeEntry> CreateCategoryEntryForMerge(const FText& LabelText, const FText& ToolTipText, FOnDiffEntryFocused FocusCallback, const TArray< TSharedPtr<FBlueprintDifferenceTreeEntry> >& Children, bool bHasRemoteDifferences, bool bHasLocalDifferences, bool bHasConflicts);
FOnDiffEntryFocused OnFocus;
FGenerateDiffEntryWidget GenerateWidget;
TArray< TSharedPtr<FBlueprintDifferenceTreeEntry> > Children;
};
namespace DiffTreeView
{
UNREALED_API TSharedRef< STreeView<TSharedPtr< FBlueprintDifferenceTreeEntry > > > CreateTreeView(TArray< TSharedPtr<FBlueprintDifferenceTreeEntry> >* DifferencesList);
UNREALED_API int32 CurrentDifference( TSharedRef< STreeView<TSharedPtr< FBlueprintDifferenceTreeEntry > > > TreeView, const TArray< TSharedPtr<class FBlueprintDifferenceTreeEntry> >& Differences );
UNREALED_API void HighlightNextDifference(TSharedRef< STreeView<TSharedPtr< FBlueprintDifferenceTreeEntry > > > TreeView, const TArray< TSharedPtr<class FBlueprintDifferenceTreeEntry> >& Differences, const TArray< TSharedPtr<class FBlueprintDifferenceTreeEntry> >& RootDifferences);
UNREALED_API void HighlightPrevDifference(TSharedRef< STreeView<TSharedPtr< FBlueprintDifferenceTreeEntry > > > TreeView, const TArray< TSharedPtr<class FBlueprintDifferenceTreeEntry> >& Differences, const TArray< TSharedPtr<class FBlueprintDifferenceTreeEntry> >& RootDifferences);
UNREALED_API bool HasNextDifference(TSharedRef< STreeView<TSharedPtr< FBlueprintDifferenceTreeEntry > > > TreeView, const TArray< TSharedPtr<class FBlueprintDifferenceTreeEntry> >& Differences);
UNREALED_API bool HasPrevDifference(TSharedRef< STreeView<TSharedPtr< FBlueprintDifferenceTreeEntry > > > TreeView, const TArray< TSharedPtr<class FBlueprintDifferenceTreeEntry> >& Differences);
}
struct FRevisionInfo;
namespace DiffViewUtils
{
UNREALED_API FLinearColor LookupColor( bool bDiffers, bool bConflicts = false );
UNREALED_API FLinearColor Differs();
UNREALED_API FLinearColor Identical();
UNREALED_API FLinearColor Missing();
UNREALED_API FLinearColor Conflicting();
UNREALED_API FText PropertyDiffMessage(FSingleObjectDiffEntry Difference, FText ObjectName);
UNREALED_API FText SCSDiffMessage(const FSCSDiffEntry& Difference, FText ObjectName);
UNREALED_API FText GetPanelLabel(const UObject* Asset, const FRevisionInfo& Revision, FText Label);
UNREALED_API SHorizontalBox::FSlot::FSlotArguments Box(bool bIsPresent, FLinearColor Color);
}