Files
UnrealEngine/Engine/Plugins/Experimental/PlainProps/Source/Private/PlainPropsSave.cpp
2025-05-18 13:04:45 +08:00

509 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PlainPropsSave.h"
#include "PlainPropsBind.h"
#include "PlainPropsInternalBind.h"
#include "PlainPropsInternalBuild.h"
#include "PlainPropsInternalDiff.h"
#include "PlainPropsInternalFormat.h"
#include "PlainPropsSaveMember.h"
#include "Algo/Find.h"
#include <type_traits>
namespace PlainProps
{
static uint64 GetBit(uint8 Byte, uint8 BitIdx)
{
return (Byte >> BitIdx) & 1;
}
static uint64 SaveLeaf(const uint8* Member, FUnpackedLeafBindType Leaf)
{
#if DO_CHECK
if (Leaf.Type == ELeafBindType::Float)
{
switch (Leaf.Width)
{
case ELeafWidth::B32: return ValueCast(*reinterpret_cast<const float*>(Member));
case ELeafWidth::B64: return ValueCast(*reinterpret_cast<const double*>(Member));
default: check(false); return 0;
}
}
#endif
if (Leaf.Type == ELeafBindType::BitfieldBool)
{
return GetBit(*Member, Leaf.BitfieldIdx);
}
else
{
// Undefined behavior. If problematic, use memcpy or switch(Leaf.Type) and ValueCast(reinterpret_cast<...>(Member))
switch (Leaf.Width)
{
case ELeafWidth::B8: return *Member;
case ELeafWidth::B16: return *reinterpret_cast<const uint16*>(Member);
case ELeafWidth::B32: return *reinterpret_cast<const uint32*>(Member);
case ELeafWidth::B64: return *reinterpret_cast<const uint64*>(Member);
}
}
check(false);
return uint64(0);
}
struct FLeafRangeSaver
{
FBuiltRange* Out;
uint8* OutIt;
uint8* OutEnd;
FLeafRangeSaver(FScratchAllocator& Scratch, uint64 Num, SIZE_T LeafSize)
: Out(FBuiltRange::Create(Scratch, Num, LeafSize))
, OutIt(Out->Data)
, OutEnd(OutIt + Num * LeafSize)
{}
void Append(FExistingItemSlice Slice, uint32 Stride, SIZE_T LeafSize, const FSaveContext&)
{
check(OutIt + Slice.Num * LeafSize <= OutEnd);
FMemory::Memcpy(OutIt, Slice.Data, Slice.Num * LeafSize);
OutIt += Slice.Num * LeafSize;
}
FBuiltRange* Finish()
{
check(OutIt == OutEnd);
return Out;
}
};
template<SIZE_T LeafSize>
struct TStridingLeafRangeSaver : FLeafRangeSaver
{
inline TStridingLeafRangeSaver(FScratchAllocator& Scratch, uint64 Num, SIZE_T) : FLeafRangeSaver(Scratch, Num, LeafSize) {}
inline void Append(FExistingItemSlice Slice, uint32 Stride, SIZE_T, const FSaveContext&)
{
uint8* Dst = OutIt;
for (const uint8* Src = static_cast<const uint8*>(Slice.Data), *SrcEnd = Src + Slice.Num * Stride; Src != SrcEnd; Src += Stride, Dst += LeafSize)
{
FMemory::Memcpy(Dst, Src, LeafSize);
}
OutIt = Dst;
}
};
//////////////////////////////////////////////////////////////////////////
template<typename BuiltItemType, typename ItemSchemaType>
struct TNonLeafRangeSaver
{
FBuiltRange* Out;
BuiltItemType* It;
TNonLeafRangeSaver(FScratchAllocator& Scratch, uint64 Num, ItemSchemaType)
: Out(FBuiltRange::Create(Scratch, Num, sizeof(BuiltItemType)))
, It(reinterpret_cast<BuiltItemType*>(Out->Data))
{}
void Append(FExistingItemSlice Slice, uint32 Stride, ItemSchemaType Schema, const FSaveContext& OuterCtx)
{
check(It + Slice.Num <= reinterpret_cast<BuiltItemType*>(Out->Data) + Out->Num);
for (uint64 Idx = 0; Idx < Slice.Num; ++Idx)
{
*It++ = SaveRangeItem(Slice.At(Idx, Stride), Schema, OuterCtx);
}
}
[[nodiscard]] FBuiltRange* Finish()
{
check(It == reinterpret_cast<BuiltItemType*>(Out->Data) + Out->Num);
return Out;
}
};
struct FDefaultStruct
{
FBindId Id;
const void* Struct;
};
using FInternalNestedRangeSaver = TNonLeafRangeSaver<FBuiltRange*, FRangeMemberBinding>;
using FInternalStructRangeSaver = TNonLeafRangeSaver<FBuiltStruct*, FBindId>;
using FInternalStructRangeDeltaSaver = TNonLeafRangeSaver<FBuiltStruct*, FDefaultStruct>;
//////////////////////////////////////////////////////////////////////////
template<class SaverType, typename InnerContextType>
[[nodiscard]] inline FBuiltRange* SaveRangeItems(FSaveRangeContext& ReadCtx, const IItemRangeBinding& Binding, const FSaveContext& OuterCtx, InnerContextType InnerCtx)
{
const uint64 NumTotal = ReadCtx.Items.NumTotal;
SaverType Saver(OuterCtx.Scratch, NumTotal, InnerCtx);
while (true)
{
check(ReadCtx.Items.Slice.Num > 0);
Saver.Append(ReadCtx.Items.Slice, ReadCtx.Items.Stride, InnerCtx, OuterCtx);
ReadCtx.Request.NumRead += ReadCtx.Items.Slice.Num;
if (ReadCtx.Request.NumRead >= NumTotal)
{
check(ReadCtx.Request.NumRead == NumTotal);
return Saver.Finish();
}
Binding.ReadItems(ReadCtx);
}
}
template<class SaverType, typename InnerContextType>
[[nodiscard]] FBuiltRange* SaveNonLeafRange(const void* Range, const IItemRangeBinding& Binding, const FSaveContext& OuterCtx, InnerContextType InnerCtx)
{
FSaveRangeContext ReadCtx = { { Range } };
Binding.ReadItems(ReadCtx);
if (ReadCtx.Items.NumTotal)
{
return SaveRangeItems<SaverType>(ReadCtx, Binding, OuterCtx, InnerCtx);
}
return nullptr;
}
[[nodiscard]] static FBuiltRange* SaveLeafRange(const void* Range, const IItemRangeBinding& Binding, const FSaveContext& OuterCtx, ELeafWidth Width)
{
SIZE_T LeafSize = SizeOf(Width);
FSaveRangeContext ReadCtx = { { Range } };
Binding.ReadItems(ReadCtx);
if (const uint64 NumTotal = ReadCtx.Items.NumTotal)
{
if (ReadCtx.Items.Stride == LeafSize)
{
return SaveRangeItems<FLeafRangeSaver>(ReadCtx, Binding, OuterCtx, LeafSize);
}
else switch (Width)
{
case ELeafWidth::B8: return SaveRangeItems<TStridingLeafRangeSaver<1>>(ReadCtx, Binding, OuterCtx, LeafSize);
case ELeafWidth::B16: return SaveRangeItems<TStridingLeafRangeSaver<2>>(ReadCtx, Binding, OuterCtx, LeafSize);
case ELeafWidth::B32: return SaveRangeItems<TStridingLeafRangeSaver<4>>(ReadCtx, Binding, OuterCtx, LeafSize);
case ELeafWidth::B64: return SaveRangeItems<TStridingLeafRangeSaver<8>>(ReadCtx, Binding, OuterCtx, LeafSize);
}
}
return nullptr;
}
FBuiltRange* SaveLeafRange(const void* Range, const ILeafRangeBinding& Binding, FUnpackedLeafType Leaf, const FSaveContext& Ctx)
{
FLeafRangeAllocator Allocator(Ctx.Scratch, Leaf);
Binding.SaveLeaves(Range, Allocator);
return Allocator.GetAllocatedRange();
}
inline const FStructDeclaration& GetDeclaration(const FSaveContext& Ctx, FBindId BindId)
{
if (const FStructDeclaration* Out = Ctx.Customs.FindDeclaration(BindId))
{
return *Out;
}
return Ctx.Schemas.GetDeclaration(BindId);
}
[[nodiscard]] static FBuiltRange* SaveStructRange(const void* Range, const IItemRangeBinding& ItemBinding, const FSaveContext& Ctx, FBindId Id)
{
if (Ctx.Defaults && GetDeclaration(Ctx, Id).Occupancy == EMemberPresence::AllowSparse)
{
FDefaultStruct Default = { Id, Ctx.Defaults->Get(Id) };
return SaveNonLeafRange<FInternalStructRangeDeltaSaver>(Range, ItemBinding, Ctx, Default);
}
else
{
return SaveNonLeafRange<FInternalStructRangeSaver>(Range, ItemBinding, Ctx, Id);
}
}
[[nodiscard]] FBuiltRange* SaveRange(const void* Range, FRangeMemberBinding Member, const FSaveContext& Ctx)
{
FRangeBinding Binding = Member.RangeBindings[0];
FMemberBindType InnerType = Member.InnerTypes[0];
if (Binding.IsLeafBinding())
{
return SaveLeafRange(Range, Binding.AsLeafBinding(), UnpackNonBitfield(InnerType.AsLeaf()), Ctx);
}
const IItemRangeBinding& ItemBinding = Binding.AsItemBinding();
switch (InnerType.GetKind())
{
case EMemberKind::Leaf: return SaveLeafRange(Range, ItemBinding, Ctx, GetItemWidth(InnerType.AsLeaf()));
case EMemberKind::Range: return SaveNonLeafRange<FInternalNestedRangeSaver>(Range, ItemBinding, Ctx, GetInnerRange(Member));
case EMemberKind::Struct: return SaveStructRange(Range, ItemBinding, Ctx, Member.InnermostSchema.Get().AsStructBindId());
}
check(false);
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
[[nodiscard]] static FBuiltRange* SaveRangeItem(const uint8* Range, FRangeMemberBinding Member, const FSaveContext& Ctx)
{
return SaveRange(Range, Member, Ctx);
}
[[nodiscard]] static FBuiltStruct* SaveRangeItem(const uint8* Struct, FBindId Id, const FSaveContext& Ctx)
{
return SaveStruct(Struct, Id, Ctx);
}
[[nodiscard]] static FBuiltStruct* SaveRangeItem(const uint8* Struct, FDefaultStruct Default, const FSaveContext& Ctx)
{
return SaveStructDelta(Struct, Default.Struct, Default.Id, Ctx);
}
[[nodiscard]] static FMemberType* CreateInnerRangeTypes(FScratchAllocator& Scratch, uint32 NumInnerTypes, const FMemberBindType* InnerTypes)
{
if (NumInnerTypes <= 1)
{
return nullptr;
}
FMemberType* Out = Scratch.AllocateArray<FMemberType>(NumInnerTypes);
for (uint32 Idx = 0; Idx < NumInnerTypes; ++Idx)
{
Out[Idx] = ToMemberType(InnerTypes[Idx]);
}
return Out;
}
[[nodiscard]] static FMemberSchema CreateRangeSchema(FScratchAllocator& Scratch, FRangeMemberBinding Member)
{
FMemberType* InnerRangeTypes = CreateInnerRangeTypes(Scratch, Member.NumRanges, Member.InnerTypes);
return { FMemberType(Member.RangeBindings[0].GetSizeType()), ToMemberType(Member.InnerTypes[0]), Member.NumRanges, Member.InnermostSchema, InnerRangeTypes };
}
static void SaveMember(FMemberBuilder& Out, const void* Struct, FMemberId Name, const FSaveContext& Ctx, FLeafMemberBinding Member)
{
FUnpackedLeafType Type = ToUnpackedLeafType(Member.Leaf);
Out.AddLeaf(Name, Type, Member.Enum, SaveLeaf(At(Struct, Member.Offset), Member.Leaf));
}
static void SaveMember(FMemberBuilder& Out, const void* Struct, FMemberId Name, const FSaveContext& Ctx, FRangeMemberBinding Member)
{
Out.AddRange(Name, { CreateRangeSchema(Ctx.Scratch, Member), SaveRange(At(Struct, Member.Offset), Member, Ctx) });
}
static void SaveMember(FMemberBuilder& Out, const void* Struct, FMemberId Name, const FSaveContext& Ctx, FStructMemberBinding Member)
{
Out.AddStruct(Name, Member.Id, SaveStruct(At(Struct, Member.Offset), Member.Id, Ctx));
}
inline void SaveAllMembers(FMemberBuilder& Out, const void* Struct, const FSchemaBinding& Schema, const FStructDeclaration& Declaration, const FSaveContext& Ctx)
{
FMemberVisitor It(Schema);
if (Declaration.Super)
{
FBindId SuperId = It.GrabSuper();
checkSlow(SuperId == ToOptionalStruct(Declaration.Super));
const FStructDeclaration* SuperDecl;
const FSchemaBinding& SuperSchema = Ctx.Schemas.GetStruct(SuperId, /* out */ SuperDecl);
SaveAllMembers(Out, Struct, SuperSchema, *SuperDecl, Ctx);
Out.BuildSuperStruct(Ctx.Scratch, *SuperDecl, Ctx.Schemas.GetDebug());
}
for (FMemberId Name : Declaration.GetMemberOrder())
{
switch (It.PeekKind())
{
case EMemberKind::Leaf: SaveMember(Out, Struct, Name, Ctx, It.GrabLeaf()); break;
case EMemberKind::Range: SaveMember(Out, Struct, Name, Ctx, It.GrabRange()); break;
case EMemberKind::Struct: SaveMember(Out, Struct, Name, Ctx, It.GrabStruct()); break;
}
}
checkSlow(!It.HasMore());
}
FBuiltStruct* SaveStruct(const void* Struct, FBindId Id, const FSaveContext& Ctx)
{
const FStructDeclaration* Declaration = nullptr;
FMemberBuilder Out;
if (ICustomBinding* Custom = Ctx.Customs.FindStructToSave(Id, /* out */ Declaration))
{
Custom->SaveCustom(Out, Struct, nullptr, Ctx);
}
else
{
const FSchemaBinding& Schema = Ctx.Schemas.GetStruct(Id, /* out */ Declaration);
SaveAllMembers(Out, Struct, Schema, *Declaration, Ctx);
}
return Out.BuildAndReset(Ctx.Scratch, *Declaration, Ctx.Schemas.GetDebug());
}
////////////////////////////////////////////////////////////////////////////////////////////////
static bool DiffItem(const uint8* A, const uint8* B, const FSaveContext& Ctx, FRangeMemberBinding Member)
{
FRangeBinding Binding = Member.RangeBindings[0];
FMemberBindType InnerType = Member.InnerTypes[0];
if (Binding.IsLeafBinding())
{
return Binding.AsLeafBinding().DiffLeaves(A, B);
}
const IItemRangeBinding& ItemBinding = Binding.AsItemBinding();
switch (InnerType.GetKind())
{
case EMemberKind::Leaf: return DiffItemRange(A, B, ItemBinding, Ctx, SizeOf(GetItemWidth(InnerType.AsLeaf())));
case EMemberKind::Range: return DiffItemRange(A, B, ItemBinding, Ctx, GetInnerRange(Member));
case EMemberKind::Struct: return DiffItemRange(A, B, ItemBinding, Ctx, Member.InnermostSchema.Get().AsStructBindId());
}
check(false);
return false;
}
static bool DiffItem(const uint8* A, const uint8* B, const FSaveContext& Ctx, FBindId Id)
{
if (const ICustomBinding* Custom = Ctx.Customs.FindStruct(Id))
{
return Custom->DiffCustom(A, B, Ctx);
}
bool bOut = false;
for (FMemberVisitor It(Ctx.Schemas.GetStruct(Id)); It.HasMore() && !bOut; )
{
uint32 Offset = It.PeekOffset();
const uint8* ItemA = A + Offset;
const uint8* ItemB = B + Offset;
switch (It.PeekKind())
{
case EMemberKind::Leaf: bOut = !!DiffLeaf(ItemA, ItemB, It.GrabLeaf().Leaf); break;
case EMemberKind::Range: bOut = DiffItem(ItemA, ItemB, Ctx, It.GrabRange()); break;
case EMemberKind::Struct: bOut = DiffItem(ItemA, ItemB, Ctx, It.GrabStruct().Id); break;
}
}
return bOut;
}
////////////////////////////////////////////////////////////////////////////////////////////////
static void SaveMemberDelta(FMemberBuilder& Out, const void* Struct, const void* Default, FMemberId Name, const FSaveContext& Ctx, FLeafMemberBinding Member)
{
if (DiffLeaf(At(Struct, Member.Offset), At(Default, Member.Offset), Member.Leaf))
{
SaveMember(Out, Struct, Name, Ctx, Member);
}
}
static void SaveMemberDelta(FMemberBuilder& Out, const void* Struct, const void* Default, FMemberId Name, const FSaveContext& Ctx, FRangeMemberBinding Member)
{
const uint8* Range = At(Struct, Member.Offset);
if (DiffItem(Range, At(Default, Member.Offset), Ctx, Member))
{
Out.AddRange(Name, { CreateRangeSchema(Ctx.Scratch, Member), SaveRange(Range, Member, Ctx) });
}
}
static void SaveMemberDelta(FMemberBuilder& Out, const void* Struct, const void* Default, FMemberId Name, const FSaveContext& Ctx, FStructMemberBinding Member)
{
if (FBuiltStruct* Delta = SaveStructDeltaIfDiff(At(Struct, Member.Offset), At(Default, Member.Offset), Member.Id, Ctx))
{
Out.AddStruct(Name, Member.Id, Delta);
}
}
static void SaveSchemaBoundStructDelta(FMemberBuilder& Out, const void* Struct, const void* Default, const FSchemaBinding& Schema, const FStructDeclaration& Declaration, const FSaveContext& Ctx)
{
if (Declaration.Occupancy == EMemberPresence::AllowSparse)
{
FMemberVisitor It(Schema);
if (Declaration.Super)
{
FBindId SuperId = It.GrabSuper();
checkSlow(SuperId == ToOptionalStruct(Declaration.Super));
const FStructDeclaration* SuperDecl;
const FSchemaBinding& SuperSchema = Ctx.Schemas.GetStruct(SuperId, /* out */ SuperDecl);
SaveSchemaBoundStructDelta(Out, Struct, Default, SuperSchema, *SuperDecl, Ctx);
Out.BuildSuperStruct(Ctx.Scratch, *SuperDecl, Ctx.Schemas.GetDebug());
}
for (FMemberId Name : Declaration.GetMemberOrder())
{
switch (It.PeekKind())
{
case EMemberKind::Leaf: SaveMemberDelta(Out, Struct, Default, Name, Ctx, It.GrabLeaf()); break;
case EMemberKind::Range: SaveMemberDelta(Out, Struct, Default, Name, Ctx, It.GrabRange()); break;
case EMemberKind::Struct: SaveMemberDelta(Out, Struct, Default, Name, Ctx, It.GrabStruct()); break;
}
}
checkSlow(!It.HasMore());
}
else
{
SaveAllMembers(Out, Struct, Schema, Declaration, Ctx);
}
}
FBuiltStruct* SaveStructDelta(const void* Struct, const void* Default, FBindId Id, const FSaveContext& Ctx)
{
FMemberBuilder Out;
const FStructDeclaration* Declaration = nullptr;
if (ICustomBinding* Custom = Ctx.Customs.FindStructToSave(Id, /* out */ Declaration))
{
if (Custom->DiffCustom(Struct, Default, Ctx))
{
Custom->SaveCustom(Out, Struct, Default, Ctx);
// Could type-check saved custom struct against declaration here,
// or add a type-checking BuildAndReset overload and call that.
// It's not important to type-check bindings, they're generated.
}
}
else
{
const FSchemaBinding& Schema = Ctx.Schemas.GetStruct(Id, /* out */ Declaration);
SaveSchemaBoundStructDelta(Out, Struct, Default, Schema, *Declaration, Ctx);
}
return Out.BuildAndReset(Ctx.Scratch, *Declaration, Ctx.Schemas.GetDebug());
}
FBuiltStruct* SaveStructDeltaIfDiff(const void* Struct, const void* Default, FBindId Id, const FSaveContext& Ctx)
{
FMemberBuilder Out;
const FStructDeclaration* Declaration = nullptr;
if (ICustomBinding* Custom = Ctx.Customs.FindStructToSave(Id, /* out */ Declaration))
{
if (Custom->DiffCustom(Struct, Default, Ctx))
{
Custom->SaveCustom(Out, Struct, Default, Ctx);
return Out.BuildAndReset(Ctx.Scratch, *Declaration, Ctx.Schemas.GetDebug());
}
return nullptr;
}
const FSchemaBinding& Schema = Ctx.Schemas.GetStruct(Id, /* out */ Declaration);
SaveSchemaBoundStructDelta(Out, Struct, Default, Schema, *Declaration, Ctx);
return Out.IsEmpty() ? nullptr : Out.BuildAndReset(Ctx.Scratch, *Declaration, Ctx.Schemas.GetDebug());
}
////////////////////////////////////////////////////////////////////////////////////////////////
FRangeSaverBase::FRangeSaverBase(FScratchAllocator& Scratch, uint64 Num, SIZE_T ItemSize)
: Range(FBuiltRange::Create(Scratch, Num, ItemSize))
, It(Range->Data)
#if DO_CHECK
, End(It + Num * ItemSize)
#endif
{}
} // namespace PlainProps