Files
UnrealEngine/Engine/Plugins/Experimental/PlainProps/Source/Public/PlainPropsBind.h
2025-05-18 13:04:45 +08:00

1265 lines
40 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/ArrayView.h"
#include "Containers/Set.h"
#include "Containers/StringView.h"
#include "Memory/MemoryFwd.h"
#include "Memory/MemoryView.h"
#include "PlainPropsCtti.h"
#include "PlainPropsDeclare.h"
#include "PlainPropsTypename.h"
#include "PlainPropsRead.h"
#include "PlainPropsSpecify.h"
#include "PlainPropsTypes.h"
#include <tuple>
namespace PlainProps
{
struct FBindContext;
struct FBuiltRange;
struct FBuiltStruct;
struct FCustomBindingHandle;
struct FDiffContext;
class FIdIndexerBase;
struct FLoadBatch;
class FMemberBuilder;
class FRangeLoader;
struct FSchemaBatch;
class FScratchAllocator;
struct FStructLoadView;
class FRangeBinding;
struct FSaveContext;
struct FTypedRange;
struct IItemRangeBinding;
template<class T> class TIdIndexer;
////////////////////////////////////////////////////////////////////////////////////////////////
inline FAnsiStringView ToAnsiView(std::string_view Str) { return FAnsiStringView(Str.data(), Str.length()); }
////////////////////////////////////////////////////////////////////////////////////////////////
enum class ELeafBindType : uint8 { Bool, IntS, IntU, Float, Hex, Enum, Unicode, BitfieldBool };
inline static constexpr ELeafBindType ToLeafBindType(ELeafType Type)
{
return static_cast<ELeafBindType>(static_cast<uint8>(Type));
}
inline static constexpr ELeafType ToLeafType(ELeafBindType Type)
{
return Type == ELeafBindType::BitfieldBool ? ELeafType::Bool : static_cast<ELeafType>(static_cast<uint8>(Type));
}
struct FBasicLeafBindType
{
EMemberKind _ : 2;
ELeafBindType __ : 3;
ELeafWidth Width : 2;
uint8 _Pad : 1;
};
struct FBitfieldBoolBindType
{
EMemberKind _ : 2;
ELeafBindType __ : 3;
uint8 Idx : 3;
};
union FLeafBindType
{
constexpr explicit FLeafBindType(ELeafBindType BasicType, ELeafWidth Width) : Basic({EMemberKind::Leaf, BasicType, Width}) {}
constexpr explicit FLeafBindType(FUnpackedLeafType In) : Basic({EMemberKind::Leaf, ToLeafBindType(In.Type), In.Width}) {}
constexpr explicit FLeafBindType(FLeafType In) : FLeafBindType(FUnpackedLeafType(In)) {}
constexpr explicit FLeafBindType(FBitfieldBoolBindType In) : Bitfield({EMemberKind::Leaf, ELeafBindType::BitfieldBool, In.Idx}) {}
constexpr explicit FLeafBindType(uint8 BitfieldIdx) : Bitfield({EMemberKind::Leaf, ELeafBindType::BitfieldBool, BitfieldIdx}) {}
struct
{
EMemberKind _ : 2;
ELeafBindType Type : 3;
uint8 _Pad : 3;
} Bind;
FBasicLeafBindType Basic;
FBitfieldBoolBindType Bitfield;
};
inline static constexpr FLeafType ToLeafType(FLeafBindType Leaf)
{
if (Leaf.Bind.Type == ELeafBindType::BitfieldBool)
{
return { EMemberKind::Leaf, ELeafWidth::B8, ELeafType::Bool };
}
return { EMemberKind::Leaf, Leaf.Basic.Width, ToLeafType(Leaf.Bind.Type) };
}
struct FRangeBindType : FRangeType {};
struct FStructBindType : FStructType {};
union FMemberBindType
{
constexpr explicit FMemberBindType(FLeafType In) : Leaf(In) {}
constexpr explicit FMemberBindType(FUnpackedLeafType In) : Leaf(In) {}
constexpr explicit FMemberBindType(FLeafBindType In) : Leaf(In) {}
constexpr explicit FMemberBindType(FBitfieldBoolBindType In) : Leaf(In) {}
constexpr explicit FMemberBindType(FRangeType In) : Range(In) {}
constexpr explicit FMemberBindType(ERangeSizeType MaxSize) : Range({EMemberKind::Range, MaxSize}) {}
constexpr explicit FMemberBindType(FStructType In) : Struct(In) {}
bool IsLeaf() const { return Kind == EMemberKind::Leaf; }
bool IsRange() const { return Kind == EMemberKind::Range; }
bool IsStruct() const { return Kind == EMemberKind::Struct; }
EMemberKind GetKind() const { return Kind; }
FLeafBindType AsLeaf() const { checkSlow(IsLeaf()); return Leaf; }
FRangeBindType AsRange() const { checkSlow(IsRange()); return Range; }
FStructBindType AsStruct() const { checkSlow(IsStruct()); return Struct; }
uint8 AsByte() const { return BitCast<uint8>(*this); }
friend inline bool operator==(FMemberBindType A, FMemberBindType B) { return A.AsByte() == B.AsByte(); }
friend inline uint32 GetTypeHash(FMemberBindType In) { return In.AsByte(); }
private:
EMemberKind Kind : 2;
FLeafBindType Leaf;
FRangeBindType Range;
FStructBindType Struct;
};
static_assert(sizeof(FMemberBindType) == 1);
////////////////////////////////////////////////////////////////////////////////////////////////
// Members are loaded in saved FStructSchema order, not current offset order unless upgrade layer reorders
struct alignas(/*FRangeBinding*/ 8) FSchemaBinding
{
FDeclId DeclId;
uint16 NumMembers;
uint16 NumInnerSchemas;
uint16 NumInnerRanges;
FMemberBindType Members[0];
const FMemberBindType* GetInnerRangeTypes() const { return Members + NumMembers; }
const uint32* GetOffsets() const { return AlignPtr<uint32>(GetInnerRangeTypes() + NumInnerRanges); }
const FInnerId* GetInnerSchemas() const { return AlignPtr<FInnerId>(GetOffsets() + NumMembers); }
const FRangeBinding* GetRangeBindings() const { return AlignPtr<FRangeBinding>(GetInnerSchemas() + NumInnerSchemas); }
uint32 CalculateSize() const;
bool HasSuper() const { return NumInnerSchemas > 0 && Members[0].IsStruct() && Members[0].AsStruct().IsSuper; }
};
////////////////////////////////////////////////////////////////////////////////////////////////
struct FUnpackedLeafBindType
{
ELeafBindType Type;
union
{
ELeafWidth Width;
uint8 BitfieldIdx;
};
constexpr FUnpackedLeafBindType(FLeafBindType In)
: Type(In.Bind.Type)
{
if (Type != ELeafBindType::BitfieldBool)
{
Width = In.Basic.Width;
}
else
{
BitfieldIdx = In.Bitfield.Idx;
}
}
FMemberBindType Pack() const
{
return FMemberBindType(Type == ELeafBindType::BitfieldBool ? FLeafBindType(BitfieldIdx) : FLeafBindType(Type, Width));
}
};
inline static constexpr FUnpackedLeafType ToUnpackedLeafType(FUnpackedLeafBindType Leaf)
{
if (Leaf.Type == ELeafBindType::BitfieldBool)
{
return { ELeafType::Bool, ELeafWidth::B8 };
}
return { ToLeafType(Leaf.Type), Leaf.Width };
}
// @pre Type != ELeafBindType::BitfieldBool
inline FUnpackedLeafType UnpackNonBitfield(FLeafBindType Packed)
{
FUnpackedLeafBindType Unpacked(Packed);
return { ToLeafType(Unpacked.Type), Unpacked.Width };
}
struct FLeafMemberBinding
{
FUnpackedLeafBindType Leaf;
FOptionalEnumId Enum;
SIZE_T Offset;
};
struct FRangeMemberBinding
{
const FMemberBindType* InnerTypes;
const FRangeBinding* RangeBindings;
uint16 NumRanges; // At least 1, >1 for nested ranges
FOptionalInnerId InnermostSchema;
SIZE_T Offset;
};
struct FStructMemberBinding
{
FStructType Type;
FBindId Id;
SIZE_T Offset;
};
PLAINPROPS_API FRangeMemberBinding GetInnerRange(FRangeMemberBinding In); // @pre In.NumRanges > 1
////////////////////////////////////////////////////////////////////////////////////////////////
struct FBothStructId
{
FBindId BindId;
FDeclId DeclId;
bool IsLowered() const { return BindId != DeclId; }
};
// FStructId statically known to share FBindId and FDeclId, i.e. not lowered
struct FDualStructId : FStructId
{
explicit FDualStructId(FStructId In) : FStructId(In) {}
operator FBindId() const { return {Idx}; }
operator FDeclId() const { return {Idx}; }
};
////////////////////////////////////////////////////////////////////////////////////////////////
// Helps raise lowered FDeclId by seeing which FBindId a particular named member has
struct FInnerStruct
{
FMemberId Name; // Of outer Range or Struct
FBindId Id;
};
enum ECustomLoadMethod { Construct, Assign };
/// Load/save a struct with custom code to handle:
/// * reference types
/// * private members
/// * non-default constructible types
/// * custom delta semantics
/// * other runtime representations than struct/class, e.g. serialize database
/// * optimizations of very common structs
struct ICustomBinding
{
virtual ~ICustomBinding() {}
virtual void SaveCustom(FMemberBuilder& Dst, const void* Src, const void* Default, const FSaveContext& Ctx) = 0;
virtual void LoadCustom(void* Dst, FStructLoadView Src, ECustomLoadMethod Method) const = 0;
virtual bool DiffCustom(const void* StructA, const void* StructB, const FBindContext& Ctx) const = 0;
// Overload to track the first diffing member, see FDiffPath
PLAINPROPS_API virtual bool DiffCustom(const void* StructA, const void* StructB, FDiffContext& Ctx) const;
};
struct FInnersHandle
{
uint32 Num = 0;
uint32 Idx = 0;
};
struct FIdWindow
{
uint32 Min = 0;
uint32 Num = 0;
};
// Slightly optimized map from FStructId to ICustomBinding/FStructDeclaration/FInnersHandle
struct FCustomBindingMap
{
UE_NONCOPYABLE(FCustomBindingMap);
explicit FCustomBindingMap(FDebugIds Dbg);
~FCustomBindingMap();
void Bind(FBindId Id, ICustomBinding& Binding, FStructDeclarationPtr&& Declaration, FInnersHandle LoweredInners);
FCustomBindingHandle Find(FBindId Id) const;
void Drop(FBindId Id);
FIdWindow Window;
TSet<FBindId> Keys;
ICustomBinding** Values = nullptr;
uint32 MaxValues = 0;
TArray<FStructDeclarationPtr> Declarations; // Consider switch to TSparseArray or TMap
FDebugIds Debug;
};
class FCustomBindings
{
public:
// @param Binding must outlive this or call DropStruct()
PLAINPROPS_API void BindStruct(FBindId Id, ICustomBinding& Binding, FStructDeclarationPtr&& Declaration, TConstArrayView<FInnerStruct> LoweredInners);
PLAINPROPS_API void BindStruct(FBindId Id, ICustomBinding& Binding, const FStructSpec& DeclSpec, TConstArrayView<FInnerStruct> LoweredInners);
PLAINPROPS_API const ICustomBinding* FindStruct(FBindId Id) const;
PLAINPROPS_API const ICustomBinding* FindStruct(FBindId Id, TConstArrayView<FInnerStruct>& OutLoweredInners) const;
PLAINPROPS_API const ICustomBinding* FindStruct(FBindId Id, const FStructDeclaration*& OutDeclaration) const;
PLAINPROPS_API ICustomBinding* FindStructToSave(FBindId Id, const FStructDeclaration*& OutDeclaration) const;
PLAINPROPS_API const FStructDeclaration* FindDeclaration(FBindId Id) const;
PLAINPROPS_API void DropStruct(FBindId Id);
virtual FCustomBindingHandle Find(FBindId Id) const = 0;
protected:
PLAINPROPS_API FCustomBindings(TArray<FInnerStruct>& Inners, FDebugIds Dbg);
PLAINPROPS_API FCustomBindings(const FCustomBindings* Under);
PLAINPROPS_API ~FCustomBindings();
FCustomBindingMap Map;
TArray<FInnerStruct>& BottomInners;
};
class FCustomBindingsBottom final : public FCustomBindings
{
mutable TArray<FInnerStruct> AllInners;
PLAINPROPS_API virtual FCustomBindingHandle Find(FBindId Id) const override;
public:
explicit FCustomBindingsBottom(FDebugIds Dbg) : FCustomBindings(/* unconstructed */ AllInners, Dbg) {}
};
class FCustomBindingsOverlay final : public FCustomBindings
{
const FCustomBindings& Underlay;
PLAINPROPS_API virtual FCustomBindingHandle Find(FBindId Id) const override;
public:
explicit FCustomBindingsOverlay(const FCustomBindings& Under) : FCustomBindings(&Under), Underlay(Under) {}
};
template<typename T>
struct TCustomBind { using Type = void; };
template<class T>
struct TCustomDeltaBind { using Type = void; };
template<typename T>
using CustomBind = typename TCustomBind<T>::Type;
template<typename T>
using CustomDeltaBind = typename TCustomDeltaBind<T>::Type;
////////////////////////////////////////////////////////////////////////////////////////////////
template<typename T>
struct TOccupancyOf
{
static constexpr EMemberPresence Value = EMemberPresence::AllowSparse;
};
struct FRequireAll
{
static constexpr EMemberPresence Value = EMemberPresence::RequireAll;
};
////////////////////////////////////////////////////////////////////////////////////////////////
// Request from loading layer to IItemRangeBinding to allocate and construct items
class FConstructionRequest
{
friend FRangeLoader;
void* const Range = nullptr;
const uint64 Num = 0;
uint64 Index = 0;
FConstructionRequest(void* InRange, uint64 InNum) : Range(InRange), Num(InNum) {}
public:
template<typename T>
T& GetRange() const { return *reinterpret_cast<T*>(Range); }
uint64 NumTotal() const { return Num; }
uint64 NumMore() const { return Num - Index; }
uint64 GetIndex() const { return Index; }
bool IsFirstCall() const { return Index == 0;}
bool IsFinalCall() const { return Index == Num;}
};
// Response from IItemRangeBinding with contiguous items ready to be loaded
//
// Non-contiguous containers provide one items one by one or use
// FLoadRangeContext::Scratch or a temp allocation to avoid that
class FConstructedItems
{
public:
// E.g. allow hash table to rehash after all items are loaded
void RequestFinalCall() { bNeedFinalize = true; }
void SetUnconstructed() { bUnconstructed = true; }
// Non-contiguous items must be set individually
template<typename ItemType>
void Set(ItemType* Items, uint64 NumItems)
{
Set(Items, NumItems, sizeof(ItemType));
}
void Set(void* Items, uint64 NumItems, uint32 ItemSize)
{
check(NumItems == 0 || Items != Data);
Data = reinterpret_cast<uint8*>(Items);
Num = NumItems;
Size = ItemSize;
}
void UpdateNum(uint64 NumItems)
{
check(Data);
Num = NumItems;
}
template<typename ItemType>
TArrayView<ItemType> Get()
{
return MakeArrayView(reinterpret_cast<ItemType*>(Data), Num);
}
inline uint8* GetData() { return Data; }
inline uint8* GetDataEnd() { return Data + NumBytes(); }
private:
friend FRangeLoader;
uint8* Data = nullptr;
uint64 Num = 0;
uint32 Size = 0;
bool bNeedFinalize = false;
bool bUnconstructed = false;
uint64 NumBytes() const { return Num * Size; }
};
struct FLoadRangeContext
{
FConstructionRequest Request; // Request to construct items to be loaded
FConstructedItems Items; // Response from IItemRangeBinding
uint64 Scratch[64]; // Scratch memory for IItemRangeBinding
};
// todo: switch to class
struct FGetItemsRequest
{
template<typename T>
const T& GetRange() const { return *reinterpret_cast<const T*>(Range); }
bool IsFirstCall() const { return NumRead == 0;}
const void* Range = nullptr;
uint64 NumRead = 0;
};
struct FExistingItemSlice
{
const void* Data = nullptr;
uint64 Num = 0;
explicit operator bool() const { return !!Num; }
const uint8* At(uint64 Idx, uint32 Stride) const
{
check(Idx < Num);
return reinterpret_cast<const uint8*>(Data) + Idx * Stride;
}
};
struct FExistingItems
{
uint64 NumTotal = 0;
uint32 Stride = 0;
FExistingItemSlice Slice;
void SetAll(const void* Items, uint64 Num, uint32 InStride)
{
NumTotal = Num;
Stride = InStride;
Slice = { Items, Num };
}
template<typename ItemType>
void SetAll(const ItemType* Items, uint64 Num)
{
SetAll(Items, Num, sizeof(ItemType));
}
};
struct FSaveRangeContext
{
FGetItemsRequest Request; // Request to get items to be saved
FExistingItems Items; // Response from IRangeBinding
uint64 Scratch[8]; // Scratch memory for IRangeBinding
};
struct alignas(16) IItemRangeBinding
{
virtual void ReadItems(FSaveRangeContext& Ctx) const = 0;
virtual void MakeItems(FLoadRangeContext& Ctx) const = 0;
// Try living without virtual destructor and trivially destructible members
explicit IItemRangeBinding(FConcreteTypenameId RangeBindName) : BindName(RangeBindName) {}
const FConcreteTypenameId BindName;
};
////////////////////////////////////////////////////////////////////////////////////////////////
// Possible save opt: Use paged linear allocator that only allocates on page exhaustion
class FLeafRangeAllocator
{
FScratchAllocator& Scratch;
FBuiltRange* Range = nullptr;
const FUnpackedLeafType Expected;
PLAINPROPS_API void* Allocate(uint64 Num, SIZE_T LeafSize);
public:
FLeafRangeAllocator(FScratchAllocator& InScratch, FUnpackedLeafType InExpected) : Scratch(InScratch), Expected(InExpected) {}
template<LeafType T, typename SizeType>
T* AllocateRange(SizeType Num)
{
check(ReflectArithmetic<T> == Expected);
return Num ? static_cast<T*>(Allocate(static_cast<uint64>(Num), sizeof(T))) : nullptr;
}
template<typename SizeType>
void* AllocateNonEmptyRange(SizeType Num, ELeafWidth Width)
{
check(Num > 0);
check(Width == Expected.Width);
return Allocate(static_cast<uint64>(Num), SizeOf(Width));
}
FBuiltRange* GetAllocatedRange() { return Range; }
};
// Specialized binding for transcoding leaf ranges
struct alignas(16) ILeafRangeBinding
{
virtual void SaveLeaves(const void* Range, FLeafRangeAllocator& Out) const = 0;
virtual void LoadLeaves(void* Range, FLeafRangeLoadView Leaves) const = 0;
virtual bool DiffLeaves(const void* RangeA, const void* RangeB) const = 0;
explicit ILeafRangeBinding(FConcreteTypenameId RangeBindName) : BindName(RangeBindName) {}
const FConcreteTypenameId BindName;
template<typename SizeType>
static bool Diff(SizeType NumA, SizeType NumB, const void* A, const void* B, SIZE_T ItemSize)
{
return NumA != NumB || (NumA > 0 && FMemory::Memcmp(A, B, static_cast<uint64>(NumA) * ItemSize) != 0);
}
};
////////////////////////////////////////////////////////////////////////////////////////////////
class FRangeBinding
{
static constexpr uint64 SizeMask = 0b1111;
static constexpr uint64 LeafMask = uint64(1) << FPlatformMemory::KernelAddressBit;
static constexpr uint64 BindMask = ~(SizeMask | LeafMask);
uint64 Handle;
public:
PLAINPROPS_API FRangeBinding(const IItemRangeBinding& Binding, ERangeSizeType SizeType);
PLAINPROPS_API FRangeBinding(const ILeafRangeBinding& Binding, ERangeSizeType SizeType);
bool IsLeafBinding() const { return !!(LeafMask & Handle); }
const IItemRangeBinding& AsItemBinding() const { check(!IsLeafBinding()); return *reinterpret_cast<IItemRangeBinding*>(Handle & BindMask); }
const ILeafRangeBinding& AsLeafBinding() const { check( IsLeafBinding()); return *reinterpret_cast<ILeafRangeBinding*>(Handle & BindMask); }
ERangeSizeType GetSizeType() const { return static_cast<ERangeSizeType>(Handle & SizeMask); }
FConcreteTypenameId GetBindName() const { return IsLeafBinding() ? AsLeafBinding().BindName : AsItemBinding().BindName; }
inline bool operator==(FRangeBinding O) const { return Handle == O.Handle; }
};
template<typename T>
struct TRangeBind{ using Type = void; };
template<typename T>
using RangeBind = typename TRangeBind<T>::Type;
////////////////////////////////////////////////////////////////////////////////////////////////
struct FBothType
{
FType BindType;
FType DeclType;
bool IsLowered() const { return BindType != DeclType; }
};
////////////////////////////////////////////////////////////////////////////////////////////////
struct FMemberBinding
{
explicit FMemberBinding(uint64 InOffset = 0)
: Offset(InOffset)
, InnermostType(FLeafBindType(ELeafBindType::Bool, ELeafWidth::B8))
{}
uint64 Offset;
FMemberBindType InnermostType; // Always Leaf or Struct
FOptionalInnerId InnermostSchema; // Enum or struct schema
TConstArrayView<FRangeBinding> RangeBindings; // Non-empty -> Range
// @pre InnermostSchema isn't type-erased / lowered
PLAINPROPS_API FBothType IndexParameterName(FIdIndexerBase& Ids) const;
};
class FSchemaBindings
{
public:
UE_NONCOPYABLE(FSchemaBindings);
PLAINPROPS_API explicit FSchemaBindings(FDebugIds In);
PLAINPROPS_API ~FSchemaBindings();
PLAINPROPS_API void BindStruct(FBindId Id, TConstArrayView<FMemberBinding> Members, FStructSpec Decl);
PLAINPROPS_API void BindStruct(FBindId Id, TConstArrayView<FMemberBinding> Members, FStructDeclarationPtr&& Decl);
PLAINPROPS_API const FSchemaBinding* FindStruct(FBindId Id) const;
PLAINPROPS_API const FSchemaBinding* FindStruct(FBindId Id, const FStructDeclaration*& OutDeclaration) const;
PLAINPROPS_API const FSchemaBinding& GetStruct(FBindId Id) const;
PLAINPROPS_API const FSchemaBinding& GetStruct(FBindId Id, const FStructDeclaration*& OutDecl) const;
PLAINPROPS_API void DropStruct(FBindId Id);
PLAINPROPS_API FDeclId Lower(FBindId Id) const;
PLAINPROPS_API const FStructDeclaration& GetDeclaration(FBindId Id) const;
PLAINPROPS_API const FStructDeclaration* FindDeclaration(FBindId Id) const;
FDebugIds GetDebug() const { return Debug; }
private:
TArray<FStructDeclarationPtr> Declarations;
TArray<TUniquePtr<FSchemaBinding>> Bindings;
FDebugIds Debug;
FSchemaBinding* Bind(FBindId Id, TConstArrayView<FMemberBinding> Members, FDeclId DeclId);
FStructDeclarationPtr& At(FDeclId Id);
};
////////////////////////////////////////////////////////////////////////////////////////////////
// Lookup default instances when delta-saving struct ranges
struct IDefaultStructs
{
virtual const void* Get(FBindId Id) = 0;
};
////////////////////////////////////////////////////////////////////////////////////////////////
struct FBindContext
{
const FSchemaBindings& Schemas;
FCustomBindings& Customs;
PLAINPROPS_API const FStructDeclaration& GetDeclaration(FBindId Id) const;
};
struct FBindDeclarations final : IDeclarations
{
FBindDeclarations(const FEnumDeclarations& E, const FCustomBindings& C, const FSchemaBindings& S) : Enums(E), Customs(C), Schemas(S) {}
const FEnumDeclarations& Enums;
const FCustomBindings& Customs;
const FSchemaBindings& Schemas;
PLAINPROPS_API virtual const FEnumDeclaration* Find(FEnumId Id) const override;
PLAINPROPS_API virtual const FStructDeclaration* Find(FStructId Id) const override;
PLAINPROPS_API virtual FDeclId Lower(FBindId Id) const override;
};
////////////////////////////////////////////////////////////////////////////////////////////////
template<class Ids, class Typename>
FScopeId IndexNamespaceId() requires (Typename::Namespace.size() > 0)
{
// Opt: Make cached GetNamespaceId(), either via new namespace CTTI types (maybe PP_REFLECT_NAMESPACE)
// or some compile time string template parameters, perhaps a variadic template taking any number of chars
return Ids::IndexScope(ToAnsiView(Typename::Namespace));
}
template<class Ids, class Typename>
FScopeId IndexNamespaceId()
{
return NoId;
}
template<ETypename Kind, class Typename>
constexpr std::string_view SelectStructName()
{
if constexpr (Kind == ETypename::Bind && ExplicitBindName<Typename>)
{
return Typename::BindName;
}
else
{
return Typename::DeclName;
}
}
template<class Ids, ETypename Kind, class Typename>
FType IndexStructName()
{
FType BaseName = { IndexNamespaceId<Ids, Typename>(),
FTypenameId(Ids::IndexTypename(ToAnsiView(SelectStructName<Kind, Typename>()))) };
if constexpr (ParametricName<Typename>)
{
return IndexParametricType<Ids, Kind>(BaseName, (typename Typename::Parameters*)nullptr);
}
else
{
return BaseName;
}
}
template<class Ids, class Typename>
FBothStructId IndexStructBothId(FType DeclName = IndexStructName<Ids, ETypename::Decl, Typename>())
{
FBothStructId Out;
Out.DeclId = FDeclId(Ids::IndexStruct(DeclName));
Out.BindId = UpCast(Out.DeclId);
if constexpr (ExplicitBindName<Typename> || ParametricName<Typename>)
{
FType BindName = IndexStructName<Ids, ETypename::Bind, Typename>();
Out.BindId = BindName != DeclName ? FBindId(Ids::IndexStruct(BindName)) : UpCast(Out.DeclId);
}
return Out;
}
template<class Ids, class Typename>
FDualStructId IndexStructDualId(FType Name = IndexStructName<Ids, ETypename::Decl, Typename>()) requires (!ExplicitBindName<Typename>)
{
return FDualStructId(Ids::IndexStruct(Name));
}
// Cached by function static
template<class Ids, typename Struct>
FDeclId GetStructDeclId()
{
static FDeclId Id = FDeclId(Ids::IndexStruct(IndexStructName<Ids, ETypename::Decl, TTypename<Struct>>()));
return Id;
}
// Cached by function static
template<class Ids, typename Struct>
FBindId GetStructBindId()
{
static FBindId Id = FBindId(Ids::IndexStruct(IndexStructName<Ids, ETypename::Bind, TTypename<Struct>>()));
return Id;
}
// Cached by function static
template<class Ids, typename Struct>
FBothStructId GetStructBothId()
{
static FBothStructId Id = IndexStructBothId<Ids, TTypename<Struct>>();
return Id;
}
// Cached by function static
template<class Ids, typename Struct>
FDualStructId GetStructDualId()
{
static FDualStructId Id = IndexStructDualId<Ids, TTypename<Struct>>();
return Id;
}
template<class Ids, typename Ctti>
FType IndexCttiName()
{
FTypenameId Name{Ids::IndexTypename(Ctti::Name)};
FScopeId Namespace = NoId;
if constexpr (Ctti::Namespace[0] != '\0')
{
Namespace = Ids::IndexScope(ToAnsiView(Ctti::Namespace));
}
return { Namespace, Name };
}
// Cached by function static
template<class Ids, Enumeration Enum>
FEnumId GetEnumId()
{
static FEnumId Id = Ids::IndexEnum(IndexCttiName<Ids, CttiOf<Enum>>());
return Id;
}
template<class Ids, Arithmetic T>
FType IndexArithmeticName()
{
static constexpr FUnpackedLeafType Leaf = ReflectArithmetic<T>;
return { NoId, FTypenameId(Ids::IndexTypename(ToAnsiView(ArithmeticName<Leaf.Type, Leaf.Width>))) };
}
template<class Ids, ETypename, Arithmetic Leaf>
FType IndexParameterName()
{
return IndexArithmeticName<Ids, Leaf>();
}
template<class Ids, ETypename, Enumeration Enum>
FType IndexParameterName()
{
return GetEnumId<Ids, Enum>();
}
template<class Ids, ETypename Kind, typename T>
FType IndexParameterName()
{
using RangeBinding = RangeBind<T>;
if constexpr (std::is_void_v<RangeBinding>)
{
return IndexStructName<Ids, Kind, TTypename<T>>();
}
else
{
FType ItemParam = IndexParameterName<Ids, Kind, typename RangeBinding::ItemType>();
FType SizeParam = Ids::GetIndexer().MakeRangeParameter(RangeSizeOf(typename RangeBinding::SizeType{}));
if constexpr (Kind == ETypename::Decl)
{
// Type-erase range type
return Ids::GetIndexer().MakeAnonymousParametricType({ItemParam, SizeParam});
}
else
{
using Typename = TTypename<T>;
FType RangeBindName = { IndexNamespaceId<Ids, Typename>(), FTypenameId(Ids::IndexTypename(ToAnsiView(RangeBinding::BindName))) };
return Ids::GetIndexer().MakeParametricType(RangeBindName, {ItemParam, SizeParam});
}
}
}
template<class Ids, ETypename Kind, typename... Ts>
FType IndexParametricType(FType TemplatedType, const std::tuple<Ts...>*)
{
FType Parameters[] = { (IndexParameterName<Ids, Kind, Ts>())... };
return Ids::GetIndexer().MakeParametricType(TemplatedType, Parameters);
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Works around C++'s lack of templated nullary constructors
template<class Ids>
struct TInit {};
////////////////////////////////////////////////////////////////////////////////////////////////
using InnerStructArray = TArray<FInnerStruct, TInlineAllocator<8>>;
struct FCustomInit
{
UE_NONCOPYABLE(FCustomInit);
FCustomInit() = default;
InnerStructArray LoweredInners;
// Only needed for inner structs that might get type-erased/lowered
void RegisterInnerStruct(FBothStructId InnerId, TConstArrayView<FMemberId> Names)
{
if (InnerId.IsLowered())
{
for (FMemberId Name : Names)
{
LoweredInners.Add({Name, InnerId.BindId});
}
}
}
};
// Helps custom binding constructors create ids and register types that might get type-erased
template<class Ids>
struct TCustomInit : FCustomInit, TInit<Ids>
{};
// Constructor API for static custom bindings to create ids, register type-erased inner structs
// and specify member types, inheritance and version
template<class Ids, uint32 N>
struct TCustomSpecifier : TCustomInit<Ids>
{
FOptionalStructId Super;
uint16 Version = 0;
FMemberSpec Members[N];
template <typename... Ts>
void SetMembers(Ts&&... Specs) requires (N == sizeof...(Specs))
{
FMemberSpec* It = &Members[0];
((*It++ = FMemberSpec(Forward<Ts>(Specs))), ...);
}
void FillMembers(FMemberSpec Same)
{
for (FMemberSpec& Member : Members)
{
Member = Same;
}
}
};
// Workaround for clang requiring FStructLoadView definition, make it dependent on any other type
template<typename>
struct TDependent { using StructLoadView = FStructLoadView; };
// Scoped custom binding for native type
template<class T, class Runtime, typename CustomBinding = CustomBind<T>>
struct TScopedStructBinding : FBothStructId, CustomBinding
{
using Ids = typename Runtime::Ids;
using Typename = TCustomTypename<CustomBinding>::Type;
static constexpr uint32 N = sizeof(CustomBinding::MemberIds) / sizeof(CustomBinding::MemberIds[0]);
explicit TScopedStructBinding(FType DeclName = IndexStructName<Ids, ETypename::Decl, Typename>())
: TScopedStructBinding(IndexStructBothId<Ids, Typename>(DeclName))
{}
explicit TScopedStructBinding(FDualStructId Dual)
: TScopedStructBinding(FBothStructId{Dual, Dual})
{}
explicit TScopedStructBinding(FBothStructId Both)
: TScopedStructBinding(Both, TCustomSpecifier<Ids, N>())
{}
TScopedStructBinding(FBothStructId Both, TCustomSpecifier<Ids, N>&& Init)
: FBothStructId(Both)
, CustomBinding(/* out */ Init)
{
FStructSpec Decl = { DeclId, /* todo */ NoId, Init.Version, TOccupancyOf<T>::Value, CustomBinding::MemberIds, Init.Members };
Runtime::GetCustoms().BindStruct(BindId, *this, Decl, Init.LoweredInners);
}
~TScopedStructBinding()
{
Runtime::GetCustoms().DropStruct(BindId);
}
virtual void SaveCustom(FMemberBuilder& Dst, const void* Src, const void* Default, const FSaveContext& Ctx) override
{
CustomBinding::Save(Dst, *static_cast<const T*>(Src), static_cast<const T*>(Default), Ctx);
}
virtual void LoadCustom(void* Dst, TDependent<Ids>::StructLoadView Src, ECustomLoadMethod Method) const override
{
CustomBinding::Load(*static_cast<T*>(Dst), Src, Method);
}
virtual bool DiffCustom(const void* A, const void* B, const FBindContext& Ctx) const override
{
return CustomBinding::Diff(*static_cast<const T*>(A), *static_cast<const T*>(B), Ctx);
}
virtual bool DiffCustom(const void* A, const void* B, FDiffContext& Ctx) const override
{
return CustomBinding::Diff(*static_cast<const T*>(A), *static_cast<const T*>(B), Ctx);
}
};
// Specialization for schemabound structs
template<class T, class Runtime>
struct TScopedStructBinding<T, Runtime, void> : FBothStructId
{
using Ids = typename Runtime::Ids;
using Typename = TTypename<T>;
TScopedStructBinding(FBothStructId Both = IndexStructBothId<Ids, Typename>() )
: FBothStructId(Both)
{
BindNativeStruct<CttiOf<T>, Runtime>(/* out */ Runtime::GetSchemas(), Both, TOccupancyOf<T>::Value);
}
~TScopedStructBinding()
{
Runtime::GetSchemas().DropStruct(BindId);
}
};
template<class Struct, typename CustomBinding, class Runtime>
FBothStructId BindCustomStructOnce()
{
static TScopedStructBinding<Struct, Runtime, CustomBinding> Instance;
return Instance;
}
inline constexpr FMemberBindType DefaultStructBindType = FMemberBindType(FStructType{EMemberKind::Struct, /* IsDynamic */ 0, /* IsSuper */ 0});
inline constexpr FMemberBindType SuperStructBindType = FMemberBindType(FStructType{EMemberKind::Struct, /* IsDynamic */ 0, /* IsSuper */ 1});
inline FMemberBindType BindInnerStruct(FOptionalInnerId& OutBindId, FMemberSpec& OutSpec, FBothStructId Both)
{
OutBindId = FInnerId(Both.BindId);
OutSpec = FMemberSpec(Both.DeclId);
return DefaultStructBindType;
}
inline FMemberBindType BindInnerLeaf(FOptionalInnerId& OutId, FMemberSpec& OutSpec, FUnpackedLeafType Leaf, FOptionalEnumId Id)
{
OutId = ToOptionalInner(Id);
OutSpec = FMemberSpec(Leaf, Id);
return FMemberBindType(Leaf);
}
template<typename Struct, class Ids>
FMemberBindType BindInnermostType(FOptionalInnerId& OutBindId, FMemberSpec& OutSpec)
{
return BindInnerStruct(OutBindId, OutSpec, GetStructBothId<Ids, Struct>());
}
template<Arithmetic Type, class Ids>
FMemberBindType BindInnermostType(FOptionalInnerId& OutId, FMemberSpec& OutSpec)
{
return BindInnerLeaf(OutId, OutSpec, ReflectArithmetic<Type>, NoId);
}
template<Enumeration Enum, class Ids>
FMemberBindType BindInnermostType(FOptionalInnerId& OutId, FMemberSpec& OutSpec)
{
return BindInnerLeaf(OutId, OutSpec, ReflectEnum<Enum>, GetEnumId<Ids, Enum>());
}
template<typename RangeBinding>
constexpr uint32 CountRangeBindings()
{
using InnerBinding = RangeBind<typename RangeBinding::ItemType>;
return 1 + CountRangeBindings<InnerBinding>();
}
template<>
constexpr uint32 CountRangeBindings<void>()
{
return 0;
}
template<typename RangeBinding, uint32 NestLevel>
struct TInnermostType
{
using InnerType = typename RangeBinding::ItemType;
using Type = TInnermostType<RangeBind<InnerType>, NestLevel - 1>::Type;
};
template<typename RangeBinding>
struct TInnermostType<RangeBinding, 1>
{
using Type = typename RangeBinding::ItemType;
};
template<typename RangeBinding, typename Ids, uint32 N>
TConstArrayView<FRangeBinding> GetRangeBindings() requires (N > 0)
{
struct FOnce
{
FOnce(FAnsiStringView RangeBindName)
: Instance(Ids::IndexTypename(RangeBindName))
, Binding(Instance, RangeSizeOf(typename RangeBinding::SizeType{}))
{}
RangeBinding Instance;
FRangeBinding Binding;
};
if constexpr (N == 1)
{
static FOnce Static(ToAnsiView(RangeBinding::BindName));
return MakeArrayView(&Static.Binding, N);
}
else
{
using InnerType = typename RangeBinding::ItemType;
using InnerRangeBinding = RangeBind<InnerType>;
struct FNestedOnce : FOnce
{
FNestedOnce()
: FOnce(ToAnsiView(InnerRangeBinding::BindName))
{
FMemory::Memcpy(NestedBindings, GetRangeBindings<InnerRangeBinding, Ids, N - 1>().GetData(), sizeof(NestedBindings));
}
uint8 NestedBindings[sizeof(FRangeBinding) * (N - 1)] = {};
};
static_assert(std::is_trivially_destructible_v<FRangeBinding>);
static_assert(offsetof(FNestedOnce, Binding) + sizeof(FRangeBinding) == offsetof(FNestedOnce, NestedBindings));
static FNestedOnce Static;
return MakeArrayView(&Static.Binding, N);
}
}
template<LeafType Type, class Runtime>
FMemberBinding BindMember(uint64 Offset, FMemberSpec& OutSpec)
{
FMemberBinding Out(Offset);
Out.InnermostType = BindInnermostType<Type, typename Runtime::Ids>(Out.InnermostSchema, OutSpec);
return Out;
}
template<typename Type, class Runtime>
FMemberBinding BindMember(uint64 Offset, FMemberSpec& OutSpec)
{
using Ids = typename Runtime::Ids;
using CustomBinding = typename Runtime::template CustomBindings<Type>::Type;
FMemberBinding Out(Offset);
if constexpr (!std::is_void_v<CustomBinding>)
{
FBothStructId Both = BindCustomStructOnce<Type, CustomBinding, Runtime>();
Out.InnermostType = BindInnerStruct(Out.InnermostSchema, OutSpec, Both);
}
else
{
using RangeBinding = RangeBind<Type>;
if constexpr (!std::is_void_v<RangeBinding>)
{
constexpr uint32 NumRangeBindings = CountRangeBindings<RangeBinding>();
using InnermostType = typename TInnermostType<RangeBinding, NumRangeBindings>::Type;
Out.RangeBindings = GetRangeBindings<RangeBinding, Ids, NumRangeBindings>();
Out.InnermostType = BindInnermostType<InnermostType, Ids>(Out.InnermostSchema, OutSpec);
for (FRangeBinding Range : ReverseIterate(Out.RangeBindings))
{
OutSpec.RangeWrap(Range.GetSizeType());
}
}
else
{
FBothStructId Both = GetStructBothId<typename Runtime::Ids, Type>();
Out.InnermostType = BindInnerStruct(Out.InnermostSchema, OutSpec, Both);
}
}
return Out;
}
////////////////////////////////////////////////////////////////////////////////////////////////
template<class Ctti, class Ids>
FEnumId DeclareNativeEnum(FEnumDeclarations& Out, EEnumMode Mode)
{
using UnderlyingType = std::underlying_type_t<typename Ctti::Type>;
FType Type = IndexCttiName<Ids, Ctti>();
FEnumId Id = Ids::IndexEnum(Type);
FEnumerator Enumerators[Ctti::NumEnumerators];
for (FEnumerator& Enumerator : Enumerators)
{
Enumerator.Name = Ids::IndexName(Ctti::Enumerators[&Enumerator - Enumerators].Name);
Enumerator.Constant = static_cast<uint64>(static_cast<UnderlyingType>(Ctti::Enumerators[&Enumerator - Enumerators].Constant));
}
Out.Declare(Id, Type, Mode, Enumerators, EEnumAliases::Fail);
return Id;
}
template<class Ctti, class Runtime>
void BindNativeStruct(FSchemaBindings& Out, FBothStructId Both, EMemberPresence Occupancy)
{
using Ids = Runtime::Ids;
using SuperType = typename Ctti::Super;
FOptionalDeclId SuperId;
if constexpr (!std::is_void_v<SuperType>)
{
SuperId = GetStructDeclId<Ids, SuperType>();
}
if constexpr (Ctti::NumVars) // Temp workaround til MakeArrayView handles zero-sized arrays
{
FMemberId MemberNames[Ctti::NumVars];
FMemberSpec MemberTypes[Ctti::NumVars];
FMemberBinding MemberBindings[Ctti::NumVars];
ForEachVar<Ctti>([&]<class Var>()
{
MemberNames[Var::Index] = Ids::IndexMember(Var::Name);
FMemberSpec& OutSpec = MemberTypes[Var::Index];
MemberBindings[Var::Index] = BindMember<typename Var::Type, Runtime>(Var::Offset, OutSpec);
});
const FStructSpec Spec = { Both.DeclId, SuperId, /* no CTTI version yet */ 0, Occupancy, MemberNames, MemberTypes };
Out.BindStruct(Both.BindId, MemberBindings, Spec);
}
else
{
const FStructSpec Spec = { Both.DeclId, SuperId, /* no CTTI version yet */ 0, Occupancy, {}, {} };
Out.BindStruct(Both.BindId, {}, Spec);
}
}
//////////////////////////////////////////////////////////////////////////
// Save -> load struct ids for ESchemaFormat::InMemoryNames, alternative to side-channel with ExtractRuntimeIds()
[[nodiscard]] PLAINPROPS_API TArray<FStructId> IndexRuntimeIds(const FSchemaBatch& Schemas, FIdIndexerBase& Indexer);
// Save -> load ids for ESchemaFormat::StableNames
struct FIdBinding
{
TConstArrayView<FNameId> Names;
TConstArrayView<FNestedScopeId> NestedScopes;
TConstArrayView<FParametricTypeId> ParametricTypes;
TConstArrayView<FInnerId> Schemas;
FNameId Remap(FNameId Old) const { return Names[Old.Idx]; }
FMemberId Remap(FMemberId Old) const { return { Remap(Old.Id) }; }
FFlatScopeId Remap(FFlatScopeId Old) const { return { Remap(Old.Name) }; }
FNestedScopeId Remap(FNestedScopeId Old) const { return NestedScopes[Old.Idx]; }
FScopeId Remap(FScopeId Old) const { return Old.IsFlat() ? FScopeId(Remap(Old.AsFlat())) : Old ? FScopeId(Remap(Old.AsNested())) : Old; }
FConcreteTypenameId Remap(FConcreteTypenameId Old) const { return { Remap(Old.Id) }; }
FParametricTypeId Remap(FParametricTypeId Old) const { return ParametricTypes[Old.Idx]; }
FTypenameId Remap(FTypenameId Old) const { return Old.IsConcrete() ? FTypenameId(Remap(Old.AsConcrete())) : FTypenameId(Remap(Old.AsParametric())); }
FType Remap(FType Old) const { return { Remap(Old.Scope), Remap(Old.Name) }; }
template<typename T>
TOptionalId<T> Remap(TOptionalId<T> Old) const { return Old ? ToOptional(Remap(Old.Get())) : Old; }
TConstArrayView<FStructId> GetStructIds(int32 NumStructs) const
{
// All saved struct schema ids are lower than enum schema ids
check(NumStructs <= Schemas.Num());
return MakeArrayView(reinterpret_cast<const FStructId*>(Schemas.GetData()), NumStructs);
}
};
struct FIdTranslatorBase
{
PLAINPROPS_API static uint32 CalculateTranslationSize(int32 NumSavedNames, const FSchemaBatch& Batch);
PLAINPROPS_API static FIdBinding TranslateIds(FMutableMemoryView To, FIdIndexerBase& Indexer, TConstArrayView<FNameId> TranslatedNames, const FSchemaBatch& From);
};
// Maps saved ids -> runtime load ids for ESchemaFormat::StableNames
struct FIdTranslator : FIdTranslatorBase
{
template<class NameType>
FIdTranslator(TIdIndexer<NameType>& Indexer, TConstArrayView<NameType> SavedNames, const FSchemaBatch& Batch)
{
Allocator.SetNumUninitialized(CalculateTranslationSize(SavedNames.Num(), Batch));
// Translate names
TArrayView<FNameId> NewNames(reinterpret_cast<FNameId*>(&Allocator[0]), SavedNames.Num());
FNameId* NameIt = NewNames.GetData();
for (const NameType& SavedName : SavedNames)
{
(*NameIt++) = Indexer.MakeName(SavedName);
}
FMutableMemoryView OtherIds(NameIt, Allocator.Num() - NewNames.Num() * sizeof(FNameId));
Translation = TranslateIds(/* out */ OtherIds, Indexer, NewNames, Batch);
}
FIdBinding Translation;
TArray<uint8, TInlineAllocator<1024>> Allocator;
};
PLAINPROPS_API FSchemaBatch* CreateTranslatedSchemas(const FSchemaBatch& Schemas, FIdBinding NewIds);
PLAINPROPS_API void DestroyTranslatedSchemas(const FSchemaBatch* Schemas);
} // namespace PlainProps