// Copyright Epic Games, Inc. All Rights Reserved. #include "PlainPropsBuild.h" #include "PlainPropsIndex.h" #include "PlainPropsInternalBuild.h" #include "PlainPropsInternalFormat.h" #include "Serialization/VarInt.h" #include "Templates/TypeCompatibleBytes.h" #include "Hash/xxhash.h" namespace PlainProps { uint8* FScratchAllocator::AllocateInNewPage(SIZE_T Size, uint32 Alignment) { if (Size >= DataSize || (DataSize - Size) < static_cast(PageEnd - Cursor)) { SIZE_T LonePageSize = Align(offsetof(FPage, Data) + Size, Alignment); FPage*& PrevPage = LastPage ? LastPage->PrevPage : LastPage; FPage Header = { PrevPage }; PrevPage = new (FMemory::Malloc(LonePageSize)) FPage { Header }; return Align(PrevPage->Data, Alignment); } FPage Header = { LastPage }; LastPage = new (FMemory::Malloc(PageSize)) FPage { Header }; uint8* Out = Align(LastPage->Data, Alignment); Cursor = Out + Size; PageEnd = LastPage->Data + DataSize; check(Cursor <= PageEnd); return Out; } FScratchAllocator::~FScratchAllocator() { for (FPage* It = LastPage; It; ) { FPage* Prev = It->PrevPage; FMemory::Free(It); It = Prev; } } FMemberType& FMemberSchema::EditInnermostType(FScratchAllocator& Scratch) { if (NumInnerRanges > 1) { FMemberType* Clone = Scratch.AllocateArray(NumInnerRanges); FMemory::Memcpy(Clone, NestedRangeTypes, NumInnerRanges * sizeof(FMemberType)); NestedRangeTypes = Clone; return Clone[NumInnerRanges - 1]; } return NumInnerRanges == 0 ? Type : InnerRangeType; } ////////////////////////////////////////////////////////////////////////// FBuiltRange* FBuiltRange::Create(FScratchAllocator& Scratch, uint64 NumItems, SIZE_T ItemSize) { check(NumItems > 0); FBuiltRange* Out = new (Scratch.Allocate(sizeof(FBuiltRange) + NumItems * ItemSize, alignof(FBuiltRange))) FBuiltRange; Out->Num = NumItems; return Out; } ////////////////////////////////////////////////////////////////////////// FMemberSchema MakeNestedRangeSchema(FScratchAllocator& Scratch, ERangeSizeType SizeType, FMemberSchema InnerRangeSchema) { check(InnerRangeSchema.NumInnerRanges > 0); uint16 NumInnerRanges = IntCastChecked(1 + InnerRangeSchema.NumInnerRanges); FMemberType* InnerRangeTypes = Scratch.AllocateArray(NumInnerRanges); InnerRangeTypes[0] = InnerRangeSchema.Type; FMemory::Memcpy(&InnerRangeTypes[1], InnerRangeSchema.GetInnerRangeTypes().GetData(), InnerRangeSchema.NumInnerRanges * sizeof(FMemberType)); return { FMemberType(SizeType), InnerRangeSchema.Type, NumInnerRanges, InnerRangeSchema.InnerSchema, InnerRangeTypes }; } ////////////////////////////////////////////////////////////////////////// FBuiltRange* CloneLeaves(FScratchAllocator& Scratch, uint64 Num, const void* InData, SIZE_T LeafSize) { if (Num == 0) { return nullptr; } FBuiltRange* Out = FBuiltRange::Create(Scratch, Num, LeafSize); FMemory::Memcpy(Out->Data, InData, Num * LeafSize); return Out; } // template // void NormalizeFloats(FloatType* Values, uint64 Num) // { // for (uint64 Idx = 0; Idx < Num; ++Idx) // { // // Reject NaN / INF and ignore negative zero for now // checkf(FMath::IsFinite(Values[Idx]), TEXT("Saving NaN or INF isn't supported")); // } // } // // void NormalizeLeafRange(FUnpackedLeafType Leaf, FBuiltRange& Out) // { // check(Leaf.Type == ELeafType::Float); // if (Leaf.Width == ELeafWidth::B32) // { // NormalizeFloats(reinterpret_cast(Out.Data), Out.Num); // } // else // { // check(Leaf.Width == ELeafWidth::B64); // NormalizeFloats(reinterpret_cast(Out.Data), Out.Num); // } // } //} // namespace Private ////////////////////////////////////////////////////////////////////////// void FMemberBuilder::AddSuperStruct(FStructId SuperSchema, FBuiltStruct* SuperStruct) { check(SuperStruct); check(Members.IsEmpty()); Members.Emplace(FBuiltMember::MakeSuper(SuperSchema, SuperStruct)); check(IsSuper(Members[0].Schema.Type)); } void FMemberBuilder::BuildSuperStruct(FScratchAllocator& Scratch, const FStructDeclaration& Super, const FDebugIds& Debug) { // If we need to support dense substructs, we need access to struct declaration here // or create an empty super structs that we throw away in BuildAndReset. if (Members.IsEmpty() || (Members.Num() == 1 && IsSuper(Members[0].Schema.Type))) { return; } FBuiltStruct* OnlyMember = BuildAndReset(Scratch, Super, Debug); Members.Emplace(FBuiltMember::MakeSuper(Super.Id, MoveTemp(OnlyMember))); check(IsSuper(Members[0].Schema.Type)); } FBuiltStruct* FMemberBuilder::BuildAndReset(FScratchAllocator& Scratch, const FStructDeclaration& Declared, const FDebugIds& Debug) { #if DO_CHECK if (Declared.Occupancy == EMemberPresence::RequireAll) { checkf(Members.Num() == Declared.NumMembers, TEXT("'%s' requires exactly %d members but %d were added"), *Debug.Print(Declared.Id), Declared.NumMembers, Members.Num()); checkf(!Declared.Super, TEXT("Bug, dense substructs should fail DeclareStruct() check")); } // Verify members were added in declared order if (int32 Num = Members.Num()) { const bool HasSuper = IsSuper(Members[0].Schema.Type); int32 OrderIdx = 0; TConstArrayView Order = Declared.GetMemberOrder(); int32 SkipSuper = Declared.Super && HasSuper; for (FBuiltMember* It = Members.GetData() + SkipSuper, *End = Members.GetData() + Num; It != End; ++It) { FBuiltMember& Member = *It; for (; OrderIdx < Order.Num() && Order[OrderIdx] != Member.Name; ++OrderIdx) {} checkf(OrderIdx < Order.Num(), TEXT("Member '%s' in '%s' %s"), *Debug.Print(Member.Name), *Debug.Print(Declared.Id), Order.Contains(Member.Name) ? TEXT("appeared in non-declared order") : TEXT("is undeclared")); ++OrderIdx; } } #endif uint32 Num = static_cast(Members.Num()); SIZE_T NumBytes = sizeof(FBuiltStruct) + Num * sizeof(FBuiltMember); FBuiltStruct* Out = reinterpret_cast(Scratch.Allocate(NumBytes, alignof(FBuiltStruct))); Out->NumMembers = IntCastChecked(Num); FMemory::Memcpy(Out->Members, Members.GetData(), Members.NumBytes()); Members.Reset(); return Out; } ////////////////////////////////////////////////////////////////////////// FBuiltStruct* FDenseMemberBuilder::BuildHomo(const FStructDeclaration& Declaration, FMemberType Leaf, TConstArrayView Values) const { check(Declaration.NumMembers == Values.Num()); FMemberSchema Schema = {Leaf, Leaf}; const uint32 Num = static_cast(Values.Num()); SIZE_T NumBytes = sizeof(FBuiltStruct) + Num * sizeof(FBuiltMember); FBuiltStruct* Out = reinterpret_cast(Scratch.Allocate(NumBytes, alignof(FBuiltStruct))); Out->NumMembers = static_cast(Num); const FMemberId* Names = Declaration.MemberNames; for (uint32 Idx = 0; Idx < Num; ++Idx) { new (Out->Members + Idx) FBuiltMember(Names[Idx], Schema, Values[Idx]); } return Out; } ////////////////////////////////////////////////////////////////////////// template inline IntType CheckFiniteBitCast(FloatType Value) { // Todo: Decide if we should accept, reject or sanitize NaN / INF / -0 //checkf(FMath::IsFinite(Value), TEXT("Saving NaN or INF isn't supported")); return BitCast(Value); } uint64 ValueCast(float Value) { return CheckFiniteBitCast(Value); } uint64 ValueCast(double Value) { return CheckFiniteBitCast(Value); } ////////////////////////////////////////////////////////////////////////// template static FMemberSchema MakeMemberSchema(FMemberType Type, InnerIdType InnerSchema) { return {Type, Type, 0, FOptionalInnerId(InnerSchema), nullptr}; } FBuiltMember::FBuiltMember(FMemberId Name, FUnpackedLeafType Leaf, FOptionalEnumId Enum, uint64 Value) : FBuiltMember(Name, MakeMemberSchema(Leaf.Pack(), Enum), { .Leaf = Value}) {} FBuiltMember::FBuiltMember(FMemberId Name, FTypedRange Range) : FBuiltMember(Name, MoveTemp(Range.Schema), { .Range = Range.Values }) {} FBuiltMember::FBuiltMember(FMemberId Name, FStructId Id, FBuiltStruct* Value) : FBuiltMember(Name, MakeMemberSchema(DefaultStructType, FInnerId(Id)), { .Struct = Value }) {} FBuiltMember FBuiltMember::MakeSuper(FStructId Id, FBuiltStruct* Value) { return FBuiltMember(NoId, MakeMemberSchema(SuperStructType, FInnerId(Id)), { .Struct = Value }); } ////////////////////////////////////////////////////////////////////////// FTypedRange FStructRangeBuilder::BuildAndReset(FScratchAllocator& Scratch, const FStructDeclaration& Declared, const FDebugIds& Debug) { FTypedRange Out = { MakeStructRangeSchema(SizeType, Declared.Id) }; if (int64 Num = Structs.Num()) { Out.Values = FBuiltRange::Create(Scratch, Structs.Num(), sizeof(FBuiltStruct*)); FBuiltStruct** OutIt = reinterpret_cast(Out.Values->Data); for (FMemberBuilder& Struct : Structs) { *OutIt++ = Struct.BuildAndReset(Scratch, Declared, Debug); } } return Out; } ////////////////////////////////////////////////////////////////////////// FNestedRangeBuilder::~FNestedRangeBuilder() { checkf(Ranges.IsEmpty(), TEXT("Half-built range, forgot to call BuildAndReset() before destruction?")); } FTypedRange FNestedRangeBuilder::BuildAndReset(FScratchAllocator& Scratch, ERangeSizeType SizeType) { FBuiltRange* Out = nullptr; if (int64 Num = Ranges.Num()) { Out = FBuiltRange::Create(Scratch, Num, sizeof(FBuiltRange*)); FMemory::Memcpy(Out->Data, Ranges.GetData(), Ranges.NumBytes()); Ranges.Reset(); } return { MakeNestedRangeSchema(Scratch, SizeType, Schema), Out }; } } // namespace PlainProps