Files
UnrealEngine/Engine/Plugins/Experimental/PlainProps/Source/Private/PlainPropsDiff.cpp
2025-05-18 13:04:45 +08:00

563 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PlainPropsDiff.h"
#include "PlainPropsBind.h"
#include "PlainPropsBuild.h"
#include "PlainPropsInternalBind.h"
#include "PlainPropsInternalDiff.h"
#include "PlainPropsInternalFormat.h"
#include "PlainPropsInternalRead.h"
namespace PlainProps
{
inline bool DiffMember(FLeafMemberBinding Member, const void* OwnerA, const void* OwnerB, const FBindContext&, FOptionalMemberId)
{
return !!DiffLeaf(At(OwnerA, Member.Offset), At(OwnerB, Member.Offset), Member.Leaf);
}
inline bool DiffMember(FLeafMemberBinding Member, const void* OwnerA, const void* OwnerB, FDiffContext& Ctx, FOptionalMemberId Name)
{
const uint8* A = At(OwnerA, Member.Offset);
const uint8* B = At(OwnerB, Member.Offset);
if (DiffLeaf(A, B, Member.Leaf))
{
Ctx.Out.Add({Member.Leaf.Pack(), Name, {Member.Enum}, A, B});
return true;
}
return false;
}
template<typename ContextType>
bool DiffMember(FRangeMemberBinding Member, const void* OwnerA, const void* OwnerB, ContextType& Ctx, FOptionalMemberId Name)
{
FRangeBinding Binding = Member.RangeBindings[0];
FMemberBindType InnerType = Member.InnerTypes[0];
EMemberKind InnerKind = InnerType.GetKind();
const uint8* A = At(OwnerA, Member.Offset);
const uint8* B = At(OwnerB, Member.Offset);
bool bDiff;
if (Binding.IsLeafBinding())
{
bDiff = Binding.AsLeafBinding().DiffLeaves(A, B);
}
else
{
const IItemRangeBinding& ItemBinding = Binding.AsItemBinding();
if (InnerKind == EMemberKind::Struct)
{
bDiff = DiffItemRange(A, B, ItemBinding, Ctx, Member.InnermostSchema.Get().AsStructBindId());
}
else if (InnerKind == EMemberKind::Leaf)
{
// Todo: float/double ranges should use PreciseFP
bDiff = DiffItemRange(A, B, ItemBinding, Ctx, SizeOf(GetItemWidth(InnerType.AsLeaf())));
}
else
{
bDiff = DiffItemRange(A, B, ItemBinding, Ctx, GetInnerRange(Member));
}
}
if constexpr (std::is_same_v<ContextType, FDiffContext>)
{
if (bDiff)
{
Ctx.Out.Add({FMemberBindType(Binding.GetSizeType()), Name, {.Range = Binding}, A, B});
}
}
return bDiff;
}
template<typename ContextType>
bool DiffMembers(FBindId Id, const void* A, const void* B, ContextType& Ctx);
template<typename ContextType>
inline bool DiffStruct(FBindId Id, const void* A, const void* B, ContextType& Ctx)
{
if (const ICustomBinding* Custom = Ctx.Customs.FindStruct(Id))
{
return Custom->DiffCustom(A, B, Ctx);
}
return DiffMembers(Id, A, B, Ctx);
}
static bool DiffMember(FStructMemberBinding Member, const void* OwnerA, const void* OwnerB, const FBindContext& Ctx, FMemberId)
{
return DiffStruct(Member.Id, At(OwnerA, Member.Offset), At(OwnerB, Member.Offset), Ctx);
}
static bool DiffMember(FStructMemberBinding Member, const void* OwnerA, const void* OwnerB, FDiffContext& Ctx, FMemberId Name)
{
const uint8* A = At(OwnerA, Member.Offset);
const uint8* B = At(OwnerB, Member.Offset);
if (DiffStruct(Member.Id, A, B, Ctx))
{
Ctx.Out.Add({FMemberBindType(Member.Type), Name, {.Struct = Member.Id}, A, B});
return true;
}
return false;
}
template<typename ContextType>
bool DiffMember(/* in-out */ FMemberVisitor& It, const void* A, const void* B, ContextType& Ctx, FMemberId Name)
{
switch (It.PeekKind())
{
case EMemberKind::Leaf: return DiffMember(It.GrabLeaf(), A, B, Ctx, Name);
case EMemberKind::Range: return DiffMember(It.GrabRange(), A, B, Ctx, Name);
case EMemberKind::Struct: return DiffMember(It.GrabStruct(), A, B, Ctx, Name);
}
return true;
}
template<typename ContextType>
bool DiffMembers(FBindId Id, const void* A, const void* B, ContextType& Ctx)
{
const FStructDeclaration* Declaration;
const FSchemaBinding& Schema = Ctx.Schemas.GetStruct(Id, /* out */ Declaration);
FMemberVisitor It(Schema);
if (Schema.HasSuper())
{
if (DiffMembers(It.GrabSuper(), A, B, Ctx))
{
return true;
}
}
for (FMemberId Name : Declaration->GetMemberOrder())
{
if (DiffMember(/* in-out */It, A, B, Ctx, Name))
{
return true;
}
}
check(!It.HasMore());
return false;
}
template<typename ContextType>
bool DiffItem(const void* A, const void* B, ContextType& Ctx, FRangeMemberBinding Range)
{
return DiffMember(Range, A, B, Ctx, NoId);
}
template<typename ContextType>
bool DiffItem(const void* A, const void* B, ContextType& Ctx, FBindId Id)
{
return DiffStruct(Id, A, B, Ctx);
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool DiffStructs(const void* A, const void* B, FBindId Id, const FBindContext& Ctx)
{
return DiffStruct(Id, A, B, Ctx);
}
bool DiffStructs(const void* A, const void* B, FBindId Id, FDiffContext& Ctx)
{
return DiffStruct(Id, A, B, Ctx);
}
bool DiffLeaves(float A, float B)
{
return !::UE::PreciseFPEqual(A, B);
}
bool DiffLeaves(double A, double B)
{
return !::UE::PreciseFPEqual(A, B);
}
bool DiffRanges(const void* A, const void* B, const IItemRangeBinding& Binding, FUnpackedLeafType Leaf)
{
// Todo: float/double ranges should use PreciseFP
return DiffItemRange(A, B, Binding, /* dummy ctx */ Binding, SizeOf(Leaf.Width));
}
bool DiffRanges(const void* A, const void* B, const IItemRangeBinding& Binding, FBindId ItemType, const FBindContext& Ctx)
{
return DiffItemRange(A, B, Binding, Ctx, ItemType);
}
bool DiffRanges(const void* A, const void* B, const IItemRangeBinding& Binding, FBindId ItemType, FDiffContext& Ctx)
{
return DiffItemRange(A, B, Binding, Ctx, ItemType);
}
bool DiffRanges(const void* A, const void* B, const IItemRangeBinding& Binding, FRangeMemberBinding ItemType, const FBindContext& Ctx)
{
return DiffItemRange(A, B, Binding, Ctx, ItemType);
}
bool DiffRanges(const void* A, const void* B, const IItemRangeBinding& Binding, FRangeMemberBinding ItemType, FDiffContext& Ctx)
{
return DiffItemRange(A, B, Binding, Ctx, ItemType);
}
////////////////////////////////////////////////////////////////////////////////
static uint64 CalculateSize(const FSchemaBatch& In)
{
uint32 NumParameters = 0;
for (FParametricType ParametricType : In.GetParametricTypes())
{
NumParameters += ParametricType.Parameters.NumParameters;
}
const uint8* End = reinterpret_cast<const uint8*>(In.GetFirstParameter() + NumParameters);
const uint64 Size = End - reinterpret_cast<const uint8*>(&In);
return Size;
}
static bool operator==(const FSchemaBatch& A, const FSchemaBatch& B)
{
const uint64 SizeA = CalculateSize(A);
const uint64 SizeB = CalculateSize(B);
return SizeA == SizeB && FMemory::Memcmp(&A, &B, SizeA) == 0;
}
static bool operator==(const FStructSchema& A, const FStructSchema& B)
{
const uint64 SizeA = CalculateSize(A);
const uint64 SizeB = CalculateSize(B);
return SizeA == SizeB && FMemory::Memcmp(&A, &B, SizeA) == 0;
}
static bool operator==(const FEnumSchema& A, const FEnumSchema& B)
{
const uint64 SizeA = CalculateSize(A);
const uint64 SizeB = CalculateSize(B);
return SizeA == SizeB && FMemory::Memcmp(&A, &B, SizeA) == 0;
}
static bool operator!=(const FSchemaBatch& A, const FSchemaBatch& B) { return !(A == B); }
static bool operator!=(const FStructSchema& A, const FStructSchema& B) { return !(A == B); }
static bool operator!=(const FEnumSchema& A, const FEnumSchema& B) { return !(A == B); }
////////////////////////////////////////////////////////////////////////////////
static bool DiffStructSchema(const FStructSchema& A, const FStructSchema& B)
{
const bool Diff = A != B;
#if DO_CHECK
check(A.Type == B.Type);
check(A.NumMembers == B.NumMembers);
check(A.NumRangeTypes == B.NumRangeTypes);
check(A.NumInnerSchemas == B.NumInnerSchemas);
check(A.Inheritance == B.Inheritance);
check(A.IsDense == B.IsDense);
check(EqualItems(A.GetMemberTypes(), B.GetMemberTypes()));
check(EqualItems(A.GetRangeTypes(), B.GetRangeTypes()));
check(EqualItems(A.GetMemberNames(), B.GetMemberNames()));
check(EqualItems(
MakeArrayView(A.GetInnerSchemas(), A.NumInnerSchemas),
MakeArrayView(B.GetInnerSchemas(), B.NumInnerSchemas)));
check(A.GetSuperSchema() == B.GetSuperSchema());
check(A == B);
#endif
return Diff;
}
static bool DiffEnumSchema(const FEnumSchema& A, const FEnumSchema& B)
{
const bool Diff = A != B;
#if DO_CHECK
check(A.Type == B.Type);
check(A.FlagMode == B.FlagMode);
check(A.ExplicitConstants == B.ExplicitConstants);
check(A.Width == B.Width);
check(A.Num == B.Num);
check(EqualItems(MakeArrayView(A.Footer, A.Num), MakeArrayView(B.Footer, B.Num)));
if (A.Width == B.Width)
{
switch (A.Width)
{
case ELeafWidth::B8: check(EqualItems(GetConstants<uint8 >(A), GetConstants<uint8 >(B))); break;
case ELeafWidth::B16: check(EqualItems(GetConstants<uint16>(A), GetConstants<uint16>(B))); break;
case ELeafWidth::B32: check(EqualItems(GetConstants<uint32>(A), GetConstants<uint32>(B))); break;
case ELeafWidth::B64: check(EqualItems(GetConstants<uint64>(A), GetConstants<uint64>(B))); break;
}
}
check(A == B);
#endif
return Diff;
}
static bool DiffSchemas(const FSchemaBatch& A, const FSchemaBatch& B)
{
const bool Diff = A != B;
#if DO_CHECK
check(A.NumNestedScopes == B.NumNestedScopes);
check(A.NestedScopesOffset == B.NestedScopesOffset);
check(A.NumParametricTypes == B.NumParametricTypes);
check(A.NumSchemas == B.NumSchemas);
check(A.NumStructSchemas == B.NumStructSchemas);
check(EqualItems(A.GetSchemaOffsets(), B.GetSchemaOffsets()));
check(EqualItems(A.GetNestedScopes(), B.GetNestedScopes()));
check(EqualItems(A.GetParametricTypes(), B.GetParametricTypes()));
{
TSchemaRange<const FStructSchema> AA = GetStructSchemas(A);
TSchemaRange<const FStructSchema> BB = GetStructSchemas(B);
TSchemaIterator<const FStructSchema> ItA = AA.begin();
TSchemaIterator<const FStructSchema> ItB = BB.begin();
for (; ItA != AA.end() && ItB != BB.end(); ++ItA, ++ItB)
{
check(!DiffStructSchema(*ItA, *ItB));
}
}
{
TSchemaRange<const FEnumSchema> AA = GetEnumSchemas(A);
TSchemaRange<const FEnumSchema> BB = GetEnumSchemas(B);
TSchemaIterator<const FEnumSchema> ItA = AA.begin();
TSchemaIterator<const FEnumSchema> ItB = BB.begin();
for (; ItA != AA.end() && ItB != BB.end(); ++ItA, ++ItB)
{
check(!DiffEnumSchema(*ItA, *ItB));
}
}
check(!Diff);
#endif
return Diff;
}
bool DiffSchemas(FSchemaBatchId A, FSchemaBatchId B)
{
return DiffSchemas(GetReadSchemas(A), GetReadSchemas(B));
}
////////////////////////////////////////////////////////////////////////////////
static bool DiffLeaf(FLeafView A, FLeafView B)
{
if (A.Leaf != B.Leaf || A.Enum != B.Enum)
{
return true;
}
if (A.Leaf.Type == ELeafType::Bool)
{
return A.Value.bValue != B.Value.bValue;
}
switch (A.Leaf.Width)
{
case ELeafWidth::B8: return !!FMemory::Memcmp(A.Value.Ptr, B.Value.Ptr, 1);
case ELeafWidth::B16: return !!FMemory::Memcmp(A.Value.Ptr, B.Value.Ptr, 2);
case ELeafWidth::B32: return !!FMemory::Memcmp(A.Value.Ptr, B.Value.Ptr, 4);
case ELeafWidth::B64: return !!FMemory::Memcmp(A.Value.Ptr, B.Value.Ptr, 8);
}
unimplemented();
return false;
}
static bool DiffLeaves(FUnpackedLeafType Leaf, FLeafRangeView A, FLeafRangeView B, uint64& OutIdx)
{
OutIdx = ~0u;
switch (Leaf.Type)
{
case ELeafType::Bool:
check(Leaf.Width == ELeafWidth::B8);
return DiffItems(A.AsBools(), B.AsBools(), OutIdx);
case ELeafType::IntS:
switch (Leaf.Width)
{
case ELeafWidth::B8: return DiffItems(A.AsS8s(), B.AsS8s(), OutIdx);
case ELeafWidth::B16: return DiffItems(A.AsS16s(), B.AsS16s(), OutIdx);
case ELeafWidth::B32: return DiffItems(A.AsS32s(), B.AsS32s(), OutIdx);
case ELeafWidth::B64: return DiffItems(A.AsS64s(), B.AsS64s(), OutIdx);
}
case ELeafType::IntU:
switch (Leaf.Width)
{
case ELeafWidth::B8: return DiffItems(A.AsU8s(), B.AsU8s(), OutIdx);
case ELeafWidth::B16: return DiffItems(A.AsU16s(), B.AsU16s(), OutIdx);
case ELeafWidth::B32: return DiffItems(A.AsU32s(), B.AsU32s(), OutIdx);
case ELeafWidth::B64: return DiffItems(A.AsU64s(), B.AsU64s(), OutIdx);
}
case ELeafType::Float:
if (Leaf.Width == ELeafWidth::B32)
{
return DiffItems(A.AsFloats(), B.AsFloats(), OutIdx);
}
check(Leaf.Width == ELeafWidth::B64);
return DiffItems(A.AsDoubles(), B.AsDoubles(), OutIdx);
case ELeafType::Hex:
// PP-TEXT: Implement DiffItems(Hex)
check(Leaf.Type != ELeafType::Hex);
break;
case ELeafType::Enum:
switch (Leaf.Width)
{
case ELeafWidth::B8: return DiffItems(A.AsUnderlyingValues<uint8>(), B.AsUnderlyingValues<uint8>(), OutIdx);
case ELeafWidth::B16: return DiffItems(A.AsUnderlyingValues<uint16>(), B.AsUnderlyingValues<uint16>(), OutIdx);
case ELeafWidth::B32: return DiffItems(A.AsUnderlyingValues<uint32>(), B.AsUnderlyingValues<uint32>(), OutIdx);
case ELeafWidth::B64: return DiffItems(A.AsUnderlyingValues<uint64>(), B.AsUnderlyingValues<uint64>(), OutIdx);
}
case ELeafType::Unicode:
switch (Leaf.Width)
{
case ELeafWidth::B8: return DiffItems(A.AsUtf8(), B.AsUtf8(), OutIdx);
case ELeafWidth::B16: return DiffItems(A.AsUtf16(), B.AsUtf16(), OutIdx);
case ELeafWidth::B32: return DiffItems(A.AsUtf32(), B.AsUtf32(), OutIdx);
case ELeafWidth::B64: check(false);
}
}
unimplemented();
return false;
}
static bool DiffMembers(FStructView A, FStructView B, FReadDiffPath& Out);
static bool DiffRange(ERangeSizeType NumType, FRangeView A, FRangeView B, FReadDiffPath& Out);
static bool DiffStructs(FStructType Struct, FStructRangeView A, FStructRangeView B, FReadDiffPath& Out)
{
uint64 NumA = A.Num();
uint64 NumB = B.Num();
uint64 DiffIdx = 0;
bool bDiff = false;
FStructRangeIterator ItB = B.begin();
for (FStructView ItA : A)
{
if (DiffIdx >= NumB)
{
break;
}
bDiff = DiffMembers(ItA, *ItB, Out);
if (bDiff)
{
break;
}
++ItB;
++DiffIdx;
}
if (bDiff || NumA != NumB)
{
Out.Emplace(FMemberType(Struct), NoId, NoId, DiffIdx);
return true;
}
return false;
}
static bool DiffRanges(ERangeSizeType NumType, FNestedRangeView A, FNestedRangeView B, FReadDiffPath& Out)
{
uint64 NumA = A.Num();
uint64 NumB = B.Num();
uint64 DiffIdx = 0;
bool bDiff = false;
FNestedRangeIterator ItB = B.begin();
for (FRangeView ItA : A)
{
if (DiffIdx >= NumB)
{
break;
}
bDiff = DiffRange(NumType, ItA, *ItB, Out);
if (bDiff)
{
break;
}
++ItB;
++DiffIdx;
}
if (bDiff || NumA != NumB)
{
Out.Emplace(FMemberType(NumType), NoId, NoId, DiffIdx);
return true;
}
return false;
}
static bool DiffLeaves(FUnpackedLeafType Leaf, FLeafRangeView A, FLeafRangeView B, FReadDiffPath& Out)
{
uint64 DiffIdx;
if (DiffLeaves(Leaf, A, B, DiffIdx))
{
Out.Emplace(Leaf.Pack(), NoId, NoId, DiffIdx);
return true;
}
return false;
}
static bool DiffRange(ERangeSizeType NumType, FRangeView A, FRangeView B, FReadDiffPath& Out)
{
bool bDiff = A.GetItemType() != B.GetItemType();
if (!bDiff)
{
switch (A.GetItemType().GetKind())
{
case EMemberKind::Leaf:
bDiff = DiffLeaves(A.GetItemType().AsLeaf(), A.AsLeaves(), B.AsLeaves(), Out);
break;
case EMemberKind::Struct:
bDiff = DiffStructs(A.GetItemType().AsStruct(), A.AsStructs(), B.AsStructs(), Out);
break;
case EMemberKind::Range:
bDiff = DiffRanges(A.GetItemType().AsRange().MaxSize, A.AsRanges(), B.AsRanges(), Out);
break;
}
}
return bDiff;
}
static bool DiffMembers(FStructView A, FStructView B, FReadDiffPath& Out)
{
FMemberReader ItA(A);
FMemberReader ItB(B);
for (; ItA.HasMore() && ItB.HasMore(); )
{
FOptionalMemberId NameA = ItA.PeekName();
FOptionalMemberId NameB = ItB.PeekName();
FMemberType TypeA = ItA.PeekType();
FMemberType TypeB = ItB.PeekType();
bool bDiff = TypeA != TypeB || NameA != NameB;
if (!bDiff)
{
switch (TypeA.GetKind())
{
case EMemberKind::Leaf:
bDiff = DiffLeaf(ItA.GrabLeaf(), ItB.GrabLeaf());
break;
case EMemberKind::Struct:
bDiff = DiffMembers(ItA.GrabStruct(), ItB.GrabStruct(), Out);
break;
case EMemberKind::Range:
bDiff = DiffRange(TypeA.AsRange().MaxSize, ItA.GrabRange(), ItB.GrabRange(), Out);
break;
}
}
if (bDiff)
{
Out.Emplace(TypeA, A.Schema.Id, NameA);
return true;
}
}
if (ItA.HasMore())
{
Out.Emplace(ItA.PeekType(), A.Schema.Id, ItA.PeekName());
return true;
}
if (ItB.HasMore())
{
Out.Emplace(ItB.PeekType(), A.Schema.Id, ItB.PeekName());
return true;
}
return false;
}
bool DiffStruct(FStructView A, FStructView B, FReadDiffPath& Out)
{
return DiffMembers(A, B, Out);
}
} // namespace PlainProps