// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/ArrayView.h" #include "HAL/PlatformMemory.h" #include "Memory/MemoryView.h" #include "Misc/AssertionMacros.h" #include "Serialization/VarInt.h" #include "PlainPropsTypes.h" namespace PlainProps { class FLeafRangeLoadView; class FMemberLoader; class FMemberPrinter; struct FEnumSchema; struct FSchemaBatch; struct FStructSchema; ////////////////////////////////////////////////////////////////////////// // Represents a batch currently being read from class FSchemaBatchId { friend class FReadSchemaRegistry; uint16 Idx = 0; }; // @param Schemas must outlive read batch PLAINPROPS_API const FSchemaBatch* ValidateSchemas(FMemoryView Schemas); PLAINPROPS_API FSchemaBatchId MountReadSchemas(const FSchemaBatch* Schemas); PLAINPROPS_API const FSchemaBatch* UnmountReadSchemas(FSchemaBatchId Batch); PLAINPROPS_API uint32 NumStructSchemas(FSchemaBatchId Batch); PLAINPROPS_API const FStructSchema& ResolveStructSchema(FSchemaBatchId Batch, FStructSchemaId Id); PLAINPROPS_API const FEnumSchema& ResolveEnumSchema(FSchemaBatchId Batch, FEnumSchemaId Id); PLAINPROPS_API FNestedScope ResolveUntranslatedNestedScope(FSchemaBatchId Batch, FNestedScopeId Id); PLAINPROPS_API FParametricTypeView ResolveUntranslatedParametricType(FSchemaBatchId Batch, FParametricTypeId Id); ////////////////////////////////////////////////////////////////////////// class FByteReader { const uint8* It = nullptr; #if DO_CHECK const uint8* End = nullptr; #endif public: FByteReader() = default; explicit FByteReader(FMemoryView View) : FByteReader(static_cast(View.GetData()), View.GetSize()) {} FByteReader(const uint8* Data, uint64 NumBytes) : FByteReader(Data, Data + NumBytes) {} FByteReader(const uint8* InBegin, const uint8* InEnd) : It(InBegin) #if DO_CHECK , End(InEnd) #endif {} [[nodiscard]] inline const uint8* GrabBytes(uint64 NumBytes) { #if DO_CHECK check(It + NumBytes <= End); #endif const uint8* Out = It; It += NumBytes; return Out; } [[nodiscard]] inline FMemoryView GrabSlice(uint64 NumBytes) { return FMemoryView(GrabBytes(NumBytes), NumBytes); } [[nodiscard]] inline FMemoryView GrabSkippableSlice() { return GrabSlice(GrabVarIntU()); } [[nodiscard]] inline uint8 GrabByte() { return *GrabBytes(1); } template [[nodiscard]] inline T Grab() { return FPlatformMemory::ReadUnaligned(GrabBytes(sizeof(T))); } [[nodiscard]] FORCEINLINE uint64 GrabVarIntU() { #if DO_CHECK checkSlow(It < End); #endif uint32 NumBytesRead; uint64 Out = ReadVarUInt(It, NumBytesRead); It += NumBytesRead; #if DO_CHECK checkSlow(It <= End); #endif return Out; } [[nodiscard]] const uint8* Peek() const { return It; } [[nodiscard]] FMemoryView PeekSkippableSlice() const { return FByteReader(*this).GrabSkippableSlice(); } template void SkipAlignmentPadding() { for (; !IsAligned(It, alignof(T)); ++It) { check(*It == 0); } } void CheckEmpty() const { #if DO_CHECK check(It == End); #endif } void CheckNonEmpty() const { #if DO_CHECK check(It != End); #endif } void CheckSize(int64 ExpectedSize) const { #if DO_CHECK check(End - It == ExpectedSize); #endif } }; // Helper that consumes 8 bits from the byte value stream class FBitCacheReader { uint8 Bits = 0; uint8 BitIt = 0; public: [[nodiscard]] FORCEINLINE bool GrabNext(FByteReader& Bytes) { BitIt <<= 1; // Shift up til overflow if (BitIt == 0) { Bits = Bytes.GrabByte(); BitIt = 1; } return !!(Bits & BitIt); } FORCENOINLINE void Skip(uint32 Num, FByteReader& Bytes) { uint32 NumCached = 1 + FMath::CountLeadingZeros8(BitIt); if (NumCached > Num) { BitIt <<= Num; } else { uint32 NumUncached = Num - NumCached; // Grab new bytes, keep the last byte and bit within it uint32 NumBytes = Align(NumUncached + 1, 8) / 8; Bits = Bytes.GrabBytes(NumBytes)[NumBytes - 1]; BitIt = 1 << (NumUncached % 8); } } }; ////////////////////////////////////////////////////////////////////////// struct FStructSchemaHandle { FStructSchemaId Id; FSchemaBatchId Batch; const FStructSchema& Resolve() const { return ResolveStructSchema(Batch, Id); } }; struct FStructView { FStructSchemaHandle Schema; FByteReader Values; }; ////////////////////////////////////////////////////////////////////////// union FMemberValue { const uint8* Ptr; // From byte stream bool bValue; // From bit cache }; struct FLeafView { FUnpackedLeafType Leaf; FSchemaBatchId Batch; FOptionalEnumSchemaId Enum; FMemberValue Value; FORCEINLINE bool AsBool() const { return As(); } FORCEINLINE int8 AsS8() const { return As(); } FORCEINLINE uint8 AsU8() const { return As(); } FORCEINLINE int16 AsS16() const { return As(); } FORCEINLINE uint16 AsU16() const { return As(); } FORCEINLINE int32 AsS32() const { return As(); } FORCEINLINE uint32 AsU32() const { return As(); } FORCEINLINE int64 AsS64() const { return As(); } FORCEINLINE uint64 AsU64() const { return As(); } FORCEINLINE double AsDouble() const { return As(); } FORCEINLINE float AsFloat() const { return As(); } FORCEINLINE char8_t AsChar8() const { return As(); } FORCEINLINE char16_t AsChar16() const { return As(); } FORCEINLINE char32_t AsChar32() const { return As(); } template FORCEINLINE T AsUnderlyingValue() const { return As.Width}>(); } template> FORCEINLINE T As() const { check(Leaf == ExpectedLeaf); return *reinterpret_cast(Value.Ptr); } template<> FORCEINLINE bool As() const { check(Leaf == ReflectLeaf); return Value.bValue; } }; ////////////////////////////////////////////////////////////////////////// //struct FEnumSchemaFlatView //{ // const FEnumSchema& Schema; // // TOptional ToValue(FMemberId Id) const; // TOptional ToId(uint64 Value) const; // bool IsSet(FMemberId Id, uint64 Value) const; //}; // //struct FEnumSchemaFlagView //{ // const FEnumSchema& Schema; // // FEnumSchemaFlagView(const FEnumSchema& InSchema); // @pre sizeof(T) matches Schema.Width and all schema values use single bit // // TOptional ToFlags(TConstArrayView Ids) const; // TOptional> ToIds(uint64 Flags) const; // bool HasAny(FMemberId Id, uint64 Flags) const; // bool HasAll(FMemberId Id, uint64 Flags) const; //}; // //struct FEnumFlatView //{ // const FEnumSchemaFlatView* Schema; // uint64 Value; // // bool Equals(FMemberId Id) const; //}; // //struct FEnumFlagView //{ // const FEnumSchemaFlagView* Schema; // uint64 Value; //}; ////////////////////////////////////////////////////////////////////////// class FMemberReader; class FNestedRangeIterator; class FStructRangeIterator; template class TStructuralRangeView; class FLeafRangeView; using FStructRangeView = TStructuralRangeView; using FNestedRangeView = TStructuralRangeView; struct FRangeSchema { FMemberType ItemType; FSchemaBatchId Batch; // Needed to resolve inner schema FOptionalSchemaId InnermostSchema; const FMemberType* NestedItemTypes; // For nested ranges, can be out-of-bounds otherwise }; class FRangeView { public: uint64 Num() const { return NumItems;} bool IsEmpty() const { return NumItems == 0; } FMemberType GetItemType() const { return Schema.ItemType; } bool IsLeafRange() const { return Schema.ItemType.GetKind() == EMemberKind::Leaf; } bool IsStructRange() const { return Schema.ItemType.GetKind() == EMemberKind::Struct; } bool IsNestedRange() const { return Schema.ItemType.GetKind() == EMemberKind::Range; } PLAINPROPS_API FLeafRangeView AsLeaves() const; // @pre IsLeafRange() PLAINPROPS_API FStructRangeView AsStructs() const; // @pre IsStructRange() PLAINPROPS_API FNestedRangeView AsRanges() const; // @pre IsNestedRange() private: friend FMemberLoader; friend FMemberPrinter; friend FMemberReader; friend FNestedRangeIterator; FRangeSchema Schema; uint64 NumItems; FMemoryView Values; }; ////////////////////////////////////////////////////////////////////////// class FBoolRangeIterator { public: FBoolRangeIterator(const uint8* Data, uint64 Idx) : Byte(Data + Idx / 8) , Mask(1u << (Idx % 8)) {} bool operator*() const { return !!((*Byte) & Mask); } FBoolRangeIterator& operator++() { Mask <<= 1; if (Mask == 0x100) { ++Byte; Mask = 1; } return *this; } FBoolRangeIterator operator++(int) { FBoolRangeIterator Tmp(*this); operator++(); return Tmp; } bool operator!=(FBoolRangeIterator Rhs) { return Byte != Rhs.Byte || Mask != Rhs.Mask; } private: const uint8* Byte; uint32 Mask; }; class FBoolRangeView { public: FBoolRangeView(const uint8* InData, uint64 InNum) : Data(InData) , NumBits(InNum) {} inline uint64 Num() const { return NumBits;} inline bool operator[](uint64 Idx) const { return *FBoolRangeIterator(Data, Idx); } inline void Copy(void* Dst, uint64 NumBytes) const; inline FBoolRangeIterator begin() const { return FBoolRangeIterator(Data, 0); } inline FBoolRangeIterator end() const { return FBoolRangeIterator(Data, NumBits); } private: const uint8* Data; uint64 NumBits; friend inline uint64 GetNum(const FBoolRangeView& Range) { return Range.NumBits; } }; ////////////////////////////////////////////////////////////////////////// template class TRangeView { public: TRangeView(const T* InData, uint64 InNum) : Data(InData) , NumItems(InNum) {} inline uint64 Num() const { return NumItems;} inline T operator[](uint64 Idx) const { check(Idx < NumItems); return Data[Idx]; } inline void Copy(void* Dst, uint64 NumBytes) const; inline const T* begin() const { return Data; } inline const T* end() const { return Data + NumItems; } private: const T* Data; uint64 NumItems; friend inline uint64 GetNum(const TRangeView& Range) { return Range.NumItems; } friend inline const T* GetData(const TRangeView& Range) { return Range.Data; } }; // Works with FBoolRangeView, which lack GetData(), TRangeView, TArrayView, std::intializer_list and T[] template bool EqualItems(RangeTypeA&& A, RangeTypeB&& B) { if (static_cast(GetNum(A)) != static_cast(GetNum(B))) { return false; } auto BIt = std::begin(B); for (auto bA : A) { if (bA != *BIt++) { return false; } } return true; } inline void FBoolRangeView::Copy(void* Dst, uint64 NumBytes) const { check(NumBytes == NumBits); bool* bOut = static_cast(Dst); for (bool b : *this) { *bOut++ = b; } } template inline void TRangeView::Copy(void* Dst, uint64 NumBytes) const { check(NumBytes == NumItems * sizeof(T)); if (NumBytes) { FMemory::Memcpy(Dst, Data, NumBytes); } } ////////////////////////////////////////////////////////////////////////// class FLeafRangeView { public: FLeafRangeView(FUnpackedLeafType Leaf, FSchemaBatchId InBatch, FOptionalEnumSchemaId InEnum, uint64 Num, const uint8* Data) : Type(Leaf.Type) , Width(Leaf.Width) , Batch(InBatch) , Enum(InEnum) , NumItems(Num) , Values(Data) {} uint64 Num() const { return NumItems; } // These range views hide the internal representations to enable future format changes, // e.g. store zeroes or 1.0f in some compact fashion or even variable length int encodings // // They could also provide various conversion helpers template> auto As() const { check(FUnpackedLeafType(Type, Width) == ExpectedLeaf); return TRangeView(reinterpret_cast(Values), NumItems); } template<> auto As() const { return AsBools(); } FBoolRangeView AsBools() const { check(Type == ELeafType::Bool); return FBoolRangeView(Values, NumItems); } template auto AsUnderlyingValues() const { return As.Width}>(); } TRangeView< int8> AsS8s() const { return As< int8>(); } TRangeView AsU8s() const { return As(); } TRangeView< int16> AsS16s() const { return As< int16>(); } TRangeView AsU16s() const { return As(); } TRangeView< int32> AsS32s() const { return As< int32>(); } TRangeView AsU32s() const { return As(); } TRangeView< int64> AsS64s() const { return As< int64>(); } TRangeView AsU64s() const { return As(); } TRangeView AsFloats() const { return As(); } TRangeView AsDoubles() const { return As(); } TRangeView AsUtf8() const { return As(); } TRangeView AsUtf16() const { return As(); } TRangeView AsUtf32() const { return As(); } FLeafRangeLoadView AsLoadView() const; private: ELeafType Type; ELeafWidth Width; FSchemaBatchId Batch; FOptionalEnumSchemaId Enum; uint64 NumItems; const uint8* Values; }; ////////////////////////////////////////////////////////////////////////// class FNestedRangeIterator { friend FNestedRangeView; FRangeSchema Schema; FByteReader ByteIt; FBitCacheReader BitIt; public: FNestedRangeIterator(const FRangeSchema& InSchema, FMemoryView Data) : Schema(InSchema) , ByteIt(Data) {} PLAINPROPS_API FRangeView operator*() const; bool operator!=(const FNestedRangeIterator& Rhs) const { return ByteIt.Peek() != Rhs.ByteIt.Peek(); }; PLAINPROPS_API void operator++(); }; class FStructRangeIterator { friend FStructRangeView; FStructSchemaHandle Schema; FByteReader ByteIt; public: FStructRangeIterator(const FStructSchemaHandle& InSchema, FMemoryView Data) : Schema(InSchema) , ByteIt(Data) {} FStructView operator*() const { return { Schema, FByteReader(ByteIt.PeekSkippableSlice()) }; } bool operator!=(const FStructRangeIterator& Rhs) const { return ByteIt.Peek() != Rhs.ByteIt.Peek(); }; void operator++() { (void)ByteIt.GrabSkippableSlice(); } }; template class TStructuralRangeView { public: using SchemaType = decltype(IteratorType::Schema); TStructuralRangeView(uint64 N, FMemoryView D, SchemaType S) : NumItems(N), Data(D), Schema(S) {} uint64 Num() const { return NumItems; } const SchemaType& GetSchema() const { return Schema; } IteratorType begin() const { return IteratorType(Schema, Data); } IteratorType end() const { return IteratorType(Schema, FMemoryView(Data.GetDataEnd(), 0)); } private: uint64 NumItems; FMemoryView Data; SchemaType Schema; }; ////////////////////////////////////////////////////////////////////////// //class FAnyMemberView //{ //public: // EMemberKind GetKind() const { return Type.Kind; }; // // bool IsLeaf() const { return Type.Kind == EMemberKind::Leaf; } // bool IsStruct() const { return Type.Kind == EMemberKind::Struct; } // bool IsRange() const { return Type.Kind == EMemberKind::Range; } // // FStructView AsStruct() const; // @pre bool() && IsStruct() // FLeafView AsLeaf() const; // @pre bool() && IsLeaf() // FRangeView AsRange() const; // @pre bool() && IsRange() // //private: // friend class FMemberReader; // // FMemberType Type; // uint32 BatchId; // To resolve struct schema // FMemberValue Value; //}; ////////////////////////////////////////////////////////////////////////// // Iterates over struct members class FMemberReader { public: explicit FMemberReader(FStructView Struct) : FMemberReader(Struct.Schema.Resolve(), Struct.Values, Struct.Schema.Batch) {} FMemberReader(const FStructSchema& Schema, FByteReader Values, FSchemaBatchId InBatch); bool HasMore() const { return MemberIdx < NumMembers; } PLAINPROPS_API FOptionalMemberId PeekName() const; // @pre HasMore() PLAINPROPS_API FOptionalMemberId PeekNameUnchecked() const; // @pre HasMore() PLAINPROPS_API EMemberKind PeekKind() const; // @pre HasMore() PLAINPROPS_API FMemberType PeekType() const; // @pre HasMore() PLAINPROPS_API FLeafView GrabLeaf(); // @pre PeekKind() == EMemberKind::Leaf PLAINPROPS_API FRangeView GrabRange(); // @pre PeekKind() == EMemberKind::Range PLAINPROPS_API FStructView GrabStruct(); // @pre PeekKind() == EMemberKind::Struct //FAnyMemberView GrabAny(); // @pre HasMore() // Experimental // @pre Has N more contiguous members of the expected leaf type template void GrabLeaves(T* Out, uint32 N); // Experimental // @pre Has N more contiguous members of the expected leaf type template void GrabEnums(T* Out, uint32 N); protected: // for unit tests const FMemberType* Footer; const FSchemaBatchId Batch; // Needed to resolve schemas const bool IsSparse : 1; const bool HasSuper : 1; const uint32 NumMembers; const uint32 NumRangeTypes; // Number of ranges and nested ranges uint32 MemberIdx = 0; uint32 RangeTypeIdx = 0; // Types of [nested] ranges uint32 InnerSchemaIdx = 0; // Types of static structs and enums FBitCacheReader Bits; FByteReader ValueIt; #if DO_CHECK const uint32 NumInnerSchemas; // Number of static structs and enums #endif const FMemberType* GetMemberTypes() const; const FMemberType* GetRangeTypes() const; const FSchemaId* GetInnerSchemas() const; const FMemberId* GetMemberNames() const; void AdvanceToNextMember(); void AdvanceToLaterMember(uint32 Num); void SkipMissingSparseMembers(); void SkipSchema(FMemberType InnermostType); using FMemberTypeRange = TConstArrayView; FMemberTypeRange GrabRangeTypes(); FSchemaId GrabInnerSchema(); FStructSchemaId GrabStructSchema(FStructType Type); FOptionalSchemaId GrabRangeSchema(FMemberType InnermostType); inline FEnumSchemaId GrabEnumSchema() { return static_cast(GrabInnerSchema()); } inline bool GrabBit() { return Bits.GrabNext(/* in-out */ ValueIt); } inline uint64 GrabSkipLength() { return ValueIt.GrabVarIntU(); } void GrabBools(void* Out, uint32 Num); void GrabEnums(void* Out, uint32 Num, SIZE_T NumBytes); void GrabLeaves(void* Out, uint32 Num, SIZE_T NumBytes); }; template void FMemberReader::GrabLeaves(T* Out, uint32 N) { if (N) { if constexpr (std::is_same_v) { GrabBools(Out, N); } else { GrabLeaves(Out, N, sizeof(T)); } } } template void FMemberReader::GrabEnums(T* Out, uint32 N) { if (N) { GrabLeaves(Out, N, sizeof(T)); InnerSchemaIdx += N; } } ////////////////////////////////////////////////////////////////////////// // Hides the inheritance chain and iterates over super members first class FFlatMemberReader { public: FFlatMemberReader(FStructView Struct); bool HasMore() const { return It->HasMore(); } FMemberId PeekName() const { return It->PeekName().Get(); } EMemberKind PeekKind() const { return It->PeekKind(); } FType PeekOwner() const { return It->Owner; } FLeafView GrabLeaf() { return Grabbed(It->GrabLeaf()); } FRangeView GrabRange() { return Grabbed(It->GrabRange()); } FStructView GrabStruct() { return Grabbed(It->GrabStruct()); } private: struct FReader : public FMemberReader { FReader(FStructView Struct); FType Owner; }; TArray> Lineage; FReader* It = nullptr; template ViewType Grabbed(ViewType Out) { It -= (It != &Lineage[0] && !It->HasMore()); return Out; } }; ////////////////////////////////////////////////////////////////////////// // Serialized id resolver class FBatchIds : public FIdsBase { public: using FIdsBase::Resolve; using FIdsBase::AppendString; PLAINPROPS_API FBatchIds(FSchemaBatchId Batch); PLAINPROPS_API virtual uint32 NumEnums() const; PLAINPROPS_API virtual uint32 NumStructs() const; PLAINPROPS_API virtual FType Resolve(FEnumSchemaId Id) const; PLAINPROPS_API virtual FType Resolve(FStructSchemaId Id) const; PLAINPROPS_API virtual void AppendString(FUtf8Builder& Out, FEnumSchemaId Enum) const; PLAINPROPS_API virtual void AppendString(FUtf8Builder& Out, FStructSchemaId Struct) const; const FSchemaBatch& GetSchemas() const { return Schemas; } FSchemaBatchId GetBatchId() const { return BatchId; } protected: const FSchemaBatch& Schemas; FSchemaBatchId BatchId; }; // Serialized id resolver for ESchemaFormat::InMemoryNames class FMemoryBatchIds final : public FBatchIds { public: using FBatchIds::Resolve; using FBatchIds::AppendString; FMemoryBatchIds(FSchemaBatchId Batch, const FIdsBase& InNames) : FBatchIds(Batch), Names(InNames) {} virtual uint32 NumNames() const override { return Names.NumNames(); } virtual uint32 NumNestedScopes() const override { return Names.NumNestedScopes(); } virtual uint32 NumParametricTypes() const override { return Names.NumParametricTypes(); } virtual FNestedScope Resolve(FNestedScopeId Id) const override { return Names.Resolve(Id); } virtual FParametricTypeView Resolve(FParametricTypeId Id) const override { return Names.Resolve(Id); } virtual void AppendString(FUtf8Builder& Out, FNameId Name) const override { Names.AppendString(Out, Name); } private: const FIdsBase& Names; }; // Serialized id resolver for ESchemaFormat::StableNames class FStableBatchIds : public FBatchIds { public: using FBatchIds::FBatchIds; using FBatchIds::Resolve; PLAINPROPS_API virtual uint32 NumNestedScopes() const override final; PLAINPROPS_API virtual uint32 NumParametricTypes() const override final; PLAINPROPS_API virtual FNestedScope Resolve(FNestedScopeId Id) const override final; PLAINPROPS_API virtual FParametricTypeView Resolve(FParametricTypeId Id) const override final; }; } // namespace PlainProps