493 lines
15 KiB
C++
493 lines
15 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PlainPropsBuildSchema.h"
|
|
#include "PlainPropsInternalBuild.h"
|
|
#include "PlainPropsInternalFormat.h"
|
|
#include "PlainPropsInternalPrint.h"
|
|
#include "Algo/Compare.h"
|
|
#include "Algo/Find.h"
|
|
#include "Containers/Map.h"
|
|
#include "Containers/StringConv.h"
|
|
#include "Misc/StringBuilder.h"
|
|
|
|
namespace PlainProps
|
|
{
|
|
|
|
static FString PrintMemberSchema(const FIds& Ids, FMemberSchema Schema)
|
|
{
|
|
TUtf8StringBuilder<256> Utf8SchemaStr;
|
|
PrintMemberSchema(Utf8SchemaStr, Ids, Schema);
|
|
FString SchemaStr(FStringView(StringCast<TCHAR>(Utf8SchemaStr.GetData(), Utf8SchemaStr.Len())));
|
|
|
|
FMemberType InnermostType = Schema.GetInnermostType();
|
|
if (InnermostType.IsStruct())
|
|
{
|
|
return FString::Printf(TEXT("%sstruct [%d]%s%s => %s"),
|
|
Schema.Type.IsRange() ? TEXT("Range(s) of ") : TEXT(""),
|
|
Schema.InnerSchema.Get().Idx,
|
|
InnermostType.AsStruct().IsSuper ? TEXT(" (super)") : TEXT(""),
|
|
InnermostType.AsStruct().IsDynamic ? TEXT(" (dynamic)") : TEXT(""),
|
|
*SchemaStr);
|
|
}
|
|
else if (IsEnum(InnermostType))
|
|
{
|
|
return FString::Printf(TEXT("%s%s [%d] => %s"),
|
|
Schema.Type.IsRange() ? TEXT("Range(s) of ") : TEXT(""),
|
|
StringCast<TCHAR>(ToString(InnermostType.AsLeaf()).GetData()).Get(),
|
|
Schema.InnerSchema.Get().Idx,
|
|
*SchemaStr);
|
|
}
|
|
return SchemaStr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
struct FStructSchemaBuilder
|
|
{
|
|
FStructSchemaBuilder(FStructId InId, const FStructDeclaration& InDecl, FSchemasBuilder& InSchemas);
|
|
|
|
const FStructDeclaration& Declaration;
|
|
FSchemasBuilder& AllSchemas;
|
|
uint16 MinMembers = 0xFFFF;
|
|
FStructId Id;
|
|
FOptionalMemberId* MemberOrder = nullptr;
|
|
FMemberSchema* NotedSchemas = nullptr;
|
|
TBitArray<> NotedMembers;
|
|
|
|
void NoteMembersRecursively(const FBuiltStruct& Struct);
|
|
void NoteRangeRecursively(ERangeSizeType NumType, TConstArrayView<FMemberType> Types, void* InnermostSchemaBuilder, const FBuiltRange* Range, FMemberId Member);
|
|
FBuiltStructSchema Build() const;
|
|
};
|
|
|
|
struct FEnumSchemaBuilder
|
|
{
|
|
const FEnumDeclaration& Declaration;
|
|
FDebugIds Debug;
|
|
FEnumId Id;
|
|
TOptional<ELeafWidth> NotedWidth;
|
|
TSet<uint64> NotedConstants;
|
|
|
|
void NoteValue(ELeafWidth Width, uint64 Value, FStructId Struct, FMemberId Member);
|
|
void NoteEmpty(ELeafWidth Width);
|
|
FBuiltEnumSchema Build() const;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
FSchemasBuilder::FSchemasBuilder(const FIds& Names, const IDeclarations& Types, FScratchAllocator& InScratch, ESchemaFormat InFormat)
|
|
: Declarations(Types)
|
|
, Ids(Names)
|
|
, Format(InFormat)
|
|
, Scratch(InScratch)
|
|
, Debug(Names)
|
|
{}
|
|
|
|
FSchemasBuilder::~FSchemasBuilder() {}
|
|
|
|
FEnumSchemaBuilder& FSchemasBuilder::NoteEnum(FEnumId Id)
|
|
{
|
|
checkf(!bBuilt, TEXT("Noted new members after building"));
|
|
|
|
FSetElementId Idx = EnumIndices.FindId(Id);
|
|
if (Idx.IsValidId())
|
|
{
|
|
return Enums[Idx.AsInteger()];
|
|
}
|
|
|
|
auto Declaration = Declarations.Find(Id);
|
|
checkf(Declaration, TEXT("Undeclared enum '%s' noted"), *Debug.Print(Id));
|
|
|
|
Idx = EnumIndices.Emplace(Id);
|
|
check(Idx.AsInteger() == Enums.Num());
|
|
Enums.Emplace(*Declaration, Debug, Id);
|
|
|
|
return Enums.Last();
|
|
}
|
|
|
|
FStructSchemaBuilder& FSchemasBuilder::NoteStruct(FStructId Id)
|
|
{
|
|
checkf(!bBuilt, TEXT("Noted new members after building"));
|
|
|
|
// Id is either FBindId or FDeclId depending on Format
|
|
// Most bind ids are the same as decl ids though
|
|
if (FSetElementId Idx = StructIndices.FindId(Id); Idx.IsValidId())
|
|
{
|
|
return Structs[Idx.AsInteger()];
|
|
}
|
|
|
|
const FStructDeclaration* Declaration = Declarations.Find(Id);
|
|
checkf(Declaration, TEXT("Undeclared struct '%s' noted"), *Debug.Print(Id));
|
|
|
|
// StableNames format can have lowered ids, so builder might exist already
|
|
FStructId NoteId = (Format == ESchemaFormat::StableNames) ? FStructId(Declaration->Id) : Id;
|
|
if (NoteId != Id)
|
|
{
|
|
if (FSetElementId Idx = StructIndices.FindId(NoteId); Idx.IsValidId())
|
|
{
|
|
return Structs[Idx.AsInteger()];
|
|
}
|
|
}
|
|
|
|
// First time noted, make new builder
|
|
FSetElementId Idx = StructIndices.Emplace(NoteId);
|
|
check(Idx.AsInteger() == Structs.Num());
|
|
Structs.Emplace(NoteId, *Declaration, *this);
|
|
|
|
return Structs.Last();
|
|
}
|
|
|
|
void FSchemasBuilder::NoteStructAndMembers(FStructId Id, const FBuiltStruct& Struct)
|
|
{
|
|
NoteStruct(Id).NoteMembersRecursively(Struct);
|
|
}
|
|
|
|
FBuiltSchemas FSchemasBuilder::Build()
|
|
{
|
|
checkf(!bBuilt, TEXT("Already built"));
|
|
bBuilt = true;
|
|
|
|
NoteInheritanceChains();
|
|
|
|
FBuiltSchemas Out;
|
|
Out.Structs.Reserve(Structs.Num());
|
|
Out.Enums.Reserve(Enums.Num());
|
|
|
|
for (FStructSchemaBuilder& Struct : Structs)
|
|
{
|
|
Out.Structs.Emplace(Struct.Build());
|
|
}
|
|
for (FEnumSchemaBuilder& Enum : Enums)
|
|
{
|
|
Out.Enums.Emplace(Enum.Build());
|
|
}
|
|
|
|
return Out;
|
|
}
|
|
|
|
void FSchemasBuilder::NoteInheritanceChains()
|
|
{
|
|
for (int Idx = 0, Num = Structs.Num(); Idx < Num; ++Idx)
|
|
{
|
|
const FStructDeclaration* Declaration = &Structs[Idx].Declaration;
|
|
while (Declaration->Super)
|
|
{
|
|
FDeclId Super = Declaration->Super.Get();
|
|
if (StructIndices.Contains(Super))
|
|
{
|
|
break;
|
|
}
|
|
|
|
Declaration = Declarations.Find(Super);
|
|
checkf(Declaration, TEXT("Undeclared super struct '%s' noted"), *Debug.Print(Super));
|
|
|
|
FSetElementId ElemId = StructIndices.Emplace(Super);
|
|
check(ElemId.AsInteger() == Structs.Num());
|
|
Structs.Emplace(Super, *Declaration, *this);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
FStructSchemaBuilder::FStructSchemaBuilder(FStructId InId, const FStructDeclaration& Decl, FSchemasBuilder& Schemas)
|
|
: Declaration(Decl)
|
|
, AllSchemas(Schemas)
|
|
, Id(InId)
|
|
, NotedMembers(false, Declaration.NumMembers + int32(!!Declaration.Super))
|
|
{
|
|
if (int32 Num = NotedMembers.Num())
|
|
{
|
|
MemberOrder = Schemas.GetScratch().AllocateArray<FOptionalMemberId>(Num);
|
|
NotedSchemas = Schemas.GetScratch().AllocateArray<FMemberSchema>(Num);
|
|
|
|
MemberOrder[0] = NoId; // In case unnamed super exists
|
|
TConstArrayView<FMemberId> Order = Declaration.GetMemberOrder();
|
|
FMemory::Memcpy(MemberOrder + int32(!!Declaration.Super), Order.GetData(), Order.NumBytes());
|
|
}
|
|
}
|
|
|
|
static bool RequiresDynamicStructSchema(const FMemberSchema& A, const FMemberSchema& B)
|
|
{
|
|
if (A.InnerSchema != B.InnerSchema && A.Type.GetKind() == B.Type.GetKind())
|
|
{
|
|
if (A.Type.IsStruct())
|
|
{
|
|
return true;
|
|
}
|
|
else if (A.Type.IsRange() && A.GetInnermostType().IsStruct() && B.GetInnermostType().IsStruct())
|
|
{
|
|
// Same range size and nested range sizes
|
|
return A.Type == B.Type && Algo::Compare( A.GetInnerRangeTypes().LeftChop(1),
|
|
B.GetInnerRangeTypes().LeftChop(1));
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void SetIsDynamic(FMemberType& InOut)
|
|
{
|
|
FStructType Type = InOut.AsStruct();
|
|
Type.IsDynamic = true;
|
|
InOut = FMemberType(Type);
|
|
}
|
|
|
|
static void* NoteStructOrEnum(FSchemasBuilder& AllSchemas, bool bStruct, FInnerId Id)
|
|
{
|
|
return bStruct ? static_cast<void*>(&AllSchemas.NoteStruct(Id.AsStructBindId())) : &AllSchemas.NoteEnum(Id.AsEnum());
|
|
}
|
|
|
|
void FStructSchemaBuilder::NoteMembersRecursively(const FBuiltStruct& Struct)
|
|
{
|
|
checkf(Declaration.Occupancy != EMemberPresence::RequireAll || Struct.NumMembers == Declaration.NumMembers,
|
|
TEXT("'%s' with %d members noted while declared to always have all %d members"), *AllSchemas.GetDebug().Print(Declaration.Id), Struct.NumMembers, Declaration.NumMembers);
|
|
MinMembers = FMath::Min(MinMembers, Struct.NumMembers);
|
|
|
|
if (Struct.NumMembers == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int32 NumNoted = NotedMembers.Num();
|
|
int32 NoteIdx = 0;
|
|
for (const FBuiltMember& Member : MakeArrayView(Struct.Members, Struct.NumMembers))
|
|
{
|
|
while (MemberOrder[NoteIdx] != Member.Name)
|
|
{
|
|
++NoteIdx;
|
|
check(NoteIdx < NumNoted);
|
|
}
|
|
|
|
if (NotedMembers[NoteIdx])
|
|
{
|
|
FMemberSchema& NotedSchema = NotedSchemas[NoteIdx];
|
|
if (RequiresDynamicStructSchema(NotedSchema, Member.Schema))
|
|
{
|
|
if (!NotedSchema.GetInnermostType().AsStruct().IsDynamic)
|
|
{
|
|
SetIsDynamic(NotedSchema.EditInnermostType(AllSchemas.GetScratch()));
|
|
NotedSchema.InnerSchema = NoId;
|
|
}
|
|
check(NotedSchema.InnerSchema == NoId);
|
|
|
|
}
|
|
else
|
|
{
|
|
checkf(NotedSchema == Member.Schema, TEXT("Member '%s' in '%s' first added as '%s' and later as '%s'."),
|
|
*AllSchemas.GetDebug().Print(Member.Name),
|
|
*AllSchemas.GetDebug().Print(Declaration.Id),
|
|
*PrintMemberSchema(AllSchemas.GetIds(), NotedSchema),
|
|
*PrintMemberSchema(AllSchemas.GetIds(), Member.Schema));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NotedMembers[NoteIdx] = true;
|
|
NotedSchemas[NoteIdx] = Member.Schema;
|
|
}
|
|
|
|
++NoteIdx;
|
|
|
|
const FMemberSchema& Schema = Member.Schema;
|
|
if (Schema.InnerSchema)
|
|
{
|
|
checkSlow(IsStructOrEnum(Schema.GetInnermostType()));
|
|
FInnerId InnerSchema = Schema.InnerSchema.Get();
|
|
|
|
switch (Schema.Type.GetKind())
|
|
{
|
|
case EMemberKind::Leaf:
|
|
AllSchemas.NoteEnum(InnerSchema.AsEnum()).NoteValue(Schema.Type.AsLeaf().Width, Member.Value.Leaf, Id, Member.Name.Get());
|
|
break;
|
|
|
|
case EMemberKind::Struct:
|
|
AllSchemas.NoteStruct(InnerSchema.AsStructBindId()).NoteMembersRecursively(*Member.Value.Struct);
|
|
break;
|
|
|
|
case EMemberKind::Range:
|
|
void* InnerSchemaBuilder = NoteStructOrEnum(AllSchemas, Schema.GetInnermostType().IsStruct(), InnerSchema);
|
|
NoteRangeRecursively(Schema.Type.AsRange().MaxSize, Schema.GetInnerRangeTypes(), InnerSchemaBuilder, Member.Value.Range, Member.Name.Get());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename IntType>
|
|
void NoteEnumValues(FEnumSchemaBuilder& Schema, const IntType* Values, uint64 Num, FStructId Struct, FMemberId Member)
|
|
{
|
|
for (IntType Value : TConstArrayView64<IntType>(Values, Num))
|
|
{
|
|
Schema.NoteValue(LeafWidth<sizeof(IntType)>, Value, Struct, Member);
|
|
}
|
|
}
|
|
|
|
static void NoteEnumRange(FEnumSchemaBuilder& Out, FLeafType Leaf, const FBuiltRange& Range, FStructId Struct, FMemberId Member)
|
|
{
|
|
check(Leaf.Type == ELeafType::Enum);
|
|
switch (Leaf.Width)
|
|
{
|
|
case ELeafWidth::B8: NoteEnumValues(Out, reinterpret_cast<const uint8* >(Range.Data), Range.Num, Struct, Member); break;
|
|
case ELeafWidth::B16: NoteEnumValues(Out, reinterpret_cast<const uint16*>(Range.Data), Range.Num, Struct, Member); break;
|
|
case ELeafWidth::B32: NoteEnumValues(Out, reinterpret_cast<const uint32*>(Range.Data), Range.Num, Struct, Member); break;
|
|
case ELeafWidth::B64: NoteEnumValues(Out, reinterpret_cast<const uint64*>(Range.Data), Range.Num, Struct, Member); break;
|
|
}
|
|
}
|
|
|
|
static void NoteEmptyRange(ERangeSizeType NumType, TConstArrayView<FMemberType> Types, void* InnermostSchema)
|
|
{
|
|
FMemberType InnermostType = Types.Last();
|
|
if (IsEnum(InnermostType))
|
|
{
|
|
static_cast<FEnumSchemaBuilder*>(InnermostSchema)->NoteEmpty(InnermostType.AsLeaf().Width);
|
|
}
|
|
}
|
|
|
|
void FStructSchemaBuilder::NoteRangeRecursively(ERangeSizeType NumType, TConstArrayView<FMemberType> Types, void* InnermostSchema, const FBuiltRange* Range, FMemberId Member)
|
|
{
|
|
if (Range == nullptr)
|
|
{
|
|
NoteEmptyRange(NumType, Types, InnermostSchema);
|
|
return;
|
|
}
|
|
checkf(Range->Num > 0, TEXT("Range was built but without values"));
|
|
FMemberType Type = Types[0];
|
|
switch (Type.GetKind())
|
|
{
|
|
case EMemberKind::Struct:
|
|
for (const FBuiltStruct* Struct : Range->AsStructs())
|
|
{
|
|
static_cast<FStructSchemaBuilder*>(InnermostSchema)->NoteMembersRecursively(*Struct);
|
|
}
|
|
break;
|
|
case EMemberKind::Range:
|
|
for (const FBuiltRange* InnerRange : Range->AsRanges())
|
|
{
|
|
NoteRangeRecursively(Type.AsRange().MaxSize, Types.RightChop(1), InnermostSchema, InnerRange, Member);
|
|
}
|
|
break;
|
|
case EMemberKind::Leaf:
|
|
NoteEnumRange(/* out */ *static_cast<FEnumSchemaBuilder*>(InnermostSchema), Type.AsLeaf(), *Range, Id, Member);
|
|
break;
|
|
}
|
|
}
|
|
|
|
FBuiltStructSchema FStructSchemaBuilder::Build() const
|
|
{
|
|
FType Type = AllSchemas.GetIds().Resolve(Id);
|
|
FBuiltStructSchema Out = { Type, Id, ToOptionalStruct(Declaration.Super), /* dense */ true };
|
|
|
|
if (int32 Num = NotedMembers.CountSetBits())
|
|
{
|
|
Out.bDense = Declaration.Occupancy == EMemberPresence::RequireAll || MinMembers == Num;
|
|
Out.MemberNames.Reserve(Num);
|
|
Out.MemberSchemas.Reserve(Num);
|
|
for (int32 NoteIdx = 0, NoteNum = NotedMembers.Num(); NoteIdx < NoteNum; ++NoteIdx)
|
|
{
|
|
if (NotedMembers[NoteIdx])
|
|
{
|
|
if (FOptionalMemberId Name = MemberOrder[NoteIdx])
|
|
{
|
|
Out.MemberNames.Add(Name.Get());
|
|
}
|
|
Out.MemberSchemas.Add(&NotedSchemas[NoteIdx]);
|
|
}
|
|
}
|
|
|
|
check(Num == Out.MemberSchemas.Num());
|
|
}
|
|
|
|
return Out;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
FBuiltEnumSchema FEnumSchemaBuilder::Build() const
|
|
{
|
|
FBuiltEnumSchema Out = { Declaration.Type, Id };
|
|
Out.Mode = Declaration.Mode;
|
|
Out.Width = NotedWidth.GetValue();
|
|
|
|
if (int32 Num = NotedConstants.Num())
|
|
{
|
|
Out.Names.Reserve(Num);
|
|
Out.Constants.Reserve(Num);
|
|
for (FEnumerator Enumerator : Declaration.GetEnumerators())
|
|
{
|
|
if (NotedConstants.Contains(Enumerator.Constant))
|
|
{
|
|
Out.Names.Add(Enumerator.Name);
|
|
Out.Constants.Add(Enumerator.Constant);
|
|
}
|
|
}
|
|
}
|
|
|
|
checkf( NotedConstants.Num() == Out.Constants.Num() ||
|
|
NotedConstants.Num() == Out.Constants.Num() + (Out.Mode == EEnumMode::Flag),
|
|
TEXT("Noted %d constants but included %d in fla%c enum %s"),
|
|
NotedConstants.Num(), Out.Constants.Num(), "tg"[Out.Mode == EEnumMode::Flag], *Debug.Print(Id));
|
|
return Out;
|
|
}
|
|
|
|
void FEnumSchemaBuilder::NoteValue(ELeafWidth Width, uint64 Value, FStructId Struct, FMemberId Member)
|
|
{
|
|
check(NotedWidth == NullOpt || NotedWidth == Width);
|
|
NotedWidth = Width;
|
|
|
|
if (Declaration.Mode == EEnumMode::Flag)
|
|
{
|
|
if (Value == 0)
|
|
{
|
|
// Don't validate 0 flag is declared, it isn't
|
|
NotedConstants.Add(Value);
|
|
}
|
|
else
|
|
{
|
|
const int32 NumValidated = NotedConstants.Num();
|
|
while (Value != 0)
|
|
{
|
|
uint64 HiBit = uint64(1) << FMath::FloorLog2_64(Value);
|
|
NotedConstants.Add(HiBit);
|
|
Value &= ~HiBit;
|
|
}
|
|
|
|
for (int32 Idx = NumValidated, Num = NotedConstants.Num(); Idx < Num; ++Idx)
|
|
{
|
|
uint64 Flag = NotedConstants.Get(FSetElementId::FromInteger(Idx));
|
|
checkf(Algo::FindBy(Declaration.GetEnumerators(), Flag, &FEnumerator::Constant), TEXT("Enum flag %d is undeclared in %s, illegal value detected in %s::%s"), Flag, *Debug.Print(Id), *Debug.Print(Struct), *Debug.Print(Member));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool bValidated;
|
|
NotedConstants.FindOrAdd(Value, /* out */ &bValidated);
|
|
if (!bValidated)
|
|
{
|
|
checkf(Algo::FindBy(Declaration.GetEnumerators(), Value, &FEnumerator::Constant), TEXT("Enum value %d is undeclared in %s, illegal value detected in %s::%s"), Value, *Debug.Print(Id), *Debug.Print(Struct), *Debug.Print(Member));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FEnumSchemaBuilder::NoteEmpty(ELeafWidth Width)
|
|
{
|
|
check(NotedWidth == NullOpt || NotedWidth == Width);
|
|
NotedWidth = Width;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
TArray<FStructId> ExtractRuntimeIds(const FBuiltSchemas& In)
|
|
{
|
|
TArray<FStructId> Out;
|
|
Out.SetNumUninitialized(In.Structs.Num());
|
|
const FBuiltStructSchema* InIt = In.Structs.GetData();
|
|
for (FStructId& Id : Out)
|
|
{
|
|
Id = (*InIt++).Id;
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
} // namespace PlainProps
|