Files
2025-05-18 13:04:45 +08:00

1772 lines
78 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright Epic Games, Inc. All Rights Reserved.
#if WITH_TESTS
#include "Algo/Compare.h"
#include "Containers/AnsiString.h"
#include "Containers/Map.h"
#include "Containers/Set.h"
#include "Containers/StaticArray.h"
#include "Containers/StringConv.h"
#include "Containers/StringView.h"
#include "Logging/LogMacros.h"
#include "PlainPropsBuildSchema.h"
#include "PlainPropsDiff.h"
#include "PlainPropsIndex.h"
#include "PlainPropsInternalBuild.h"
#include "PlainPropsInternalBuild.h"
#include "PlainPropsInternalFormat.h"
#include "PlainPropsInternalParse.h"
#include "PlainPropsInternalPrint.h"
#include "PlainPropsInternalRead.h"
#include "PlainPropsRead.h"
#include "PlainPropsVisualize.h"
#include "PlainPropsWrite.h"
#include "Templates/UnrealTemplate.h"
#include "Tests/TestHarnessAdapter.h"
DEFINE_LOG_CATEGORY_STATIC(LogPlainPropsTests, Log, All);
namespace PlainProps
{
static bool operator==(FScopeId A, FNestedScopeId B) { return A == FScopeId(B); }
static bool operator==(FScopeId A, FFlatScopeId B) { return A == FScopeId(B); }
static bool operator==(FParametricTypeView A, FParametricTypeView B)
{
return A.Name == B.Name && A.NumParameters == B.NumParameters && !FMemory::Memcmp(A.Parameters, B.Parameters, A.NumParameters * sizeof(FType));
}
////////////////////////////////////////////////////////////////////////////////
TEST_CASE_NAMED(FPlainPropsIndexTest, "System::Core::Serialization::PlainProps::Index", "[Core][PlainProps][SmokeFilter]")
{
SECTION("NestedScope")
{
FScopeId S0{FFlatScopeId{{0}}};
FScopeId S1{FFlatScopeId{{1}}};
FScopeId S2{FFlatScopeId{{2}}};
FNestedScope N01{S0, S1.AsFlat()};
FNestedScope N10{S1, S0.AsFlat()};
FNestedScope N12{S1, S2.AsFlat()};
FNestedScopeIndexer Indexer;
FScopeId S01(Indexer.Index(N01));
FScopeId S10(Indexer.Index(N10));
FScopeId S12(Indexer.Index(N12));
FNestedScope N012{S01, S2.AsFlat()};
FScopeId S012(Indexer.Index(N012));
FNestedScope N0120{S012, S0.AsFlat()};
FScopeId S0120(Indexer.Index(N0120));
CHECK(S01 == Indexer.Index(N01));
CHECK(S10 == Indexer.Index(N10));
CHECK(S12 == Indexer.Index(N12));
CHECK(S012 == Indexer.Index(N012));
CHECK(S0120 == Indexer.Index(N0120));
CHECK(N01 == Indexer.Resolve(S01.AsNested()));
CHECK(N10 == Indexer.Resolve(S10.AsNested()));
CHECK(N12 == Indexer.Resolve(S12.AsNested()));
CHECK(N012 == Indexer.Resolve(S012.AsNested()));
CHECK(N0120 == Indexer.Resolve(S0120.AsNested()));
CHECK(Indexer.Num() == 5);
}
SECTION("ParametricType")
{
FScopeId S0{FFlatScopeId{{0}}};
FScopeId S1{FFlatScopeId{{1}}};
FScopeId S2{FFlatScopeId{{2}}};
FConcreteTypenameId T3{{3}};
FConcreteTypenameId T4{{4}};
FConcreteTypenameId T5{{5}};
FType S0T3 = {S0, FTypenameId{T3}};
FType S1T3 = {S1, FTypenameId{T3}};
FType S2T3 = {S2, FTypenameId{T3}};
FParametricTypeIndexer Indexer;
FParametricTypeId T4_S0T3 = Indexer.Index({T4, 1, &S0T3});
FParametricTypeId T4_S1T3 = Indexer.Index({T4, 1, &S1T3});
CHECK(Indexer.Resolve(T4_S0T3) == FParametricTypeView{T4, 1, &S0T3});
CHECK(Indexer.Resolve(T4_S1T3) == FParametricTypeView{T4, 1, &S1T3});
FType S1T4_S0T3 = {S1, FTypenameId{T4_S0T3}};
FType S2T4_S1T3 = {S2, FTypenameId{T4_S1T3}};
CHECK(S1T4_S0T3.Name.AsParametric() == T4_S0T3);
CHECK(S2T4_S1T3.Name.AsParametric() == T4_S1T3);
FParametricTypeId T5_S0T3_S2T3 = Indexer.Index({T5, {S1T4_S0T3, S2T4_S1T3}});
CHECK(Indexer.Resolve(T5_S0T3_S2T3) == FParametricTypeView{T5, {S1T4_S0T3, S2T4_S1T3}});
CHECK(T4_S0T3 == Indexer.Index({T4, 1, &S0T3}));
CHECK(T4_S1T3 == Indexer.Index({T4, 1, &S1T3}));
CHECK(T5_S0T3_S2T3 == Indexer.Index({T5, {S1T4_S0T3, S2T4_S1T3}}));
CHECK(Indexer.Num() == 3);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline constexpr uint32 TestMagics[] = {0xFEEDF00D, 0xABCD1234, 0xDADADAAA, 0x99887766, 0xF0F1F2F3 };
template<>
void AppendString(FUtf8StringBuilderBase& Out, const FAnsiString& Str)
{
Out.Append(Str);
}
class FTestBatchBuilder final : public TIdIndexer<FAnsiString>, public IDeclarations
{
public:
FTestBatchBuilder(FScratchAllocator& InScratch) : EnumDeclarations(FDebugIds(*this)), Scratch(InScratch) {}
FEnumId DeclareEnum(FType Type, EEnumMode Mode, ELeafWidth Width, std::initializer_list<const char*> Names, std::initializer_list<uint64> Constants);
FEnumId DeclareEnum(const char* Scope, const char* Name, EEnumMode Mode, ELeafWidth Width, std::initializer_list<const char*> Names, std::initializer_list<uint64> Constants);
FDeclId DeclareStruct(FType Type, std::initializer_list<const char*> MemberOrder, TConstArrayView<FMemberSpec> MemberTypes, EMemberPresence Occupancy, FOptionalDeclId Super = NoId);
FDeclId DeclareStruct(const char* Scope, const char* Name, std::initializer_list<const char*> MemberNames, TConstArrayView<FMemberSpec> MemberTypes, EMemberPresence Occupancy, FOptionalDeclId Super = NoId);
const FEnumDeclaration& Get(FEnumId Id) const { return EnumDeclarations.Get(Id); }
const FStructDeclaration& Get(FDeclId Id) const { return *StructDeclarations.FindChecked(Id); }
void AddObject(FDeclId Id, FMemberBuilder&& Members);
TArray64<uint8> Write();
FDebugIds GetDebug() const { return FDebugIds(*this); }
private:
TArray<TPair<FDeclId, FBuiltStruct*>> Objects;
TMap<FDeclId, FStructDeclarationPtr> StructDeclarations;
FEnumDeclarations EnumDeclarations;
FScratchAllocator& Scratch;
TArray<FMemberId> NameMembers(std::initializer_list<const char*> Members)
{
TArray<FMemberId> Out;
Out.Reserve(Members.size());
for (const char* Member : Members)
{
Out.Add(NameMember(Member));
}
return Out;
}
TArray<FEnumerator> MakeEnumerators(TConstArrayView<const char*> InNames, TConstArrayView<uint64> Constants)
{
check(InNames.Num() == Constants.Num());
TArray<FEnumerator> Out;
Out.SetNumUninitialized(InNames.Num());
for (uint32 Idx = 0, Num = Out.Num(); Idx < Num; ++Idx)
{
Out[Idx] = { MakeName(InNames[Idx]), Constants[Idx] };
}
return Out;
}
TArray<char> GetNameData() const;
virtual const FEnumDeclaration* Find(FEnumId Id) const override { return &EnumDeclarations.Get(Id); }
virtual const FStructDeclaration* Find(FStructId Id) const override { return StructDeclarations.FindChecked(FDeclId(Id)); }
virtual FDeclId Lower(FBindId Id) const override
{
checkf(false, TEXT("All struct ids should be declared, nothing is bound with different names in this test suite"));
return LowerCast(Id);
}
};
FDeclId FTestBatchBuilder::DeclareStruct(FType Type, std::initializer_list<const char*> MemberNames, TConstArrayView<FMemberSpec> MemberTypes, EMemberPresence Occupancy, FOptionalDeclId Super)
{
FDeclId Id = IndexDeclId(Type);
StructDeclarations.Emplace(Id, Declare({Id, Super, /* v */ 0, Occupancy, NameMembers(MemberNames), MemberTypes}));
return Id;
}
FDeclId FTestBatchBuilder::DeclareStruct(const char* Scope, const char* Name, std::initializer_list<const char*> MemberNames, TConstArrayView<FMemberSpec> MemberTypes, EMemberPresence Occupancy, FOptionalDeclId Super)
{
return DeclareStruct(MakeType(Scope, Name), MemberNames, MemberTypes, Occupancy, Super);
}
FEnumId FTestBatchBuilder::DeclareEnum(FType Type, EEnumMode Mode, ELeafWidth, std::initializer_list<const char*> InNames, std::initializer_list<uint64> Constants)
{
FEnumId Id = IndexEnum(Type);
EnumDeclarations.Declare(Id, Type, Mode, MakeEnumerators(InNames, Constants), EEnumAliases::Fail);
return Id;
}
FEnumId FTestBatchBuilder::DeclareEnum(const char* Scope, const char* Name, EEnumMode Mode, ELeafWidth Width, std::initializer_list<const char*> InNames, std::initializer_list<uint64> Constants)
{
return DeclareEnum(MakeType(Scope, Name), Mode, Width, InNames, Constants);
}
void FTestBatchBuilder::AddObject(FDeclId Id, FMemberBuilder&& Members)
{
Objects.Emplace(Id, Members.BuildAndReset(Scratch, Get(Id), GetDebug()));
}
TArray64<uint8> FTestBatchBuilder::Write()
{
// Build partial schemas
FSchemasBuilder SchemaBuilders(*this, *this, Scratch, ESchemaFormat::StableNames);
for (const TPair<FDeclId, FBuiltStruct*>& Object : Objects)
{
SchemaBuilders.NoteStructAndMembers(Object.Key, *Object.Value);
}
FBuiltSchemas Schemas = SchemaBuilders.Build();
// Filter out declared but unused names and ids
FWriter Writer(*this, *this, Schemas, ESchemaFormat::StableNames);
// Write names
TArray64<uint8> Out;
TArray64<uint8> Tmp;
for (FNameId Name : Writer.GetUsedNames())
{
const FAnsiString& Str = ResolveName(Name);
WriteData(Tmp, *Str, Str.Len() + 1);
}
WriteInt(Out, TestMagics[0]);
WriteSkippableSlice(Out, Tmp);
Tmp.Reset();
// Write schemas
WriteInt(Out, TestMagics[1]);
Writer.WriteSchemas(/* Out */ Tmp);
WriteAlignmentPadding<uint32>(Out);
WriteInt(Out, IntCastChecked<uint32>(Tmp.Num()));
WriteArray(Out, Tmp);
Tmp.Reset();
// Write objects
WriteInt(Out, TestMagics[2]);
for (const TPair<FDeclId, FBuiltStruct*>& Object : Objects)
{
WriteInt(/* out */ Tmp, TestMagics[3]);
WriteInt(/* out */ Tmp, Writer.GetWriteId(Object.Key).Get().Idx);
Writer.WriteMembers(/* out */ Tmp, Object.Key, *Object.Value);
WriteSkippableSlice(Out, Tmp);
Tmp.Reset();
}
// Write object terminator
WriteSkippableSlice(Out, TConstArrayView64<uint8>());
WriteInt(Out, TestMagics[4]);
return Out;
}
TArray<char> FTestBatchBuilder::GetNameData() const
{
TArray<char> Out;
Out.Reserve(Names.Num() * 100);
for (const FAnsiString& Name : Names)
{
Out.Append(Name.GetCharArray());
check(Out.Last() == '\0');
}
return Out;
}
////////////////////////////////////////////////////////////////////////////////////////////////
class FTestNameReader
{
public:
void Read(FMemoryView Data)
{
check(Names.IsEmpty() && !Data.IsEmpty());
TConstArrayView<char> AllChars(static_cast<const char*>(Data.GetData()), IntCastChecked<int32>(Data.GetSize()));
const char* NextStr = AllChars.GetData();
for (const char& Char : AllChars)
{
if (!Char)
{
Names.Add(NextStr);
NextStr = &Char + 1;
}
}
check(Names.Num() >= 3); // 1 FType and 1 member id at least
check(NextStr == Data.GetDataEnd()); // end with null-terminator
}
uint32 NumNames() const { return IntCastChecked<uint32>(Names.Num()); }
FAnsiStringView operator[](FNameId Id) const { return MakeStringView(Names[Id.Idx]); }
FAnsiStringView operator[](FMemberId Name) const { return MakeStringView(Names[Name.Id.Idx]); }
FAnsiStringView operator[](FOptionalMemberId Name) const { return Name ? operator[](Name.Get()) : "Super"; }
FAnsiStringView operator[](FScopeId Scope) const { return operator[](Scope.AsFlat().Name); }
FAnsiStringView operator[](FTypenameId Name) const { return operator[](Name.AsConcrete().Id); }
private:
TArray<const char*> Names;
};
class FTestBatchIds final : public FStableBatchIds
{
const FTestNameReader& Names;
public:
FTestBatchIds(const FTestNameReader& InNames, FSchemaBatchId Batch) : FStableBatchIds(Batch), Names(InNames) {}
using FStableBatchIds::AppendString;
virtual uint32 NumNames() const override { return Names.NumNames(); }
virtual void AppendString(FUtf8StringBuilderBase& Out, FNameId Name) const override { Out.Append(Names[Name]); }
};
[[nodiscard]] FSchemaBatchId ParseBatch(TArray64<uint8>& OutData, TArray<FStructView>& OutObjects, FUtf8StringView YamlView)
{
// Parse yaml
ParseYamlBatch(OutData, YamlView);
// Grab and mount parsed schemas
FByteReader It(MakeMemoryView(OutData));
const uint32 SchemasSize = It.Grab<uint32>();
FMemoryView SchemasView = It.GrabSlice(SchemasSize);
const FSchemaBatch* Schemas = ValidateSchemas(SchemasView);
FSchemaBatchId Batch = MountReadSchemas(Schemas);
// Grab parsed objects
while (uint64 NumBytes = It.GrabVarIntU())
{
FByteReader ObjIt(It.GrabSlice(NumBytes));
FStructSchemaId Schema = { ObjIt.Grab<uint32>() };
OutObjects.Add({ { Schema, Batch }, ObjIt });
}
return Batch;
}
static void RoundtripText(const FBatchIds& BatchIds, TConstArrayView<FStructView> Objects)
{
// Print yaml
TUtf8StringBuilder<4096> Yaml;
PrintYamlBatch(Yaml, BatchIds, Objects);
FUtf8StringView YamlView = Yaml.ToView();
// Log yaml
auto Wide = StringCast<TCHAR>(YamlView.GetData(), YamlView.Len());
UE_LOG(LogPlainPropsTests, Log, TEXT("Schemas with StableNames:\n%.*s"), Wide.Length(), Wide.Get());
// Parse yaml
TArray64<uint8> Data;
TArray<FStructView> ParsedObjects;
FSchemaBatchId ParsedBatch = ParseBatch(Data, ParsedObjects, YamlView);
// Diff schemas
CHECK_FALSE(DiffSchemas(BatchIds.GetBatchId(), ParsedBatch));
// Diff objects
CHECK(Objects.Num() == ParsedObjects.Num());
const int32 NumObjects = FMath::Min(Objects.Num(), ParsedObjects.Num());
for (int32 I = 0; I < NumObjects; ++I)
{
FStructView In = Objects[I];
FStructView Parsed = ParsedObjects[I];
FReadDiffPath DiffPath;
if (DiffStruct(In, Parsed, DiffPath))
{
TUtf8StringBuilder<256> Diff;
PrintDiff(Diff, BatchIds, DiffPath);
FAIL_CHECK(FString::Printf(TEXT("Diff in '%s' in Objects[%d]"), *Print(Diff.ToString()), I));
}
}
// Unmount parsed schemas
UnmountReadSchemas(ParsedBatch);
}
////////////////////////////////////////////////////////////////////////////////////////////////
class FTestBatchReader
{
public:
FTestBatchReader(FMemoryView Data)
{
// Read names
FByteReader It(Data);
CHECK(It.Grab<uint32>() == TestMagics[0]);
Names.Read(It.GrabSkippableSlice());
// Read schemas
CHECK(It.Grab<uint32>() == TestMagics[1]);
It.SkipAlignmentPadding<uint32>();
uint32 SchemasSize = It.Grab<uint32>();
FMemoryView SchemasView = It.GrabSlice(SchemasSize);
const FSchemaBatch* Schemas = ValidateSchemas(SchemasView);
FSchemaBatchId Batch = MountReadSchemas(Schemas);
CHECK(It.Grab<uint32>() == TestMagics[2]);
// Read objects
while (uint64 NumBytes = It.GrabVarIntU())
{
FByteReader ObjIt(It.GrabSlice(NumBytes));
CHECK(ObjIt.Grab<uint32>() == TestMagics[3]);
FStructSchemaId Schema = { ObjIt.Grab<uint32>() };
Objects.Add({ { Schema, Batch }, ObjIt });
}
CHECK(It.Grab<uint32>() == TestMagics[4]);
CHECK(!Objects.IsEmpty());
BatchIds.Emplace(Names, Batch);
}
void RoundtripText()
{
PlainProps::RoundtripText(BatchIds.GetValue(), Objects);
}
~FTestBatchReader()
{
UnmountReadSchemas(Objects[0].Schema.Batch);
}
TConstArrayView<FStructView> GetObjects() const { return Objects; }
const FTestNameReader& GetNames() const { return Names; }
const FTestBatchIds& GetBatchIds() const { return BatchIds.GetValue(); }
private:
FTestNameReader Names;
TOptional<FTestBatchIds> BatchIds;
TArray<FStructView> Objects;
};
static void TestSerialize(void (*BuildObjects)(FTestBatchBuilder&, FScratchAllocator&), void (*CheckObjects)(TConstArrayView<FStructView>, const FTestNameReader&))
{
TArray64<uint8> Data;
{
FScratchAllocator Scratch;
FTestBatchBuilder Batch(Scratch);
DbgVis::FIdScope _(Batch, "AnsiStr");
BuildObjects(Batch, Scratch);
Data = Batch.Write();
}
FTestBatchReader Batch(MakeMemoryView(Data));
Batch.RoundtripText();
CheckObjects(Batch.GetObjects(), Batch.GetNames());
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Tests that everything was read
struct FTestMemberReader : public FMemberReader
{
using FMemberReader::FMemberReader;
~FTestMemberReader()
{
CHECK(MemberIdx == NumMembers); // Must read all members
CHECK(RangeTypeIdx == NumRangeTypes); // Must read all ranges
#if DO_CHECK
CHECK(InnerSchemaIdx == NumInnerSchemas); // Must read all schema ids
#endif
}
};
template<typename OutType, typename InType>
TArray<OutType> MakeArray(const InType& Items)
{
TArray<OutType> Out;
Out.Reserve(IntCastChecked<int32>(Items.Num()));
for (const auto& Item : Items)
{
Out.Emplace(Item);
}
return Out;
}
////////////////////////////////////////////////////////////////////////////////////////////////
template<uint32 N>
TStaticArray<FMemberSpec, N> Respec(FMemberSpec Spec)
{
return MakeUniformStaticArray<FMemberSpec, N>(Spec);
}
static FMemberSpec UniRange(FMemberSpec Item) { return FMemberSpec(ERangeSizeType::Uni, Item); }
static FMemberSpec S8Range(FMemberSpec Item) { return FMemberSpec(ERangeSizeType::S8, Item); }
static FMemberSpec U8Range(FMemberSpec Item) { return FMemberSpec(ERangeSizeType::U8, Item); }
static FMemberSpec S16Range(FMemberSpec Item) { return FMemberSpec(ERangeSizeType::S16, Item); }
static FMemberSpec U16Range(FMemberSpec Item) { return FMemberSpec(ERangeSizeType::U16, Item); }
static FMemberSpec S32Range(FMemberSpec Item) { return FMemberSpec(ERangeSizeType::S32, Item); }
static FMemberSpec U32Range(FMemberSpec Item) { return FMemberSpec(ERangeSizeType::U32, Item); }
static FMemberSpec S64Range(FMemberSpec Item) { return FMemberSpec(ERangeSizeType::S64, Item); }
static FMemberSpec U64Range(FMemberSpec Item) { return FMemberSpec(ERangeSizeType::U64, Item); }
inline FMemberSpec Enum8(FEnumId Id) { return FMemberSpec({ELeafType::Enum, ELeafWidth::B8}, Id); }
inline FMemberSpec Enum16(FEnumId Id) { return FMemberSpec({ELeafType::Enum, ELeafWidth::B16}, Id); }
inline FMemberSpec Enum32(FEnumId Id) { return FMemberSpec({ELeafType::Enum, ELeafWidth::B32}, Id); }
inline FMemberSpec Enum64(FEnumId Id) { return FMemberSpec({ELeafType::Enum, ELeafWidth::B64}, Id); }
////////////////////////////////////////////////////////////////////////////////////////////////
TEST_CASE_NAMED(FPlainPropsReadWriteTest, "System::Core::Serialization::PlainProps::ReadWrite", "[Core][PlainProps][SmokeFilter]")
{
SECTION("Bool")
{
static constexpr std::initializer_list<const char*> MemberNames = {"b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "b10", "b11"};
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FDeclId Id = Batch.DeclareStruct("Testing", "Bools", MemberNames, Respec<12>(SpecBool), EMemberPresence::AllowSparse);
FMemberBuilder B1T;
B1T.Add(Batch.NameMember("b3"), true);
FMemberBuilder B1F;
B1F.Add(Batch.NameMember("b1"), false);
FMemberBuilder B8M;
B8M.Add(Batch.NameMember("b1"), true);
B8M.Add(Batch.NameMember("b2"), false);
B8M.Add(Batch.NameMember("b3"), true);
B8M.Add(Batch.NameMember("b4"), false);
B8M.Add(Batch.NameMember("b5"), true);
B8M.Add(Batch.NameMember("b6"), false);
B8M.Add(Batch.NameMember("b8"), false);
B8M.Add(Batch.NameMember("b9"), true);
FMemberBuilder B9T;
B9T.Add(Batch.NameMember("b1"), true);
B9T.Add(Batch.NameMember("b2"), true);
B9T.Add(Batch.NameMember("b3"), true);
B9T.Add(Batch.NameMember("b4"), true);
B9T.Add(Batch.NameMember("b5"), true);
B9T.Add(Batch.NameMember("b6"), true);
B9T.Add(Batch.NameMember("b8"), true);
B9T.Add(Batch.NameMember("b9"), true);
B9T.Add(Batch.NameMember("b10"),true);
Batch.AddObject(Id, MoveTemp(B1T));
Batch.AddObject(Id, MoveTemp(B1F));
Batch.AddObject(Id, MoveTemp(B8M));
Batch.AddObject(Id, MoveTemp(B9T));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 4);
FTestMemberReader B1T(Objects[0]);
FTestMemberReader B1F(Objects[1]);
FTestMemberReader B8M(Objects[2]);
FTestMemberReader B9T(Objects[3]);
CHECK(Objects[0].Schema.Id == Objects[3].Schema.Id);
// Check schema
const FStructSchema& Schema = Objects[0].Schema.Resolve();
CHECK(Names[Schema.Type.Scope] == "Testing");
CHECK(Names[Schema.Type.Name] == "Bools");
CHECK(Schema.NumMembers == 9); // b0, b7 and b11 unused
CHECK(Schema.NumRangeTypes == 0);
CHECK(Schema.NumInnerSchemas == 0);
CHECK(Schema.IsDense == 0);
CHECK(Schema.Inheritance == ESuper::No);
CHECK(FStructSchema::GetMemberTypes(Schema.Footer)[0] == FUnpackedLeafType(ELeafType::Bool, ELeafWidth::B8).Pack());
CHECK(FStructSchema::GetMemberTypes(Schema.Footer)[8] == FUnpackedLeafType(ELeafType::Bool, ELeafWidth::B8).Pack());
TConstArrayView<FMemberId> MemberIds = Schema.GetMemberNames();
CHECK(Names[MemberIds[0]] == "b1");
CHECK(Names[MemberIds[1]] == "b2");
CHECK(Names[MemberIds[2]] == "b3");
CHECK(Names[MemberIds[3]] == "b4");
CHECK(Names[MemberIds[4]] == "b5");
CHECK(Names[MemberIds[5]] == "b6");
CHECK(Names[MemberIds[6]] == "b8");
CHECK(Names[MemberIds[7]] == "b9");
CHECK(Names[MemberIds[8]] == "b10");
CHECK(Names[B1T.PeekName()] == "b3");
CHECK(B1T.GrabLeaf().AsBool() == true);
CHECK(Names[B1F.PeekName()] == "b1");
CHECK(B1F.GrabLeaf().AsBool() == false);
CHECK(B8M.GrabLeaf().AsBool() == true);
CHECK(B8M.GrabLeaf().AsBool() == false);
CHECK(B8M.GrabLeaf().AsBool() == true);
CHECK(B8M.GrabLeaf().AsBool() == false);
CHECK(B8M.GrabLeaf().AsBool() == true);
CHECK(B8M.GrabLeaf().AsBool() == false);
CHECK(B8M.GrabLeaf().AsBool() == false);
CHECK(B8M.GrabLeaf().AsBool() == true);
for (int32 Idx = 0; Idx < 9; ++Idx)
{
CHECK(B9T.GrabLeaf().AsBool() == true);
}
});
}
SECTION("Number")
{
static constexpr std::initializer_list<const char*> MemberNames = {"F32", "F64", "S8", "U8", "S16", "U16", "S32", "U32", "S64", "U64"};
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FMemberSpec MemberTypes[] = {SpecF32, SpecF64, SpecS8, SpecU8, SpecS16, SpecU16, SpecS32, SpecU32, SpecS64, SpecU64};
FDeclId Id = Batch.DeclareStruct("Test", "Numbers", MemberNames, MemberTypes, EMemberPresence::AllowSparse);
FMemberBuilder Misc, Mins, Maxs, Some;
Misc.Add(Batch.NameMember("F32"), 32.f);
Misc.Add(Batch.NameMember("F64"), 64.0);
Misc.Add(Batch.NameMember("S8"), int8(-8));
Misc.Add(Batch.NameMember("U8"), uint8(8));
Misc.Add(Batch.NameMember("S16"), int16(-16));
Misc.Add(Batch.NameMember("U16"), uint16(16));
Misc.Add(Batch.NameMember("S32"), int32(-32));
Misc.Add(Batch.NameMember("U32"), uint32(32));
Misc.Add(Batch.NameMember("S64"), int64(-64));
Misc.Add(Batch.NameMember("U64"), uint64(64));
Mins.Add(Batch.NameMember("F32"), std::numeric_limits< float>::min());
Mins.Add(Batch.NameMember("F64"), std::numeric_limits<double>::min());
Mins.Add(Batch.NameMember("S8"), std::numeric_limits< int8>::min());
Mins.Add(Batch.NameMember("U8"), std::numeric_limits< uint8>::min());
Mins.Add(Batch.NameMember("S16"), std::numeric_limits< int16>::min());
Mins.Add(Batch.NameMember("U16"), std::numeric_limits<uint16>::min());
Mins.Add(Batch.NameMember("S32"), std::numeric_limits< int32>::min());
Mins.Add(Batch.NameMember("U32"), std::numeric_limits<uint32>::min());
Mins.Add(Batch.NameMember("S64"), std::numeric_limits< int64>::min());
Mins.Add(Batch.NameMember("U64"), std::numeric_limits<uint64>::min());
Maxs.Add(Batch.NameMember("F32"), std::numeric_limits< float>::max());
Maxs.Add(Batch.NameMember("F64"), std::numeric_limits<double>::max());
Maxs.Add(Batch.NameMember("S8"), std::numeric_limits< int8>::max());
Maxs.Add(Batch.NameMember("U8"), std::numeric_limits< uint8>::max());
Maxs.Add(Batch.NameMember("S16"), std::numeric_limits< int16>::max());
Maxs.Add(Batch.NameMember("U16"), std::numeric_limits<uint16>::max());
Maxs.Add(Batch.NameMember("S32"), std::numeric_limits< int32>::max());
Maxs.Add(Batch.NameMember("U32"), std::numeric_limits<uint32>::max());
Maxs.Add(Batch.NameMember("S64"), std::numeric_limits< int64>::max());
Maxs.Add(Batch.NameMember("U64"), std::numeric_limits<uint64>::max());
Some.Add(Batch.NameMember("S32"), 0);
Batch.AddObject(Id, MoveTemp(Misc));
Batch.AddObject(Id, MoveTemp(Mins));
Batch.AddObject(Id, MoveTemp(Maxs));
Batch.AddObject(Id, MoveTemp(Some));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
for (const FStructView& Object : Objects.Left(3))
{
FTestMemberReader Members(Object);
for (const char* MemberName : MemberNames)
{
CHECK(Members.HasMore());
CHECK(Names[Members.PeekName()] == MemberName);
CHECK(Members.PeekKind() == EMemberKind::Leaf);
(void)Members.GrabLeaf();
}
}
FTestMemberReader Misc(Objects[0]);
CHECK(Misc.GrabLeaf().AsFloat() == 32.f);
CHECK(Misc.GrabLeaf().AsDouble() == 64.0);
CHECK(Misc.GrabLeaf().AsS8() == int8(-8));
CHECK(Misc.GrabLeaf().AsU8() == uint8(8));
CHECK(Misc.GrabLeaf().AsS16() == int16(-16));
CHECK(Misc.GrabLeaf().AsU16() == uint16(16));
CHECK(Misc.GrabLeaf().AsS32() == int32(-32));
CHECK(Misc.GrabLeaf().AsU32() == uint32(32));
CHECK(Misc.GrabLeaf().AsS64() == int64(-64));
CHECK(Misc.GrabLeaf().AsU64() == uint64(64));
FTestMemberReader Mins(Objects[1]);
CHECK(Mins.GrabLeaf().AsFloat() == std::numeric_limits< float>::min());
CHECK(Mins.GrabLeaf().AsDouble() == std::numeric_limits<double>::min());
CHECK(Mins.GrabLeaf().AsS8() == std::numeric_limits< int8>::min());
CHECK(Mins.GrabLeaf().AsU8() == std::numeric_limits< uint8>::min());
CHECK(Mins.GrabLeaf().AsS16() == std::numeric_limits< int16>::min());
CHECK(Mins.GrabLeaf().AsU16() == std::numeric_limits<uint16>::min());
CHECK(Mins.GrabLeaf().AsS32() == std::numeric_limits< int32>::min());
CHECK(Mins.GrabLeaf().AsU32() == std::numeric_limits<uint32>::min());
CHECK(Mins.GrabLeaf().AsS64() == std::numeric_limits< int64>::min());
CHECK(Mins.GrabLeaf().AsU64() == std::numeric_limits<uint64>::min());
FTestMemberReader Maxs(Objects[2]);
CHECK(Maxs.GrabLeaf().AsFloat() == std::numeric_limits< float>::max());
CHECK(Maxs.GrabLeaf().AsDouble() == std::numeric_limits<double>::max());
CHECK(Maxs.GrabLeaf().AsS8() == std::numeric_limits< int8>::max());
CHECK(Maxs.GrabLeaf().AsU8() == std::numeric_limits< uint8>::max());
CHECK(Maxs.GrabLeaf().AsS16() == std::numeric_limits< int16>::max());
CHECK(Maxs.GrabLeaf().AsU16() == std::numeric_limits<uint16>::max());
CHECK(Maxs.GrabLeaf().AsS32() == std::numeric_limits< int32>::max());
CHECK(Maxs.GrabLeaf().AsU32() == std::numeric_limits<uint32>::max());
CHECK(Maxs.GrabLeaf().AsS64() == std::numeric_limits< int64>::max());
CHECK(Maxs.GrabLeaf().AsU64() == std::numeric_limits<uint64>::max());
FTestMemberReader Some(Objects[3]);
CHECK(Names[Some.PeekName()] == "S32");
CHECK(Some.GrabLeaf().AsS32() == 0);
});
}
SECTION("Unicode")
{
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FDeclId Char8Id = Batch.DeclareStruct("Test", "Char8", {"A", "B", "C", "D", "E", "F"}, Respec<6>(SpecUtf8), EMemberPresence::AllowSparse);
FDeclId Char16Id = Batch.DeclareStruct("Test", "Char16", {"A", "B", "C", "D", "E", "F"}, Respec<6>(SpecUtf16), EMemberPresence::AllowSparse);
FDeclId Char32Id = Batch.DeclareStruct("Test", "Char32", {"A", "B", "C", "D", "E", "F"}, Respec<6>(SpecUtf32), EMemberPresence::AllowSparse);
FMemberBuilder Char8, Char16, Char32;
Char8.Add(Batch.NameMember("A"), u8'\0'); // NUL, first valid code unit
Char8.Add(Batch.NameMember("B"), u8'\x1'); // SOH, a control character
Char8.Add(Batch.NameMember("C"), u8'\n'); // LF, an escaped character
Char8.Add(Batch.NameMember("D"), u8'%'); // %, a printable character
Char8.Add(Batch.NameMember("E"), u8'E'); // E, an alphabetic character
Char8.Add(Batch.NameMember("F"), u8'\x7F'); // DEL, last valid code unit
Char16.Add(Batch.NameMember("A"), u'\0'); // First valid code unit
Char16.Add(Batch.NameMember("B"), u'\x0024'); // Dollar sign, single byte code unit
Char16.Add(Batch.NameMember("C"), u'\xD7FF'); // Last single byte code unit
Char16.Add(Batch.NameMember("D"), u'\xE000'); // First double byte code unit
Char16.Add(Batch.NameMember("E"), u'\x20AC'); // Euro sign, double byte code unit
Char16.Add(Batch.NameMember("F"), u'\xFFFD'); // Last valid single code unit character
// UE::Core::Private::IsValidCodepoint() treats the non-characters FFFE and FFFF as invalid
Char32.Add(Batch.NameMember("A"), U'\0');
Char32.Add(Batch.NameMember("B"), U'\x1');
Char32.Add(Batch.NameMember("C"), U'C');
Batch.AddObject(Char8Id, MoveTemp(Char8));
Batch.AddObject(Char16Id, MoveTemp(Char16));
Batch.AddObject(Char32Id, MoveTemp(Char32));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 3);
FTestMemberReader Char8(Objects[0]);
CHECK(Char8.GrabLeaf().AsChar8() == u8'\0');
CHECK(Char8.GrabLeaf().AsChar8() == u8'\x1');
CHECK(Char8.GrabLeaf().AsChar8() == u8'\n');
CHECK(Char8.GrabLeaf().AsChar8() == u8'%');
CHECK(Char8.GrabLeaf().AsChar8() == u8'E');
CHECK(Char8.GrabLeaf().AsChar8() == u8'\x7F');
FTestMemberReader Char16(Objects[1]);
CHECK(Char16.GrabLeaf().AsChar16() == u'\0');
CHECK(Char16.GrabLeaf().AsChar16() == u'\x0024');
CHECK(Char16.GrabLeaf().AsChar16() == u'\xD7FF');
CHECK(Char16.GrabLeaf().AsChar16() == u'\xE000');
CHECK(Char16.GrabLeaf().AsChar16() == u'\x20AC');
CHECK(Char16.GrabLeaf().AsChar16() == u'\xFFFD');
FTestMemberReader Char32(Objects[2]);
CHECK(Char32.GrabLeaf().AsChar32() == U'\0');
CHECK(Char32.GrabLeaf().AsChar32() == U'\x1');
CHECK(Char32.GrabLeaf().AsChar32() == U'C');
});
}
SECTION("Dense")
{
static constexpr std::initializer_list<const char*> ExplicitNames = {"A", "B", "C"};
static constexpr std::initializer_list<const char*> ImplicitNames = {"0", "A", "1", "B", "2", "C", "3"};
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FMemberSpec ExplicitTypes[] = {SpecUtf8, SpecUtf16, SpecUtf32};
FMemberSpec ImplicitTypes[] = {SpecBool, SpecUtf8, SpecBool, SpecUtf16, SpecBool, SpecUtf32, SpecBool};
FDeclId ExplicitId = Batch.DeclareStruct("Test", "ExplicitDense", ExplicitNames, ExplicitTypes, EMemberPresence::RequireAll);
FDeclId ImplicitId = Batch.DeclareStruct("Test", "ImplicitDense", ImplicitNames, ImplicitTypes, EMemberPresence::AllowSparse);
FMemberBuilder X;
X.Add(Batch.NameMember("A"), char8_t('a'));
X.Add(Batch.NameMember("B"), char16_t('b'));
X.Add(Batch.NameMember("C"), char32_t('c'));
FMemberBuilder Y;
Y.Add(Batch.NameMember("A"), char8_t('1'));
Y.Add(Batch.NameMember("B"), char16_t('2'));
Y.Add(Batch.NameMember("C"), char32_t('3'));
Batch.AddObject(ExplicitId, MoveTemp(X));
Batch.AddObject(ImplicitId, MoveTemp(Y));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 2);
const FStructSchema& ExplicitSchema = Objects[0].Schema.Resolve();
const FStructSchema& ImplicitSchema = Objects[1].Schema.Resolve();
CHECK(Names[ExplicitSchema.Type.Name] == "ExplicitDense");
CHECK(ExplicitSchema.NumMembers == 3);
CHECK(ExplicitSchema.IsDense == 1);
CHECK(Names[ImplicitSchema.Type.Name] == "ImplicitDense");
CHECK(ImplicitSchema.NumMembers == 3);
CHECK(ImplicitSchema.IsDense == 1);
FTestMemberReader X(Objects[0]);
FTestMemberReader Y(Objects[1]);
CHECK(Names[X.PeekName()] == "A");
CHECK(X.GrabLeaf().AsChar8() == 'a');
CHECK(Names[X.PeekName()] == "B");
CHECK(X.GrabLeaf().AsChar16() == 'b');
CHECK(Names[X.PeekName()] == "C");
CHECK(X.GrabLeaf().AsChar32() == 'c');
CHECK(Names[Y.PeekName()] == "A");
CHECK(Y.GrabLeaf().AsChar8() == '1');
CHECK(Names[Y.PeekName()] == "B");
CHECK(Y.GrabLeaf().AsChar16() == '2');
CHECK(Names[Y.PeekName()] == "C");
CHECK(Y.GrabLeaf().AsChar32() == '3');
});
}
SECTION("Struct")
{
static constexpr std::initializer_list<const char*> ObjectMembers = {"L1", "S", "N", "L2"};
static constexpr std::initializer_list<const char*> StructMembers = {"Nested", "Leaf"};
static constexpr std::initializer_list<const char*> NestedMembers = {"I1", "I2"};
static constexpr std::initializer_list<const char*> UnusedMembers = {"Unused1", "Unused2"};
static constexpr std::initializer_list<const char*> EmptyMembers = {};
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FDeclId NestedId = Batch.DeclareStruct("Test", "Nested", NestedMembers, {SpecS32, SpecS32}, EMemberPresence::AllowSparse);
FDeclId StructId = Batch.DeclareStruct("Test", "Struct", StructMembers, {NestedId, SpecBool}, EMemberPresence::AllowSparse);
FDeclId ObjectId = Batch.DeclareStruct("Test", "Object", ObjectMembers, {SpecF32, StructId, NestedId, SpecF32}, EMemberPresence::AllowSparse);
FDeclId UnusedId = Batch.DeclareStruct("Test", "Unused", UnusedMembers, {SpecBool, SpecBool}, EMemberPresence::AllowSparse);
FDeclId EmptyId = Batch.DeclareStruct("Test", "Empty", EmptyMembers, {}, EMemberPresence::AllowSparse);
FMemberBuilder Members;
Members.Add(Batch.NameMember("I1"), 100);
FBuiltStruct* NestedInStruct = Members.BuildAndReset(Scratch, Batch.Get(NestedId), Batch.GetDebug());
Members.AddStruct(Batch.NameMember("Nested"), NestedId, MoveTemp(NestedInStruct));
Members.Add(Batch.NameMember("Leaf"), true);
FBuiltStruct* Struct = Members.BuildAndReset(Scratch, Batch.Get(StructId), Batch.GetDebug());
Members.Add(Batch.NameMember("I2"), 200);
FBuiltStruct* NestedInObject = Members.BuildAndReset(Scratch, Batch.Get(NestedId), Batch.GetDebug());
Members.Add(Batch.NameMember("L1"), 123.f);
Members.AddStruct(Batch.NameMember("S"), StructId, MoveTemp(Struct));
Members.AddStruct(Batch.NameMember("N"), NestedId, MoveTemp(NestedInObject));
Members.Add(Batch.NameMember("L2"), -45.f);
Batch.AddObject(ObjectId, MoveTemp(Members));
Batch.AddObject(UnusedId, {});
Batch.AddObject(EmptyId, {});
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 3);
CHECK(Objects[0].Schema.Resolve().IsDense == 1);
CHECK(Objects[1].Schema.Resolve().IsDense == 1);
CHECK(Objects[2].Schema.Resolve().IsDense == 1);
FTestMemberReader Object(Objects[0]);
CHECK(Object.GrabLeaf().AsFloat() == 123.f);
FTestMemberReader Struct(Object.GrabStruct());
FTestMemberReader NestedInObject(Object.GrabStruct());
CHECK(Object.GrabLeaf().AsFloat() == -45.f);
FTestMemberReader NestedInStruct(Struct.GrabStruct());
CHECK(Struct.GrabLeaf().AsBool() == true);
CHECK(NestedInObject.GrabLeaf().AsS32() == 200);
CHECK(NestedInStruct.GrabLeaf().AsS32() == 100);
FTestMemberReader Unused(Objects[1]);
FTestMemberReader Empty(Objects[2]);
});
}
SECTION("Enum")
{
enum class EFlatSparse8 : int8 { A = 1, B = 2, C = 3 };
static constexpr std::initializer_list<const char*> MemberNames =
{ "A2", "A0", "B0", "B4", "B5", "B7", "C3", "D34", "Max8", "Max16", "Max32", "Max64", "IF" };
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
// Test create holes in the original FNameId, FDeclId and FEnumId index range
FDeclId UnusedId = Batch.DeclareStruct("Test", "UnusedStruct", {"U1", "U2"}, {SpecBool, SpecBool}, EMemberPresence::AllowSparse);
FEnumId U = Batch.DeclareEnum("Test", "UnusedEnum1", EEnumMode::Flag, ELeafWidth::B8, {"U3"}, {1}); // Hole
FEnumId A = Batch.DeclareEnum("Test", "FlatDense8", EEnumMode::Flat, ELeafWidth::B8, {"A", "B", "C"}, {0, 1, 2});
FEnumId X = Batch.DeclareEnum("Test", "UnusedEnum2", EEnumMode::Flag, ELeafWidth::B8, {"U4"}, {1}); // Hole
FEnumId B = Batch.DeclareEnum("Test", "FlagDense8", EEnumMode::Flag, ELeafWidth::B8, {"A", "B", "C"}, {1, 2, 4});
FEnumId C = Batch.DeclareEnum("Test", "FlatSparse8", EEnumMode::Flat, ELeafWidth::B8, {"A", "B", "C"}, {1, 2, 3});
FEnumId D = Batch.DeclareEnum("Test", "FlagSparse8", EEnumMode::Flag, ELeafWidth::B8, {"A", "B", "C"}, {2, 16, 32});
FEnumId E = Batch.DeclareEnum("Test", "FlatLimit8", EEnumMode::Flat, ELeafWidth::B8, {"Min", "Max"}, {0, 0xFF});
FEnumId F = Batch.DeclareEnum("Test", "FlatLimit16", EEnumMode::Flat, ELeafWidth::B16, {"Min", "Max"}, {0, 0xFFFF});
FEnumId G = Batch.DeclareEnum("Test", "FlatLimit32", EEnumMode::Flat, ELeafWidth::B32, {"Min", "Max"}, {0, 0xFFFFFFFF});
FEnumId H = Batch.DeclareEnum("Test", "FlatLimit64", EEnumMode::Flat, ELeafWidth::B64, {"Min", "Max"}, {0, 0xFFFFFFFFFFFFFFFF});
FEnumId I = Batch.DeclareEnum("Test", "FlagLimit64", EEnumMode::Flag, ELeafWidth::B64, {"One", "Max"}, {1, 0x8000000000000000});
FMemberSpec MemberTypes[] = {
Enum8(A), Enum8(A), Enum8(B), Enum8(B), Enum8(B), Enum8(B),
Enum8(C), Enum8(D), Enum8(E), Enum16(F), Enum32(G), Enum64(H), Enum64(I) };
FDeclId ObjectId = Batch.DeclareStruct("Test", "Enums", MemberNames, MemberTypes, EMemberPresence::AllowSparse);
FMemberBuilder Members;
Members.AddEnum(Batch.NameMember("A2"), A, (uint8)2);
Members.AddEnum(Batch.NameMember("A0"), A, (uint8)0);
Members.AddEnum(Batch.NameMember("B0"), B, (uint8)0);
Members.AddEnum(Batch.NameMember("B4"), B, (uint8)4);
Members.AddEnum(Batch.NameMember("B5"), B, (uint8)5);
Members.AddEnum(Batch.NameMember("B7"), B, (uint8)7);
Members.AddEnum(Batch.NameMember("C3"), C, EFlatSparse8::C);
Members.AddEnum(Batch.NameMember("D34"), D, (uint8)34);
Members.AddEnum(Batch.NameMember("Max8"), E, (uint8)0xFF);
Members.AddEnum(Batch.NameMember("Max16"), F, (uint16)0xFFFF);
Members.AddEnum(Batch.NameMember("Max32"), G, (uint32)0xFFFFFFFF);
Members.AddEnum(Batch.NameMember("Max64"), H, (uint64)0xFFFFFFFFFFFFFFFF);
Members.AddEnum(Batch.NameMember("IF"), I, (uint64)0x8000000000000001);
Batch.AddObject(ObjectId, MoveTemp(Members));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 1);
FSchemaBatchId Batch = Objects[0].Schema.Batch;
auto GetEnumName = [Batch, &Names](FLeafView Leaf) { return Names[ResolveEnumSchema(Batch, Leaf.Enum.Get()).Type.Name]; };
auto EqualEnumNames = [&Names](TConstArrayView<FNameId> Ids, TConstArrayView<const char*> Strings)
{
return Algo::Compare(Ids, Strings, [&Names](auto& X, auto& Y) { return Names[X] == Y; });
};
FTestMemberReader It1(Objects[0]);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint8>() == 2);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint8>() == 0);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint8>() == 0);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint8>() == 4);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint8>() == 5);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint8>() == 7);
CHECK(It1.GrabLeaf().As<EFlatSparse8>() == EFlatSparse8::C);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint8>() == 34);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint8>() == 0xFF);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint16>() == 0xFFFF);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint32>() == 0xFFFFFFFF);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint64>() == 0xFFFFFFFFFFFFFFFF);
CHECK(It1.GrabLeaf().AsUnderlyingValue<uint64>() == 0x8000000000000001);
FTestMemberReader It2(Objects[0]);
CHECK(GetEnumName(It2.GrabLeaf()) == "FlatDense8");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlatDense8");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlagDense8");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlagDense8");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlagDense8");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlagDense8");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlatSparse8");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlagSparse8");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlatLimit8");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlatLimit16");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlatLimit32");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlatLimit64");
CHECK(GetEnumName(It2.GrabLeaf()) == "FlagLimit64");
FTestMemberReader It3(Objects[0]);
const FEnumSchema& FlatDense8 = ResolveEnumSchema(Batch, It3.GrabLeaf().Enum.Get());
(void)It3.GrabLeaf();
const FEnumSchema& FlagDense8 = ResolveEnumSchema(Batch, It3.GrabLeaf().Enum.Get());
(void)It3.GrabLeaf();
(void)It3.GrabLeaf();
(void)It3.GrabLeaf();
const FEnumSchema& FlatSparse8 = ResolveEnumSchema(Batch, It3.GrabLeaf().Enum.Get());
const FEnumSchema& FlagSparse8 = ResolveEnumSchema(Batch, It3.GrabLeaf().Enum.Get());
const FEnumSchema& FlatLimit8 = ResolveEnumSchema(Batch, It3.GrabLeaf().Enum.Get());
const FEnumSchema& FlatLimit16 = ResolveEnumSchema(Batch, It3.GrabLeaf().Enum.Get());
const FEnumSchema& FlatLimit32 = ResolveEnumSchema(Batch, It3.GrabLeaf().Enum.Get());
const FEnumSchema& FlatLimit64 = ResolveEnumSchema(Batch, It3.GrabLeaf().Enum.Get());
const FEnumSchema& FlagLimit64 = ResolveEnumSchema(Batch, It3.GrabLeaf().Enum.Get());
CHECK(FlatDense8.ExplicitConstants);
CHECK(!FlagDense8.ExplicitConstants);
CHECK(FlatSparse8.ExplicitConstants);
CHECK(FlagSparse8.ExplicitConstants);
CHECK(FlatLimit8.ExplicitConstants);
CHECK(FlatLimit16.ExplicitConstants);
CHECK(FlatLimit32.ExplicitConstants);
CHECK(FlatLimit64.ExplicitConstants);
CHECK(FlagLimit64.ExplicitConstants);
CHECK(EqualEnumNames(MakeConstArrayView(FlatDense8.Footer, FlatDense8.Num), MakeConstArrayView({"A", "C"})));
CHECK(EqualEnumNames(MakeConstArrayView(FlagDense8.Footer, FlagDense8.Num), MakeConstArrayView({"A", "B", "C"})));
CHECK(EqualEnumNames(MakeConstArrayView(FlatSparse8.Footer, FlatSparse8.Num), MakeConstArrayView({"C"})));
CHECK(EqualEnumNames(MakeConstArrayView(FlagSparse8.Footer, FlagSparse8.Num), MakeConstArrayView({"A", "C"})));
CHECK(Names[FlatLimit8.Footer[0]] == "Max");
CHECK(Names[FlatLimit16.Footer[0]] == "Max");
CHECK(Names[FlatLimit32.Footer[0]] == "Max");
CHECK(Names[FlatLimit64.Footer[0]] == "Max");
CHECK(Names[FlagLimit64.Footer[0]] == "One");
CHECK(Names[FlagLimit64.Footer[1]] == "Max");
CHECK(EqualItems(GetConstants<uint8>(FlatDense8), MakeConstArrayView({0, 2})));
CHECK(EqualItems(GetConstants<uint8>(FlagDense8), TConstArrayView<uint8>()));
CHECK(EqualItems(GetConstants<EFlatSparse8>(FlatSparse8), MakeConstArrayView({EFlatSparse8::C})));
CHECK(EqualItems(GetConstants<uint8>(FlagSparse8), MakeConstArrayView({2, 32})));
CHECK(GetConstants<uint8>(FlatLimit8)[0] == 0xFF);
CHECK(GetConstants<uint16>(FlatLimit16)[0] == 0xFFFF);
CHECK(GetConstants<uint32>(FlatLimit32)[0] == 0xFFFFFFFF);
CHECK(GetConstants<uint64>(FlatLimit64)[0] == 0xFFFFFFFFFFFFFFFF);
CHECK(GetConstants<uint64>(FlagLimit64)[0] == 1);
CHECK(GetConstants<uint64>(FlagLimit64)[1] == 0x8000000000000000);
});
}
SECTION("LeafRange")
{
enum class EABCD : uint16 { A, B, C, D };
enum class EUnused1 : uint8 { X };
enum class EUnused2 : uint8 { Y };
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FEnumId ABCD = Batch.DeclareEnum("Test", "ABCD", EEnumMode::Flat, ELeafWidth::B16, {"A", "B", "C", "D"}, {0, 1, 2, 3});
FEnumId Unused1 = Batch.DeclareEnum("Test", "Unused1", EEnumMode::Flat, ELeafWidth::B8, {"X"}, {0});
FEnumId Unused2 = Batch.DeclareEnum("Test", "Unused2", EEnumMode::Flat, ELeafWidth::B8, {"Y"}, {0});
static constexpr std::initializer_list<const char*> MemberNames = { "B0", "B1", "B8", "B9", "D0", "D3", "Hi", "E3", "E0" };
FMemberSpec MemberTypes[] = { S32Range(SpecBool), S32Range(SpecBool), S64Range(SpecBool), S64Range(SpecBool),
S32Range(SpecF64), S32Range(SpecF64), S32Range(SpecUtf8),
S32Range(Enum16(ABCD)), S32Range(Enum8(Unused1)) };
FDeclId ObjectId = Batch.DeclareStruct("Test", "Object", MemberNames, MemberTypes, EMemberPresence::AllowSparse);
FMemberBuilder Members;
Members.AddRange(Batch.NameMember("B0"), BuildLeafRange(Scratch, TConstArrayView<bool>()));
Members.AddRange(Batch.NameMember("B1"), BuildLeafRange(Scratch, MakeArrayView({true})));
Members.AddRange(Batch.NameMember("B8"), BuildLeafRange(Scratch, TConstArrayView<bool,int64>({false, true, false, true, false, true, false, true})));
Members.AddRange(Batch.NameMember("B9"), BuildLeafRange(Scratch, TConstArrayView<bool,int64>({true, false, true, false, true, false, true, false, true})));
Members.AddRange(Batch.NameMember("D0"), BuildLeafRange(Scratch, TConstArrayView<double>()));
Members.AddRange(Batch.NameMember("D3"), BuildLeafRange(Scratch, MakeArrayView({DBL_MIN, 0.0, DBL_MAX})));
Members.AddRange(Batch.NameMember("Hi"), BuildLeafRange(Scratch, MakeArrayView(u8"Hello!")));
Members.AddRange(Batch.NameMember("E3"), BuildEnumRange(Scratch, ABCD, MakeArrayView({EABCD::B, EABCD::A, EABCD::D})));
Members.AddRange(Batch.NameMember("E0"), BuildEnumRange(Scratch, Unused1, TConstArrayView<EUnused1>()));
Batch.AddObject(ObjectId, MoveTemp(Members));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 1);
FTestMemberReader It(Objects[0]);
FLeafRangeView B0 = It.GrabRange().AsLeaves();
FLeafRangeView B1 = It.GrabRange().AsLeaves();
FLeafRangeView B8 = It.GrabRange().AsLeaves();
FLeafRangeView B9 = It.GrabRange().AsLeaves();
FLeafRangeView D0 = It.GrabRange().AsLeaves();
FLeafRangeView D3 = It.GrabRange().AsLeaves();
FLeafRangeView Hi = It.GrabRange().AsLeaves();
FLeafRangeView E3 = It.GrabRange().AsLeaves();
FLeafRangeView E0 = It.GrabRange().AsLeaves();
CHECK(B0.Num() == 0);
CHECK(EqualItems(B1.AsBools(), MakeArrayView({true})));
CHECK(EqualItems(B8.AsBools(), MakeArrayView({false, true, false, true, false, true, false, true})));
CHECK(EqualItems(B9.AsBools(), MakeArrayView({true, false, true, false, true, false, true, false, true})));
CHECK(EqualItems(D0.AsDoubles(), TConstArrayView<double>()));
CHECK(EqualItems(D3.AsDoubles(), MakeArrayView({DBL_MIN, 0.0, DBL_MAX})));
CHECK(EqualItems(Hi.AsUtf8(), u8"Hello!"));
CHECK(EqualItems(E3.As<EABCD>(), MakeArrayView({EABCD::B, EABCD::A, EABCD::D})));
CHECK(EqualItems(E0.As<EUnused1>(), TConstArrayView<EUnused1>()));
});
}
SECTION("UnicodeRange")
{
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FDeclId Utf8Id = Batch.DeclareStruct("Test", "Utf8", {"Null", "Empty", "Escape", "Latin1", "CJK", "Symbols"}, Respec<6>(S32Range(SpecUtf8)), EMemberPresence::AllowSparse);
FDeclId Utf16Id = Batch.DeclareStruct("Test", "Utf16", {"Null", "Empty", "Escape", "Latin1", "CJK", "Symbols"}, Respec<6>(S32Range(SpecUtf16)), EMemberPresence::AllowSparse);
FMemberBuilder Utf8, Utf16;
Utf8.AddRange(Batch.NameMember("Null"), BuildLeafRange(Scratch, TConstArrayView<char8_t>()));
Utf8.AddRange(Batch.NameMember("Empty"), BuildLeafRange(Scratch, MakeArrayView(u8"")));
Utf8.AddRange(Batch.NameMember("Escape"), BuildLeafRange(Scratch, MakeArrayView(u8"\"\\ \x01 \x1f\" \"\b \f \n \r \t \"\\").LeftChop(1)));
Utf8.AddRange(Batch.NameMember("Latin1"), BuildLeafRange(Scratch, MakeArrayView(u8"\xE5 \xE4 \xF6"))); // å ä ö
Utf8.AddRange(Batch.NameMember("CJK"), BuildLeafRange(Scratch, MakeArrayView(u8"\u3300 \uFE30"))); // ㌀
Utf8.AddRange(Batch.NameMember("Symbols"), BuildLeafRange(Scratch, MakeArrayView(u8"\u2665 \u01F34C"))); // ♥ 🍌
Utf16.AddRange(Batch.NameMember("Null"), BuildLeafRange(Scratch, TConstArrayView<char16_t>()));
Utf16.AddRange(Batch.NameMember("Empty"), BuildLeafRange(Scratch, MakeArrayView(u"")));
Utf16.AddRange(Batch.NameMember("Escape"), BuildLeafRange(Scratch, MakeArrayView(u"\\\" \x01 \x1f\" \"\b \f \n \r \t \\\"").LeftChop(1)));
Utf16.AddRange(Batch.NameMember("Latin1"), BuildLeafRange(Scratch, MakeArrayView(u"\xC5 \xC4 \xD6"))); // Å Ä Ö
Utf16.AddRange(Batch.NameMember("CJK"), BuildLeafRange(Scratch, MakeArrayView(u"\x3300 \xFE30"))); // ㌀
Utf16.AddRange(Batch.NameMember("Symbols"), BuildLeafRange(Scratch, MakeArrayView(u"\x2665 \xD83C\xDF4C"))); // ♥ 🍌
Batch.AddObject(Utf8Id, MoveTemp(Utf8));
Batch.AddObject(Utf16Id, MoveTemp(Utf16));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 2);
FTestMemberReader It1(Objects[0]);
CHECK(EqualItems(It1.GrabRange().AsLeaves().AsUtf8(), TConstArrayView<char8_t>()));
CHECK(EqualItems(It1.GrabRange().AsLeaves().AsUtf8(), u8""));
CHECK(EqualItems(It1.GrabRange().AsLeaves().AsUtf8(), MakeArrayView(u8"\"\\ \x01 \x1f\" \"\b \f \n \r \t \"\\").LeftChop(1)));
CHECK(EqualItems(It1.GrabRange().AsLeaves().AsUtf8(), u8"\xE5 \xE4 \xF6")); // å ä ö
CHECK(EqualItems(It1.GrabRange().AsLeaves().AsUtf8(), u8"\u3300 \uFE30")); // ㌀
CHECK(EqualItems(It1.GrabRange().AsLeaves().AsUtf8(), u8"\u2665 \u01F34C")); // ♥ 🍌
FTestMemberReader It2(Objects[1]);
CHECK(EqualItems(It2.GrabRange().AsLeaves().AsUtf16(), TConstArrayView<char16_t>()));
CHECK(EqualItems(It2.GrabRange().AsLeaves().AsUtf16(), u""));
CHECK(EqualItems(It2.GrabRange().AsLeaves().AsUtf16(), MakeArrayView(u"\\\" \x01 \x1f\" \"\b \f \n \r \t \\\"").LeftChop(1)));
CHECK(EqualItems(It2.GrabRange().AsLeaves().AsUtf16(), u"\xC5 \xC4 \xD6")); // Å Ä Ö
CHECK(EqualItems(It2.GrabRange().AsLeaves().AsUtf16(), u"\x3300 \xFE30")); // ㌀
CHECK(EqualItems(It2.GrabRange().AsLeaves().AsUtf16(), u"\x2665 \xD83C\xDF4C")); // ♥ 🍌
});
}
SECTION("StructRange")
{
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FDeclId StructId = Batch.DeclareStruct("Test", "Struct", {"I", "F"}, {SpecS32, SpecF32}, EMemberPresence::AllowSparse);
FDeclId ObjectId = Batch.DeclareStruct("Test", "Object", {"Structs"}, {S32Range(StructId)}, EMemberPresence::AllowSparse);
FStructRangeBuilder Structs(4);
Structs[0].Add(Batch.NameMember("I"), 0);
Structs[1].Add(Batch.NameMember("F"), 1.f);
Structs[2].Add(Batch.NameMember("I"), 2);
Structs[2].Add(Batch.NameMember("F"), 2.f);
FMemberBuilder Members;
Members.AddRange(Batch.NameMember("Structs"), Structs.BuildAndReset(Scratch, Batch.Get(StructId), Batch.GetDebug()));
Batch.AddObject(ObjectId, MoveTemp(Members));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 1);
FTestMemberReader It(Objects[0]);
TArray<FTestMemberReader> Structs = MakeArray<FTestMemberReader>(It.GrabRange().AsStructs());
CHECK(Structs.Num() == 4);
CHECK(Structs[0].GrabLeaf().AsS32() == 0);
CHECK(Structs[1].GrabLeaf().AsFloat() == 1.f);
CHECK(Structs[2].GrabLeaf().AsS32() == 2);
CHECK(Structs[2].GrabLeaf().AsFloat() == 2.f);
CHECK(!Structs[3].HasMore());
});
}
SECTION("NestedRange")
{
enum class EAB : uint8 { A = 1, B = 4 };
enum class EUnused : uint8 { X };
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FDeclId XY = Batch.DeclareStruct("Test", "XY", {"X", "Y"}, {SpecF32, SpecF32}, EMemberPresence::RequireAll);
FDeclId ZW = Batch.DeclareStruct("Test", "ZW", {"Z", "W"}, {SpecF32, SpecF32}, EMemberPresence::AllowSparse);
FEnumId Enum = Batch.DeclareEnum("Test", "AB", EEnumMode::Flag, ELeafWidth::B8, {"A", "B"}, {1, 4});
FEnumId UnusedEnum = Batch.DeclareEnum("Test", "Unused", EEnumMode::Flat, ELeafWidth::B8, {"X"}, {0});
static constexpr std::initializer_list<const char*> MemberNames = { "IntRs", "EmptyRs", "FloatRs", "EnumRs", "UnusedEnumRs", "UnicodeRs", "StructRs", "StructRRs" };
FMemberSpec MemberTypes[] = { S32Range(S32Range(SpecS32)), S32Range(S32Range(SpecBool)), S64Range(S32Range(SpecF32)),
U8Range(S32Range(Enum8(Enum))), U8Range(S32Range(Enum8(UnusedEnum))), S32Range(S32Range(SpecUtf8)),
U64Range(U64Range(XY)), U32Range(S16Range(S16Range(ZW)))};
FDeclId Object = Batch.DeclareStruct("Test", "Object", MemberNames, MemberTypes, EMemberPresence::AllowSparse);
FNestedRangeBuilder IntRs(MakeLeafRangeSchema<int32, int32>(), 3);
IntRs.Add(BuildLeafRange(Scratch, MakeArrayView({1})));
IntRs.Add({});
IntRs.Add(BuildLeafRange(Scratch, MakeArrayView({2, 3})));
FNestedRangeBuilder FloatRs(MakeLeafRangeSchema<float, int64>(), 3);
FloatRs.Add(BuildLeafRange(Scratch, TConstArrayView<float, int64>({1.f})));
FloatRs.Add({});
FloatRs.Add(BuildLeafRange(Scratch, TConstArrayView<float, int64>({2.f, 3.f})));
FNestedRangeBuilder EnumRs(MakeEnumRangeSchema<EAB, int32>(Enum), 2);
EnumRs.Add({});
EnumRs.Add(BuildEnumRange(Scratch, Enum, MakeArrayView({EAB::A, EAB(0), EAB::B})));
FNestedRangeBuilder UnusedEnumRs(MakeEnumRangeSchema<EUnused, int32>(UnusedEnum), 2);
UnusedEnumRs.Add({});
UnusedEnumRs.Add(BuildEnumRange(Scratch, UnusedEnum, TConstArrayView<EUnused>()));
FNestedRangeBuilder UnicodeRs(MakeLeafRangeSchema<char8_t, int32>(), 3);
UnicodeRs.Add(BuildLeafRange(Scratch, MakeArrayView(u8"Hello")));
UnicodeRs.Add({});
UnicodeRs.Add(BuildLeafRange(Scratch, MakeArrayView(u8"World!")));
FStructRangeBuilder XYs(uint64(2));
XYs[0].Add(Batch.NameMember("X"), 1.f);
XYs[0].Add(Batch.NameMember("Y"), 2.f);
XYs[1].Add(Batch.NameMember("X"), 3.f);
XYs[1].Add(Batch.NameMember("Y"), 4.f);
FNestedRangeBuilder StructRs(MakeStructRangeSchema(ERangeSizeType::U64, XY), 1);
StructRs.Add(XYs.BuildAndReset(Scratch, Batch.Get(XY), Batch.GetDebug()));
FStructRangeBuilder ZWs(int16(3));
ZWs[0].Add(Batch.NameMember("Z"), 1.5f);
ZWs[2].Add(Batch.NameMember("Z"), 2.5f);
ZWs[2].Add(Batch.NameMember("W"), 3.5f);
FMemberSchema ZWRangeSchema = MakeStructRangeSchema(ERangeSizeType::S16, ZW);
FNestedRangeBuilder ZWRs(ZWRangeSchema, 1);
ZWRs.Add(ZWs.BuildAndReset(Scratch, Batch.Get(ZW), Batch.GetDebug()));
FNestedRangeBuilder StructRRs(MakeNestedRangeSchema(Scratch, ERangeSizeType::U32, ZWRangeSchema), 1);
StructRRs.Add(ZWRs.BuildAndReset(Scratch, ERangeSizeType::U32));
FMemberBuilder Members;
Members.AddRange(Batch.NameMember("IntRs"), IntRs.BuildAndReset(Scratch, ERangeSizeType::S32));
Members.AddRange(Batch.NameMember("EmptyRs"), IntRs.BuildAndReset(Scratch, ERangeSizeType::S32));
Members.AddRange(Batch.NameMember("FloatRs"), FloatRs.BuildAndReset(Scratch, ERangeSizeType::S64));
Members.AddRange(Batch.NameMember("EnumRs"), EnumRs.BuildAndReset(Scratch, ERangeSizeType::U8));
Members.AddRange(Batch.NameMember("UnusedEnumRs"), UnusedEnumRs.BuildAndReset(Scratch, ERangeSizeType::U8));
Members.AddRange(Batch.NameMember("UnicodeRs"), UnicodeRs.BuildAndReset(Scratch, ERangeSizeType::S32));
Members.AddRange(Batch.NameMember("StructRs"), StructRs.BuildAndReset(Scratch, ERangeSizeType::U64));
Members.AddRange(Batch.NameMember("StructRRs"), StructRRs.BuildAndReset(Scratch, ERangeSizeType::U32));
Batch.AddObject(Object, MoveTemp(Members));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 1);
FTestMemberReader It(Objects[0]);
TArray<FRangeView> IntRs = MakeArray<FRangeView>(It.GrabRange().AsRanges());
FNestedRangeView EmptyRs = It.GrabRange().AsRanges();
TArray<FRangeView> FloatRs = MakeArray<FRangeView>(It.GrabRange().AsRanges());
TArray<FRangeView> EnumRs = MakeArray<FRangeView>(It.GrabRange().AsRanges());
TArray<FRangeView> UnusedEnumRs = MakeArray<FRangeView>(It.GrabRange().AsRanges());
TArray<FRangeView> UnicodeRs = MakeArray<FRangeView>(It.GrabRange().AsRanges());
TArray<FRangeView> StructRs = MakeArray<FRangeView>(It.GrabRange().AsRanges());
TArray<FRangeView> StructRRs = MakeArray<FRangeView>(It.GrabRange().AsRanges());
CHECK(IntRs.Num() == 3);
CHECK(EqualItems(IntRs[0].AsLeaves().AsS32s(), MakeArrayView({1})));
CHECK(IntRs[1].IsEmpty());
CHECK(EqualItems(IntRs[2].AsLeaves().AsS32s(), MakeArrayView({2, 3})));
CHECK(EmptyRs.Num() == 0);
CHECK(EnumRs.Num() == 2);
CHECK(EnumRs[0].IsEmpty());
CHECK(EqualItems(EnumRs[1].AsLeaves().As<EAB>(), MakeArrayView({EAB::A, EAB(0), EAB::B})));
CHECK(UnusedEnumRs.Num() == 2);
CHECK(UnusedEnumRs[0].IsEmpty());
CHECK(EqualItems(UnusedEnumRs[1].AsLeaves().As<EUnused>(), TConstArrayView<EUnused>()));
CHECK(FloatRs.Num() == 3);
CHECK(EqualItems(FloatRs[0].AsLeaves().AsFloats(), MakeArrayView({1.f})));
CHECK(FloatRs[1].IsEmpty());
CHECK(EqualItems(FloatRs[2].AsLeaves().AsFloats(), MakeArrayView({2.f, 3.f})));
CHECK(StructRs.Num() == 1);
TArray<FTestMemberReader> XYs = MakeArray<FTestMemberReader>(StructRs[0].AsStructs());
CHECK(Names[XYs[0].PeekName()] == "X");
CHECK(XYs[0].GrabLeaf().AsFloat() == 1.f);
CHECK(Names[XYs[0].PeekName()] == "Y");
CHECK(XYs[0].GrabLeaf().AsFloat() == 2.f);
CHECK(Names[XYs[1].PeekName()] == "X");
CHECK(XYs[1].GrabLeaf().AsFloat() == 3.f);
CHECK(Names[XYs[1].PeekName()] == "Y");
CHECK(XYs[1].GrabLeaf().AsFloat() == 4.f);
CHECK(StructRRs.Num() == 1);
TArray<FRangeView> ZWRs = MakeArray<FRangeView>(StructRRs[0].AsRanges());
CHECK(ZWRs.Num() == 1);
TArray<FTestMemberReader> ZWs = MakeArray<FTestMemberReader>(ZWRs[0].AsStructs());
CHECK(ZWs.Num() == 3);
CHECK(Names[ZWs[0].PeekName()] == "Z");
CHECK(ZWs[0].GrabLeaf().AsFloat() == 1.5f);
CHECK(Names[ZWs[2].PeekName()] == "Z");
CHECK(ZWs[2].GrabLeaf().AsFloat() == 2.5f);
CHECK(Names[ZWs[2].PeekName()] == "W");
CHECK(ZWs[2].GrabLeaf().AsFloat() == 3.5f);
});
}
SECTION("UniRange")
{
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FMemberSpec StructMemberTypes[] = { UniRange(SpecBool), S32Range(SpecBool), UniRange(SpecBool), SpecBool };
FDeclId Struct = Batch.DeclareStruct("Test", "Struct", {"MaybeB", "Bs", "MaybeBs", "B"}, StructMemberTypes, EMemberPresence::AllowSparse);
FMemberSpec ObjectMemberTypes[] = { UniRange(SpecBool), S32Range(Struct), SpecBool, SpecBool };
FDeclId Object = Batch.DeclareStruct("Test", "Object", {"Bools", "Structs", "BF", "BT" }, ObjectMemberTypes, EMemberPresence::AllowSparse);
const bool True = true;
const bool False = false;
FNestedRangeBuilder MaybeBs(MakeLeafRangeSchema<bool, bool>(), 1);
FStructRangeBuilder Structs(10);
Structs[5].AddRange(Batch.NameMember("MaybeB"), BuildLeafRange(Scratch, &False, true));
Structs[6].AddRange(Batch.NameMember("MaybeB"), BuildLeafRange(Scratch, &True, false));
Structs[7].AddRange(Batch.NameMember("MaybeB"), BuildLeafRange(Scratch, &True, true));
Structs[7].AddRange(Batch.NameMember("Bs"), BuildLeafRange(Scratch, MakeArrayView({true, true, false, false, true, true, false, false, true, true})));
MaybeBs.Add(BuildLeafRange(Scratch, &True, true));
Structs[7].AddRange(Batch.NameMember("MaybeBs"), MaybeBs.BuildAndReset(Scratch, ERangeSizeType::Uni));
Structs[7].Add(Batch.NameMember("B"), true);
MaybeBs.Add(BuildLeafRange(Scratch, &True, false));
Structs[8].AddRange(Batch.NameMember("MaybeBs"), MaybeBs.BuildAndReset(Scratch, ERangeSizeType::Uni));
Structs[9].Add(Batch.NameMember("B"), false);
FMemberBuilder Members;
Members.AddRange(Batch.NameMember("Bools"), BuildLeafRange(Scratch, &True, true));
Members.AddRange(Batch.NameMember("Structs"), Structs.BuildAndReset(Scratch, Batch.Get(Struct), Batch.GetDebug()));
Members.Add(Batch.NameMember("BF"), false);
Members.Add(Batch.NameMember("BT"), true);
Batch.AddObject(Object, MoveTemp(Members));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 1);
FTestMemberReader It(Objects[0]);
FBoolRangeView Bools = It.GrabRange().AsLeaves().AsBools();
TArray<FTestMemberReader> Structs = MakeArray<FTestMemberReader>(It.GrabRange().AsStructs());
CHECK(It.GrabLeaf().AsBool() == false);
CHECK(It.GrabLeaf().AsBool() == true);
CHECK(Bools.Num() == 1);
CHECK(Bools[0] == true);
CHECK(EqualItems(Structs[5].GrabRange().AsLeaves().AsBools(), MakeArrayView({false})));
CHECK(Structs[6].GrabRange().AsLeaves().AsBools().Num() == 0);
CHECK(EqualItems(Structs[7].GrabRange().AsLeaves().AsBools(), MakeArrayView({true})));
CHECK(EqualItems(Structs[7].GrabRange().AsLeaves().AsBools(), MakeArrayView({true, true, false, false, true, true, false, false, true, true})));
TArray<FRangeView> MaybeBs7 = MakeArray<FRangeView>(Structs[7].GrabRange().AsRanges());
CHECK(MaybeBs7.Num() == 1);
CHECK(EqualItems(MaybeBs7[0].AsLeaves().AsBools(), MakeArrayView({true})));
CHECK(Structs[7].GrabLeaf().AsBool() == true);
TArray<FRangeView> MaybeBs8 = MakeArray<FRangeView>(Structs[8].GrabRange().AsRanges());
CHECK(MaybeBs8.Num() == 1);
CHECK(MaybeBs8[0].AsLeaves().AsBools().Num() == 0);
CHECK(Structs[9].GrabLeaf().AsBool() == false);
});
}
SECTION("DynamicStruct")
{
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FDeclId Unused1 = Batch.DeclareStruct("Test", "Unused1", {"X"}, {SpecS32}, EMemberPresence::AllowSparse);
FDeclId SA = Batch.DeclareStruct("Test", "SA", {"X"}, {SpecS32}, EMemberPresence::AllowSparse);
FDeclId Unused2 = Batch.DeclareStruct("Test", "Unused2", {"X"}, {SpecF32}, EMemberPresence::AllowSparse);
FDeclId SB = Batch.DeclareStruct("Test", "SB", {"X"}, {SpecF32}, EMemberPresence::AllowSparse);
FDeclId Object = Batch.DeclareStruct("Test", "Object", {"Same", "Some", "None", "Diff"}, {SA, SA, SpecBool, SpecDynamicStruct}, EMemberPresence::AllowSparse);
FDeclId Unused3 = Batch.DeclareStruct("Test", "Unused3", {"X"}, {SpecBool}, EMemberPresence::AllowSparse);
auto BuildStruct = [&](FDeclId Struct, auto X)
{
FMemberBuilder Members;
Members.Add(Batch.NameMember("X"), X);
return Members.BuildAndReset(Scratch, Batch.Get(Struct), Batch.GetDebug());
};
FMemberBuilder O1;
O1.AddStruct(Batch.NameMember("Same"), SA, BuildStruct(SA, 0));
O1.AddStruct(Batch.NameMember("Some"), SA, BuildStruct(SA, 1));
O1.AddStruct(Batch.NameMember("Diff"), SA, BuildStruct(SA, 2));
FMemberBuilder O2;
O2.AddStruct(Batch.NameMember("Same"), SA, BuildStruct(SA, 3));
O2.AddStruct(Batch.NameMember("Diff"), SB, BuildStruct(SB, 4.f));
Batch.AddObject(Object, MoveTemp(O1));
Batch.AddObject(Object, MoveTemp(O2));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 2);
FTestMemberReader O1(Objects[0]);
CHECK(O1.PeekType().AsStruct().IsDynamic == 0);
CHECK(FTestMemberReader(O1.GrabStruct()).GrabLeaf().AsS32() == 0);
CHECK(O1.PeekType().AsStruct().IsDynamic == 0);
CHECK(FTestMemberReader(O1.GrabStruct()).GrabLeaf().AsS32() == 1);
CHECK(O1.PeekType().AsStruct().IsDynamic == 1);
CHECK(FTestMemberReader(O1.GrabStruct()).GrabLeaf().AsS32() == 2);
FTestMemberReader O2(Objects[1]);
CHECK(O2.PeekType().AsStruct().IsDynamic == 0);
CHECK(FTestMemberReader(O2.GrabStruct()).GrabLeaf().AsS32() == 3);
CHECK(O2.PeekType().AsStruct().IsDynamic == 1);
CHECK(FTestMemberReader(O2.GrabStruct()).GrabLeaf().AsFloat() == 4.f);
});
}
SECTION("DynamicStructRange")
{
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FDeclId SA = Batch.DeclareStruct("Test", "SA", {"X"}, {S32Range(SpecS32)}, EMemberPresence::AllowSparse);
FDeclId Unused = Batch.DeclareStruct("Test", "Unused2", {"X"}, {S32Range(SpecS32)}, EMemberPresence::AllowSparse);
FDeclId SB = Batch.DeclareStruct("Test", "SB", {"X"}, {S32Range(SpecF32)}, EMemberPresence::AllowSparse);
FMemberSpec MemberTypes[] = { S32Range(SA), S32Range(SA), SpecBool, S32Range(SpecDynamicStruct), S32Range(SA), S32Range(SpecDynamicStruct), S32Range(S32Range(SpecDynamicStruct)) };
FDeclId Object = Batch.DeclareStruct("Test", "Object", {"Same", "Some", "None", "Diff", "SameEmpty", "DiffEmpty", "DiffNested"}, MemberTypes, EMemberPresence::AllowSparse);
auto BuildStructRange = [&](FDeclId Struct, auto X)
{
FStructRangeBuilder Members(1);
Members[0].Add(Batch.NameMember("X"), X);
return Members.BuildAndReset(Scratch, Batch.Get(Struct), Batch.GetDebug());
};
FMemberBuilder O1;
O1.AddRange(Batch.NameMember("Same"), BuildStructRange(SA, 10));
O1.AddRange(Batch.NameMember("Some"), BuildStructRange(SA, 11));
O1.AddRange(Batch.NameMember("Diff"), BuildStructRange(SA, 12));
O1.AddRange(Batch.NameMember("SameEmpty"), BuildStructRange(SA, 13));
O1.AddRange(Batch.NameMember("DiffEmpty"), BuildStructRange(SA, 14));
FNestedRangeBuilder NestedSA(MakeStructRangeSchema(ERangeSizeType::S32, SA), 1);
NestedSA.Add(BuildStructRange(SA, 100));
O1.AddRange(Batch.NameMember("DiffNested"), NestedSA.BuildAndReset(Scratch, ERangeSizeType::S32));
FMemberBuilder O2;
O2.AddRange(Batch.NameMember("Same"), BuildStructRange(SA, 20));
O2.AddRange(Batch.NameMember("Diff"), BuildStructRange(SB, 22.f));
O2.AddRange(Batch.NameMember("SameEmpty"), FStructRangeBuilder(0).BuildAndReset(Scratch, Batch.Get(SA), Batch.GetDebug()));
// PP-TEXT: Handle DiffEmpty in DynamicStructRange Test by printing dynamic type for empty ranges
// O2.AddRange(Batch.NameMember("DiffEmpty"), FStructRangeBuilder(0).BuildAndReset(Scratch, Batch.Get(SB), Batch.GetDebug()));
FNestedRangeBuilder NestedSB(MakeStructRangeSchema(ERangeSizeType::S32, SB), 1);
NestedSB.Add(BuildStructRange(SB, 200.f));
O2.AddRange(Batch.NameMember("DiffNested"), NestedSB.BuildAndReset(Scratch, ERangeSizeType::S32));
Batch.AddObject(Object, MoveTemp(O1));
Batch.AddObject(Object, MoveTemp(O2));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 2);
FTestMemberReader O1(Objects[0]);
CHECK(MakeArray<FTestMemberReader>(O1.GrabRange().AsStructs())[0].GrabLeaf().AsS32() == 10);
CHECK(MakeArray<FTestMemberReader>(O1.GrabRange().AsStructs())[0].GrabLeaf().AsS32() == 11);
CHECK(MakeArray<FTestMemberReader>(O1.GrabRange().AsStructs())[0].GrabLeaf().AsS32() == 12);
CHECK(MakeArray<FTestMemberReader>(O1.GrabRange().AsStructs())[0].GrabLeaf().AsS32() == 13);
CHECK(MakeArray<FTestMemberReader>(O1.GrabRange().AsStructs())[0].GrabLeaf().AsS32() == 14);
TArray<FRangeView> DiffNested1 = MakeArray<FRangeView>(O1.GrabRange().AsRanges());
CHECK(MakeArray<FTestMemberReader>(DiffNested1[0].AsStructs())[0].GrabLeaf().AsS32() == 100);
FTestMemberReader O2(Objects[1]);
CHECK(MakeArray<FTestMemberReader>(O2.GrabRange().AsStructs())[0].GrabLeaf().AsS32() == 20);
CHECK(MakeArray<FTestMemberReader>(O2.GrabRange().AsStructs())[0].GrabLeaf().AsFloat() == 22.f);
CHECK(O2.GrabRange().AsStructs().Num() == 0);
// CHECK(O2.GrabRange().AsStructs().Num() == 0);
TArray<FRangeView> DiffNested2 = MakeArray<FRangeView>(O2.GrabRange().AsRanges());
CHECK(MakeArray<FTestMemberReader>(DiffNested2[0].AsStructs())[0].GrabLeaf().AsFloat() == 200.f);
});
}
SECTION("Inheritance")
{
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FDeclId Unused = Batch.DeclareStruct("Test", "X", {"X"}, {SpecBool}, EMemberPresence::AllowSparse);
FDeclId Low = Batch.DeclareStruct("Test", "Low", {"LInt"}, {SpecS32}, EMemberPresence::AllowSparse);
FDeclId Mid = Batch.DeclareStruct("Test", "Mid", {"MInt", "MLow"}, {SpecS32, Low}, EMemberPresence::AllowSparse, ToOptional(Low));
FDeclId Top = Batch.DeclareStruct("Test", "Top", {"TInt", "TLow", "TMids"}, {SpecS32, Low, S32Range(Mid)}, EMemberPresence::AllowSparse, ToOptional(Mid));
FMemberBuilder Members;
Members.Add(Batch.NameMember("LInt"), 123);
Members.BuildSuperStruct(Scratch, Batch.Get(Low), Batch.GetDebug());
Members.Add(Batch.NameMember("MInt"), 456);
FMemberBuilder Nested;
Nested.Add(Batch.NameMember("LInt"), 1000);
Members.AddStruct(Batch.NameMember("MLow"), Low, Nested.BuildAndReset(Scratch, Batch.Get(Low), Batch.GetDebug()));
Members.BuildSuperStruct(Scratch, Batch.Get(Mid), Batch.GetDebug());
Members.Add(Batch.NameMember("TInt"), 789);
Nested.Add(Batch.NameMember("LInt"), 2000);
Members.AddStruct(Batch.NameMember("TLow"), Low, Nested.BuildAndReset(Scratch, Batch.Get(Low), Batch.GetDebug()));
FStructRangeBuilder NestedRange(1);
NestedRange[0].Add(Batch.NameMember("MInt"), 3000);
Nested.Add(Batch.NameMember("LInt"), 4000);
NestedRange[0].AddStruct(Batch.NameMember("MLow"), Low, Nested.BuildAndReset(Scratch, Batch.Get(Low), Batch.GetDebug()));
Members.AddRange(Batch.NameMember("TMids"), NestedRange.BuildAndReset(Scratch, Batch.Get(Mid), Batch.GetDebug()));
Batch.AddObject(Top, MoveTemp(Members));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 1);
FStructView TopView = Objects[0];
FTestMemberReader TopIt(TopView);
FStructView MidView = TopIt.GrabStruct();
FTestMemberReader MidIt(MidView);
FStructView LowView = MidIt.GrabStruct();
FTestMemberReader LowIt(LowView);
CHECK(LowIt.GrabLeaf().AsS32() == 123);
CHECK(MidIt.GrabLeaf().AsS32() == 456);
CHECK(FTestMemberReader(MidIt.GrabStruct()).GrabLeaf().AsS32() == 1000);
CHECK(Names[TopIt.PeekName()] == "TInt");
CHECK(TopIt.GrabLeaf().AsS32() == 789);
CHECK(Names[TopIt.PeekName()] == "TLow");
CHECK(FTestMemberReader(TopIt.GrabStruct()).GrabLeaf().AsS32() == 2000);
CHECK(Names[TopIt.PeekName()] == "TMids");
TArray<FTestMemberReader> MemberRangeIt = MakeArray<FTestMemberReader>(TopIt.GrabRange().AsStructs());
CHECK(MemberRangeIt[0].GrabLeaf().AsS32() == 3000);
CHECK(FTestMemberReader(MemberRangeIt[0].GrabStruct()).GrabLeaf().AsS32() == 4000);
const FStructSchema& TopSchema = TopView.Schema.Resolve();
const FStructSchema& MidSchema = MidView.Schema.Resolve();
const FStructSchema& LowSchema = LowView.Schema.Resolve();
CHECK(TopSchema.Inheritance == ESuper::Reused);
CHECK(MidSchema.Inheritance == ESuper::Reused);
CHECK(LowSchema.Inheritance == ESuper::No);
CHECK(TopSchema.NumMembers == 4);
CHECK(TopSchema.NumNames() == 3);
CHECK(MidSchema.NumMembers == MidSchema.NumNames() + 1);
CHECK(LowSchema.NumMembers == LowSchema.NumNames());
FFlatMemberReader FlatIt(Objects[0]);
CHECK(Names[FlatIt.PeekOwner().Name] == "Low");
CHECK(FlatIt.GrabLeaf().AsS32() == 123);
CHECK(Names[FlatIt.PeekOwner().Name] == "Mid");
CHECK(FlatIt.GrabLeaf().AsS32() == 456);
CHECK(Names[FlatIt.PeekOwner().Name] == "Mid");
CHECK(FTestMemberReader(FlatIt.GrabStruct()).GrabLeaf().AsS32() == 1000);
CHECK(Names[FlatIt.PeekOwner().Name] == "Top");
CHECK(FlatIt.GrabLeaf().AsS32() == 789);
CHECK(Names[FlatIt.PeekOwner().Name] == "Top");
CHECK(FTestMemberReader(FlatIt.GrabStruct()).GrabLeaf().AsS32() == 2000);
CHECK(Names[FlatIt.PeekOwner().Name] == "Top");
TArray<FFlatMemberReader> FlatRangeIt = MakeArray<FFlatMemberReader>(FlatIt.GrabRange().AsStructs());
CHECK(FlatRangeIt[0].GrabLeaf().AsS32() == 3000);
CHECK(FFlatMemberReader(FlatRangeIt[0].GrabStruct()).GrabLeaf().AsS32() == 4000);
CHECK(!FlatRangeIt[0].HasMore());
CHECK(!FlatIt.HasMore());
});
}
SECTION("SparseInheritance")
{
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{ // A B C usage
FDeclId B0 = Batch.DeclareStruct("Test", "B0", {"0"}, {SpecS32}, EMemberPresence::AllowSparse); // - - -
FDeclId B1 = Batch.DeclareStruct("Test", "B1", {"1"}, {SpecS32}, EMemberPresence::AllowSparse, ToOptional(B0)); // 1 1 1
FDeclId B2 = Batch.DeclareStruct("Test", "B2", {"2"}, {SpecS32}, EMemberPresence::AllowSparse, ToOptional(B1)); // - 1 1
FDeclId B3 = Batch.DeclareStruct("Test", "B3", {"3"}, {SpecS32}, EMemberPresence::AllowSparse, ToOptional(B2)); // - - 0
FDeclId B4 = Batch.DeclareStruct("Test", "B4", {"4"}, {SpecS32}, EMemberPresence::AllowSparse, ToOptional(B3)); // 1 1 1
FDeclId B5 = Batch.DeclareStruct("Test", "B5", {"5"}, {SpecS32}, EMemberPresence::AllowSparse, ToOptional(B4)); // 1 1 0
FDeclId B6 = Batch.DeclareStruct("Test", "B6", { }, { }, EMemberPresence::AllowSparse, ToOptional(B5)); // 0 - 0
FDeclId C5 = Batch.DeclareStruct("Test", "C5", {"5"}, {SpecS32}, EMemberPresence::AllowSparse, ToOptional(B4)); // - - -
FMemberBuilder A;
A.Add(Batch.NameMember("1"), 1);
A.BuildSuperStruct(Scratch, Batch.Get(B1), Batch.GetDebug());
A.Add(Batch.NameMember("4"), 4);
A.BuildSuperStruct(Scratch, Batch.Get(B4), Batch.GetDebug());
A.Add(Batch.NameMember("5"), 5);
A.BuildSuperStruct(Scratch, Batch.Get(B5), Batch.GetDebug());
FMemberBuilder B;
B.Add(Batch.NameMember("1"), 10);
B.BuildSuperStruct(Scratch, Batch.Get(B1), Batch.GetDebug());
B.Add(Batch.NameMember("2"), 20);
B.BuildSuperStruct(Scratch, Batch.Get(B2), Batch.GetDebug());
B.Add(Batch.NameMember("4"), 40);
B.BuildSuperStruct(Scratch, Batch.Get(B4), Batch.GetDebug());
FMemberBuilder C;
C.Add(Batch.NameMember("1"), 100);
C.BuildSuperStruct(Scratch, Batch.Get(B1), Batch.GetDebug());
C.Add(Batch.NameMember("2"), 200);
C.BuildSuperStruct(Scratch, Batch.Get(B2), Batch.GetDebug());
C.BuildSuperStruct(Scratch, Batch.Get(B3), Batch.GetDebug()); // Empty -> noop
C.Add(Batch.NameMember("4"), 400);
C.BuildSuperStruct(Scratch, Batch.Get(B4), Batch.GetDebug());
C.BuildSuperStruct(Scratch, Batch.Get(B5), Batch.GetDebug()); // Empty -> noop
Batch.AddObject(B6, MoveTemp(A));
Batch.AddObject(B5, MoveTemp(B));
Batch.AddObject(B6, MoveTemp(C));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
CHECK(Objects.Num() == 3);
const FStructSchema& Schema0 = Objects[0].Schema.Resolve();
const FStructSchema& Schema1 = Objects[1].Schema.Resolve();
const FStructSchema& Schema2 = Objects[2].Schema.Resolve();
CHECK(Names[Schema0.Type.Name] == "B6");
CHECK(Names[Schema1.Type.Name] == "B5");
CHECK(Names[Schema2.Type.Name] == "B6");
CHECK(Schema0.GetSuperSchema() == Objects[1].Schema.Id);
CHECK(Schema2.GetSuperSchema() == Objects[1].Schema.Id);
CHECK(Names[FMemberReader(Objects[0]).GrabStruct().Schema.Resolve().Type.Name] == "B5");
CHECK(Names[FMemberReader(Objects[1]).GrabStruct().Schema.Resolve().Type.Name] == "B4");
CHECK(Names[FMemberReader(Objects[2]).GrabStruct().Schema.Resolve().Type.Name] == "B4");
FSchemaBatchId BatchId = Objects[0].Schema.Batch;
const FStructSchema& B6 = Objects[0].Schema.Resolve();
const FStructSchema& B5 = ResolveStructSchema(BatchId, B6.GetSuperSchema().Get());
const FStructSchema& B4 = ResolveStructSchema(BatchId, B5.GetSuperSchema().Get());
const FStructSchema& B3 = ResolveStructSchema(BatchId, B4.GetSuperSchema().Get());
const FStructSchema& B2 = ResolveStructSchema(BatchId, B3.GetSuperSchema().Get());
const FStructSchema& B1 = ResolveStructSchema(BatchId, B2.GetSuperSchema().Get());
const FStructSchema& B0 = ResolveStructSchema(BatchId, B1.GetSuperSchema().Get());
// Super usage A B C Decl
CHECK(B0.Inheritance == ESuper::No); // - - - -
CHECK(B1.Inheritance == ESuper::Unused); // 0 0 0 B0
CHECK(B3.Inheritance == ESuper::Unused); // - - 0 B2
CHECK(B2.Inheritance == ESuper::Reused); // - B1 B1 B1
CHECK(B4.Inheritance == ESuper::Used); // B1 B2 B2 B3
CHECK(B5.Inheritance == ESuper::Reused); // B4 B4 0 B4
CHECK(B6.Inheritance == ESuper::Used); // B5 - B4 B5
FFlatMemberReader A(Objects[0]);
FFlatMemberReader B(Objects[1]);
FFlatMemberReader C(Objects[2]);
CHECK(A.GrabLeaf().AsS32() == 1);
CHECK(A.GrabLeaf().AsS32() == 4);
CHECK(A.GrabLeaf().AsS32() == 5);
CHECK(B.GrabLeaf().AsS32() == 10);
CHECK(B.GrabLeaf().AsS32() == 20);
CHECK(B.GrabLeaf().AsS32() == 40);
CHECK(C.GrabLeaf().AsS32() == 100);
CHECK(C.GrabLeaf().AsS32() == 200);
CHECK(C.GrabLeaf().AsS32() == 400);
CHECK(!A.HasMore());
CHECK(!B.HasMore());
CHECK(!C.HasMore());
});
}
SECTION("SparseIndex")
{
TestSerialize([](FTestBatchBuilder& Batch, FScratchAllocator& Scratch)
{
FScopeId Unused = Batch.MakeScope("Unused");
FScopeId NestedUnused1 = Batch.NestScope(Unused, "NestedUnused1");
FScopeId FlatUsed = Batch.MakeScope("FlatUsed");
FScopeId NestedUsed = Batch.NestScope(FlatUsed, "NestedUsed");
FScopeId NestedUnused2 = Batch.NestScope(Unused, "NestedUnused2");
FScopeId DoubleNested = Batch.NestScope(NestedUsed, "DoubleNested");
FScopeId NestedUnused3 = Batch.NestScope(FlatUsed, "NestedUnused3");
FType E1T{NestedUnused1, Batch.MakeTypename("E1")};
FType E2T{NestedUsed, Batch.MakeTypename("E2")};
FType E3T{NestedUnused2, Batch.MakeTypename("E3")};
FEnumId E1D = Batch.DeclareEnum(E1T, EEnumMode::Flat, ELeafWidth::B8, {"C1"}, {1});
FEnumId E2D = Batch.DeclareEnum(E2T, EEnumMode::Flat, ELeafWidth::B8, {"C2"}, {2});
FEnumId E3D = Batch.DeclareEnum(E3T, EEnumMode::Flat, ELeafWidth::B8, {"C3"}, {3});
FType S1T{NestedUnused1, Batch.MakeTypename("S1")};
FType S2T{NestedUsed, Batch.MakeTypename("S2")};
FType S3T = Batch.MakeParametricType({NestedUnused2, Batch.MakeTypename("S3")}, {S1T});
FType S4T = Batch.MakeParametricType({DoubleNested, Batch.MakeTypename("S4")}, {S2T, E2T});
FType S5T = Batch.MakeParametricType({NestedUnused3, Batch.MakeTypename("S5")}, {E3T, E1T, S2T});
FDeclId S1D = Batch.DeclareStruct(S1T, {"M1"}, {SpecS32}, EMemberPresence::AllowSparse);
FDeclId S2D = Batch.DeclareStruct(S2T, {"M2"}, {SpecS32}, EMemberPresence::AllowSparse);
FDeclId S3D = Batch.DeclareStruct(S3T, {"M3"}, {SpecS32}, EMemberPresence::AllowSparse);
FDeclId S4D = Batch.DeclareStruct(S4T, {"M4"}, {SpecS32}, EMemberPresence::AllowSparse);
FDeclId S5D = Batch.DeclareStruct(S5T, {"M5"}, {SpecS32}, EMemberPresence::AllowSparse);
FMemberBuilder Members;
Members.Add(Batch.NameMember("M4"), 1);
Batch.AddObject(S4D, MoveTemp(Members));
},
[](TConstArrayView<FStructView> Objects, const FTestNameReader& Names)
{
FSchemaBatchId Batch = Objects[0].Schema.Batch;
FType S4T = Objects[0].Schema.Resolve().Type;
FNestedScope DoubleNested = ResolveUntranslatedNestedScope(Batch, S4T.Scope.AsNested());
FNestedScope NestedUsed = ResolveUntranslatedNestedScope(Batch, DoubleNested.Outer.AsNested());
FFlatScopeId FlatUsed = NestedUsed.Outer.AsFlat();
CHECK(Names[DoubleNested.Inner.Name] == "DoubleNested");
CHECK(Names[NestedUsed.Inner.Name] == "NestedUsed");
CHECK(Names[FlatUsed.Name] == "FlatUsed");
FParametricTypeView S4 = ResolveUntranslatedParametricType(Batch, S4T.Name.AsParametric());
CHECK(Names[S4.Name.Get().Id] == "S4");
CHECK(S4.NumParameters == 2);
FType S2T = S4.Parameters[0];
FType E2T = S4.Parameters[1];
CHECK(S2T.Scope == DoubleNested.Outer);
CHECK(E2T.Scope == DoubleNested.Outer);
CHECK(Names[S2T.Name.AsConcrete().Id] == "S2");
CHECK(Names[E2T.Name.AsConcrete().Id] == "E2");
});
}
}
////////////////////////////////////////////////////////////////////////////
TEST_CASE_NAMED(FPlainPropsLoadSaveTest, "System::Core::Serialization::PlainProps::LoadSave", "[Core][PlainProps][SmokeFilter]")
{
SECTION("Leaves")
{}
SECTION("Enums")
{}
SECTION("NestedStruct")
{}
SECTION("StaticArray")
{}
SECTION("LeafVariant")
{}
SECTION("BitfieldBool")
{}
SECTION("LeafArray")
{}
SECTION("LeafOptional")
{}
SECTION("LeafSmartPtr")
{}
SECTION("LeafSetWhole")
{}
SECTION("LeafSparseArrayAppends")
{}
SECTION("LeafSetOps")
{}
SECTION("SparseStructArray")
{}
SECTION("DenseStructArray")
{}
SECTION("SubStructArray")
{}
SECTION("NestedLeafArray")
{}
SECTION("NestedStructArray")
{}
SECTION("StructToSubStructMapOps")
{}
}
} // namespace PlainProps
#endif // WITH_TESTS