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

388 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "PlainPropsDeclare.h"
#include "PlainPropsTypes.h"
#include "Algo/Compare.h"
#include "Containers/Array.h"
#include "Containers/ArrayView.h"
#include "Memory/MemoryView.h"
#include "Templates/UniquePtr.h"
namespace PlainProps
{
struct FBuiltMember;
struct FBuiltStruct;
struct FBuiltRange;
struct FUnpackedLeafType;
//////////////////////////////////////////////////////////////////////////
/// Single-threaded scratch allocator for intermediate built representation
class FScratchAllocator
{
struct FPage
{
FPage* PrevPage;
uint8 Data[0];
};
static constexpr uint32 PageSize = 65536;
static constexpr uint32 DataSize = PageSize - offsetof(FPage, Data);
uint8* Cursor = nullptr;
uint8* PageEnd = nullptr;
FPage* LastPage = nullptr;
PLAINPROPS_API uint8* AllocateInNewPage(SIZE_T Size, uint32 Alignment);
public:
UE_NONCOPYABLE(FScratchAllocator);
FScratchAllocator() = default;
PLAINPROPS_API ~FScratchAllocator();
inline void* Allocate(SIZE_T Size, uint32 Alignment)
{
uint8* Out = Align(Cursor, Alignment);
if (Out + Size <= PageEnd)
{
Cursor = Out + Size;
return Out;
}
return AllocateInNewPage(Size, Alignment);
}
inline void* AllocateZeroed(SIZE_T Size, uint32 Alignment)
{
void* Out = Allocate(Size, Alignment);
FMemory::Memzero(Out, Size);
return Out;
}
template<typename T>
inline T* AllocateArray(uint64 Num)
{
T* Out = static_cast<T*>(Allocate(Num * sizeof(T), alignof(T)));
for (uint64 Idx = 0; Idx < Num; ++Idx)
{
new (Out + Idx) T;
}
return Out;
}
};
//////////////////////////////////////////////////////////////////////////
struct FMemberSchema
{
FMemberType Type;
FMemberType InnerRangeType;
uint16 NumInnerRanges;
FOptionalInnerId InnerSchema;
const FMemberType* NestedRangeTypes;
TConstArrayView<FMemberType> GetInnerRangeTypes() const
{
return MakeArrayView(NestedRangeTypes ? NestedRangeTypes : &InnerRangeType, NumInnerRanges);
}
FMemberType GetInnermostType() const
{
return NumInnerRanges ? GetInnerRangeTypes().Last() : Type;
}
[[nodiscard]] PLAINPROPS_API FMemberType& EditInnermostType(FScratchAllocator& Scratch);
void CheckInvariants()
{
check(Type.IsRange() == !!NumInnerRanges);
check(!!NestedRangeTypes == (NumInnerRanges > 1));
}
};
inline bool operator==(FMemberSchema A, FMemberSchema B)
{
if (FMemory::Memcmp(&A, &B, sizeof(FMemberSchema)) == 0)
{
return true;
}
return A.Type == B.Type && A.InnerSchema == B.InnerSchema && Algo::Compare(A.GetInnerRangeTypes(), B.GetInnerRangeTypes());
}
//////////////////////////////////////////////////////////////////////////
inline uint64 ValueCast(bool Value) { return static_cast<uint64>(Value); }
inline uint64 ValueCast(int8 Value) { return static_cast<uint8>(Value); }
inline uint64 ValueCast(int16 Value) { return static_cast<uint16>(Value); }
inline uint64 ValueCast(int32 Value) { return static_cast<uint32>(Value); }
inline uint64 ValueCast(int64 Value) { return static_cast<uint64>(Value); }
inline uint64 ValueCast(uint8 Value) { return Value; }
inline uint64 ValueCast(uint16 Value) { return Value; }
inline uint64 ValueCast(uint32 Value) { return Value; }
inline uint64 ValueCast(uint64 Value) { return Value; }
uint64 ValueCast(float Value);
uint64 ValueCast(double Value);
inline uint64 ValueCast(char8_t Value) { return static_cast<uint8>(Value); }
inline uint64 ValueCast(char16_t Value) { return static_cast<uint16>(Value); }
inline uint64 ValueCast(char32_t Value) { return static_cast<uint32>(Value); }
//////////////////////////////////////////////////////////////////////////
struct FTypedRange
{
FMemberSchema Schema;
FBuiltRange* Values = nullptr;
};
template<Arithmetic T>
FMemberSchema MakeLeafRangeSchema(ERangeSizeType MaxSize)
{
return { FMemberType(MaxSize), ReflectArithmetic<T>.Pack(), 1, NoId, nullptr };
}
template<Arithmetic T, typename SizeType>
FMemberSchema MakeLeafRangeSchema()
{
return MakeLeafRangeSchema<T>(RangeSizeOf(SizeType{}));
}
template<Enumeration T>
FMemberSchema MakeEnumRangeSchema(FEnumId Id, ERangeSizeType MaxSize)
{
return { FMemberType(MaxSize), ReflectEnum<T>.Pack(), 1, FInnerId(Id), nullptr };
}
template<Enumeration T, typename SizeType>
FMemberSchema MakeEnumRangeSchema(FEnumId Id)
{
return MakeEnumRangeSchema<T>(Id, RangeSizeOf(SizeType{}));
}
template<UnsignedIntegral T>
FMemberSchema MakeEnumRangeSchema(FEnumId Id, ERangeSizeType MaxSize)
{
return { FMemberType(MaxSize), FMemberType(ELeafType::Enum, ReflectArithmetic<T>.Width), 1, FInnerId(Id), nullptr };
}
inline constexpr FMemberType DefaultStructType = FMemberType(FStructType{EMemberKind::Struct, /* IsDynamic */ 0, /* IsSuper */ 0});
inline constexpr FMemberType SuperStructType = FMemberType(FStructType{EMemberKind::Struct, /* IsDynamic */ 0, /* IsSuper */ 1});
inline constexpr FMemberType DynamicStructType = FMemberType(FStructType{EMemberKind::Struct, /* IsDynamic */ 1, /* IsSuper */ 0});
inline FMemberSchema MakeStructRangeSchema(ERangeSizeType SizeType, FStructId Id)
{
return { FMemberType(SizeType), DefaultStructType, 1, FInnerId(Id), nullptr };
}
inline FMemberSchema MakeDynamicStructRangeSchema(ERangeSizeType SizeType)
{
return { FMemberType(SizeType), DynamicStructType, 1, NoId, nullptr };
}
PLAINPROPS_API FMemberSchema MakeNestedRangeSchema(FScratchAllocator& Scratch, ERangeSizeType SizeType, FMemberSchema InnerRangeSchema);
//////////////////////////////////////////////////////////////////////////
[[nodiscard]] PLAINPROPS_API FBuiltRange* CloneLeaves(FScratchAllocator& Scratch, uint64 Num, const void* Data, SIZE_T LeafSize);
template<Arithmetic T>
[[nodiscard]] FTypedRange BuildLeafRange(FScratchAllocator& Scratch, ERangeSizeType SizeType, const T* Values, uint64 Num)
{
// todo: detect invalid floats
return { MakeLeafRangeSchema<T>(SizeType), CloneLeaves(Scratch, Num, Values, sizeof(T)) };
}
template<Arithmetic T, typename SizeType>
[[nodiscard]] FTypedRange BuildLeafRange(FScratchAllocator& Scratch, const T* Values, SizeType Num)
{
return BuildLeafRange(Scratch, RangeSizeOf(SizeType{}), Values, Num);
}
template<Arithmetic T>
[[nodiscard]] FTypedRange BuildLeafRange(FScratchAllocator& Scratch, ERangeSizeType SizeType, TConstArrayView<T> Values)
{
return BuildLeafRange(Scratch, SizeType, Values.GetData(), Values.Num());
}
template<Arithmetic T, typename SizeType>
[[nodiscard]] FTypedRange BuildLeafRange(FScratchAllocator& Scratch, TConstArrayView<T, SizeType> Values)
{
return BuildLeafRange(Scratch, RangeSizeOf(SizeType{}), Values.GetData(), Values.Num());
}
template<Enumeration T, typename SizeType>
[[nodiscard]] FTypedRange BuildEnumRange(FScratchAllocator& Scratch, FEnumId Enum, TConstArrayView<T, SizeType> Values)
{
return { MakeEnumRangeSchema<T, SizeType>(Enum), CloneLeaves(Scratch, Values.Num(), Values.GetData(), sizeof(T)) };
}
template<UnsignedIntegral T>
[[nodiscard]] FTypedRange BuildEnumRange(FScratchAllocator& Scratch, FEnumId Enum, ERangeSizeType SizeType, TConstArrayView<T> Values)
{
return { MakeEnumRangeSchema<T>(Enum, SizeType), CloneLeaves(Scratch, Values.Num(), Values.GetData(), sizeof(T)) };
}
[[nodiscard]] inline FTypedRange MakeStructRange(FStructId Id, ERangeSizeType SizeType, FBuiltRange* Values )
{
return { MakeStructRangeSchema(SizeType, Id), Values };
}
//////////////////////////////////////////////////////////////////////////
union FBuiltValue
{
uint64 Leaf;
FBuiltStruct* Struct;
FBuiltRange* Range;
};
struct FTypedValue
{
FMemberSchema Schema;
FBuiltValue Value;
};
struct FBuiltMember
{
FBuiltMember(FMemberId InName, FTypedValue In) : FBuiltMember(InName, In.Schema, In.Value) {}
FBuiltMember(FOptionalMemberId N, FMemberSchema S, FBuiltValue V) : Name(N), Schema(MoveTemp(S)), Value(V) {}
PLAINPROPS_API FBuiltMember(FMemberId Name, FUnpackedLeafType Leaf, FOptionalEnumId Id, uint64 Value);
PLAINPROPS_API FBuiltMember(FMemberId Name, FTypedRange Range);
PLAINPROPS_API FBuiltMember(FMemberId Name, FStructId Id, FBuiltStruct* Value);
PLAINPROPS_API static FBuiltMember MakeSuper(FStructId Id, FBuiltStruct* Value);
FOptionalMemberId Name;
FMemberSchema Schema;
FBuiltValue Value;
};
//////////////////////////////////////////////////////////////////////////
// Builds an ordered list of properties to be saved
class FMemberBuilder
{
public:
template<Arithmetic T>
void Add(FMemberId Name, T Value)
{
AddLeaf(Name, ReflectArithmetic<T>, NoId, ValueCast(Value));
}
template<Enumeration T>
void AddEnum(FMemberId Name, FEnumId Id, T Value)
{
AddLeaf(Name, ReflectEnum<T>, ToOptional(Id), ValueCast(static_cast<__underlying_type(T)>(Value)));
}
template<UnsignedIntegral T>
void AddEnum(FMemberId Name, FEnumId Id, T Value)
{
AddLeaf(Name, {ELeafType::Enum, ReflectArithmetic<T>.Width}, ToOptional(Id), Value);
}
template<UnsignedIntegral T>
void AddHex(FMemberId Name, T Value)
{
AddLeaf(Name, {ELeafType::Hex, ReflectArithmetic<T>.Width}, NoId, Value);
}
void AddLeaf(FMemberId Name, FUnpackedLeafType Leaf, FOptionalEnumId Enum, uint64 Value) { Members.Emplace(Name, Leaf, Enum, Value); }
void AddRange(FMemberId Name, FTypedRange Range) { Members.Emplace(Name, Range); }
// Add already built nested struct, must not be null
void AddStruct(FMemberId Name, FStructId Id, FBuiltStruct* Struct) { check(Struct); Members.Emplace(Name, Id, Struct); }
void Add(FMemberId Name, FTypedValue TypedValue) { Members.Emplace(Name, TypedValue); }
// Add already built super struct, must not be null and must be the first member added
PLAINPROPS_API void AddSuperStruct(FStructId SuperSchema, FBuiltStruct* SuperStruct);
// Build members into a single nested super struct member, no-op if no non-super members has been added
PLAINPROPS_API void BuildSuperStruct(FScratchAllocator& Scratch, const FStructDeclaration& Super, const FDebugIds& Debug);
[[nodiscard]] PLAINPROPS_API FBuiltStruct* BuildAndReset(FScratchAllocator& Scratch, const FStructDeclaration& Declared, const FDebugIds& Debug);
bool IsEmpty() const { return Members.IsEmpty(); }
private:
using FBuiltMemberArray = TArray<FBuiltMember, TInlineAllocator<16>>;
FBuiltMemberArray Members;
//template<typename T>
//void NormalizeLeafRange(T*, uint64) {}
//PLAINPROPS_API void NormalizeLeafRange(float*, uint64 Num);
//PLAINPROPS_API void NormalizeLeafRange(double*, uint64 Num);
};
// Rough API draft
struct FDenseMemberBuilder
{
FScratchAllocator& Scratch;
const FDebugIds& Debug;
template<typename T, typename... Ts>
[[nodiscard]] FBuiltStruct* BuildHomogeneous(const FStructDeclaration& Declaration, T Head, Ts... Tail) const
{
// Todo: Handle enums, ranges and structs
FBuiltValue Values[] = { {.Leaf = ValueCast(Head)}, {.Leaf = (ValueCast(Tail))}... };
return BuildHomo(Declaration, FMemberType(ReflectArithmetic<T>.Pack()), Values);
}
private:
[[nodiscard]] PLAINPROPS_API FBuiltStruct* BuildHomo(const FStructDeclaration& Declaration, FMemberType Leaf, TConstArrayView<FBuiltValue> Values) const;
};
// Helper class for building struct ranges
class FStructRangeBuilder
{
public:
FStructRangeBuilder(uint64 Num, ERangeSizeType InSizeType)
: SizeType(InSizeType)
{
Structs.SetNum(Num);
}
template<typename IntType>
explicit FStructRangeBuilder(IntType Num)
: FStructRangeBuilder(static_cast<uint64>(Num), RangeSizeOf(Num))
{}
FMemberBuilder& operator[](uint64 Idx) { return Structs[Idx]; }
FTypedRange BuildAndReset(FScratchAllocator& Scratch, const FStructDeclaration& Declared, const FDebugIds& Debug);
private:
TArray64<FMemberBuilder> Structs;
ERangeSizeType SizeType;
};
// Helper class for building nested ranges
class FNestedRangeBuilder
{
public:
FNestedRangeBuilder(FMemberSchema InSchema, int64 InitialReserve)
: Schema(InSchema)
{
Ranges.Reserve(InitialReserve);
}
~FNestedRangeBuilder();
void Add(FTypedRange Range)
{
check(Range.Values == nullptr || Range.Schema == Schema);
Ranges.Add(Range.Values);
}
[[nodiscard]] FTypedRange BuildAndReset(FScratchAllocator& Scratch, ERangeSizeType SizeType);
private:
TArray64<FBuiltRange*> Ranges;
FMemberSchema Schema;
};
} // namespace PlainProps