// Copyright Epic Games, Inc. All Rights Reserved. #include "PlainPropsLoad.h" #include "PlainPropsLoadMember.h" #include "PlainPropsBind.h" #include "PlainPropsInternalBind.h" #include "PlainPropsInternalFormat.h" #include "PlainPropsInternalRead.h" #include "Algo/Compare.h" #include "Algo/Find.h" #include "Containers/Set.h" #include "Misc/Optional.h" #include namespace PlainProps { struct FMemcpyLoadPlan { uint32 Size; uint32 Offset; }; // Duplicated runtime FSchemaBinding with FStructSchemaId load ids instead of runtime FInnerId struct FSchemaLoadPlan { const FSchemaBinding& Clone; TConstArrayView GetMembers() const { return MakeArrayView(Clone.Members, Clone.NumMembers); } const uint32* GetOffsets() const { return Clone.GetOffsets(); } TConstArrayView GetInnerSchemas() const { return MakeArrayView(reinterpret_cast(Clone.GetInnerSchemas()), Clone.NumInnerSchemas); } TConstArrayView GetInnerRangeTypes() const { return MakeArrayView(Clone.GetInnerRangeTypes(), Clone.NumInnerRanges); } const FRangeBinding* GetRangeBindings() const { return Clone.GetRangeBindings(); } }; // ICustomBinding with internal type-erased/lowered struct schemas struct FCustomLoadPlan { const ICustomBinding* Binding; uint32 NumLoadIds; FStructSchemaId LoadIds[0]; }; // Describes how to load a saved struct into the matching in-memory representation class FLoadStructPlan { public: FLoadStructPlan() = default; explicit FLoadStructPlan(FMemcpyLoadPlan Memcpy) : Handle((uint64(Memcpy.Size) << 32) | (uint64(Memcpy.Offset) << 2) | MemcpyMask) { check(Memcpy.Offset == AsMemcpy().Offset && Memcpy.Size == AsMemcpy().Size); } explicit FLoadStructPlan(const ICustomBinding& Custom) : Handle(uint64(&Custom) | CustomMask) { check(&Custom == &AsCustom()); } explicit FLoadStructPlan(const FCustomLoadPlan& Custom) : Handle(uint64(&Custom) | CustomMask | LoadIdsBit) { check(Custom.Binding == &AsCustom()); check(GetInnerLoadIds() == Custom.LoadIds); } // @param OffsetWidth Usage unimplemented, store size and offsets as 8/16/32/64-bit explicit FLoadStructPlan(const FSchemaBinding& Schema, ELeafWidth OffsetWidth, bool bSparse) : Handle(uint64(&Schema) | SchemaBit | (bSparse ? SparseBit : 0) | (uint64(OffsetWidth) << SchemaOffsetShift)) { static_assert(alignof(FSchemaBinding) >= 8); check(&Schema == &AsSchema().Clone); check(bSparse == IsSparseSchema()); check(OffsetWidth == GetOffsetWidth()); } bool IsSchema() const { return (Handle & SchemaBit) == SchemaBit; } bool IsSparseSchema() const { return (Handle & SparseSchemaMask) == SparseSchemaMask; } bool IsMemcpy() const { return (Handle & LoMask) == MemcpyMask; } bool IsCustom() const { return (Handle & LoMask) == CustomMask; } FMemcpyLoadPlan AsMemcpy() const { check(IsMemcpy()); return { static_cast(Handle >> 32), static_cast(Handle) >> 2 }; } FSchemaLoadPlan AsSchema() const { check(IsSchema()); return { *AsPtr() }; } const ICustomBinding& AsCustom() const; const FStructSchemaId* GetInnerLoadIds() { return (Handle & TagMask) == LoadIdsMask ? AsPtr()->LoadIds : nullptr; } ELeafWidth GetOffsetWidth() const { check(IsSchema()); return static_cast((Handle & SchemaOffsetMask) >> SchemaOffsetShift); } private: static constexpr uint64 SparseBit = uint64(1) << FPlatformMemory::KernelAddressBit; static constexpr uint64 LoadIdsBit = SparseBit; static constexpr uint64 TagMask = SparseBit | 0b111; static constexpr uint64 PtrMask = ~(TagMask); static constexpr uint64 LoMask = 0b11; static constexpr uint64 MemcpyMask = 0b00; static constexpr uint64 CustomMask = 0b10; static constexpr uint64 SchemaBit = 0b01; static constexpr uint64 SparseSchemaMask = SchemaBit | SparseBit; static constexpr uint64 SchemaOffsetShift = 1; static constexpr uint64 SchemaOffsetMask = 0b110; static constexpr uint64 LoadIdsMask = CustomMask | LoadIdsBit; template const T* AsPtr() const { check(Handle & PtrMask); return reinterpret_cast(Handle & PtrMask); } uint64 Handle = 0; }; const ICustomBinding& FLoadStructPlan::AsCustom() const { check(IsCustom()); return (Handle & LoadIdsBit) ? *AsPtr()->Binding : *AsPtr(); } //////////////////////////////////////////////////////////////////////////// static uint16 CountEnums(const FStructSchema& Schema) { if (Schema.NumInnerSchemas == 0) { return 0; } uint16 Num = 0; TConstArrayView RangeTypes = Schema.GetRangeTypes(); if (RangeTypes.IsEmpty()) { for (FMemberType Member : Schema.GetMemberTypes()) { Num += IsEnum(Member); } return Num; } uint16 RangeTypeIdx = 0; for (FMemberType Member : Schema.GetMemberTypes()) { if (Member.IsRange()) { FMemberType InnermostType = GrabInnerRangeTypes(RangeTypes, /* in-out */ RangeTypeIdx).Last(); Num += IsEnum(InnermostType); } else { Num += IsEnum(Member); } } check(RangeTypeIdx == Schema.NumRangeTypes); return Num; } static bool HasDifferentSupers(const FStructSchema& From, const FSchemaBinding& To, TConstArrayView ToStructIds) { if (From.Inheritance == ESuper::No) { return To.HasSuper(); } else if (To.HasSuper()) { FStructId FromSuper = ToStructIds[From.GetSuperSchema().Get().Idx]; FStructId ToSuper = To.GetInnerSchemas()[0].AsStruct(); return FromSuper == ToSuper; } return true; } //////////////////////////////////////////////////////////////////////////// // Used to create an additional load plan, beyond the saved struct schema ids struct FLoadIdMapping { FStructSchemaId ReadId; // ~ Batch decl id, index into saved schemas and load plans FStructSchemaId LoadId; // ~ Batch bind id, index into load plans FBindId Id; // Runtime bind id }; // Helps load type-erased/lowered structs with ExplicitBindName by allocating new load-time struct ids class FLoadIdBinder { public: FLoadIdBinder(TConstArrayView Ids, FDebugIds Dbg) : DeclIds(Ids) , NextLoadIdx(static_cast(Ids.Num())) , Debug(Dbg) {} FStructSchemaId BindLoadId(FStructSchemaId ReadId, FBindId Id) { FStructId DeclId = DeclIds[ReadId.Idx]; FStructSchemaId LoadId = Id == DeclId ? ReadId : MapLoadId(ReadId, Id); return LoadId; } FLoadIdMapping GetMapping(int32 Idx) const { return Mappings[Idx]; } int32 NumMappings() const { return Mappings.Num(); } private: TConstArrayView DeclIds; uint32 NextLoadIdx; TArray Mappings; FStructSchemaId MapLoadId(FStructSchemaId ReadId, FBindId Id) { for (FLoadIdMapping Mapping : Mappings) { if (Mapping.Id == Id) { check(Mapping.ReadId == ReadId); return Mapping.LoadId; } } FLoadIdMapping& Mapping = Mappings.AddDefaulted_GetRef(); Mapping.ReadId = ReadId; Mapping.LoadId = { NextLoadIdx++ }; Mapping.Id = Id; return Mapping.LoadId; } public: FDebugIds Debug; }; struct FLoadIdBinderDummy { FDebugIds Debug; static constexpr FStructSchemaId BindLoadId(FStructSchemaId ReadId, FBindId Id) { return ReadId; } static constexpr int32 NumMappings() { return 0; } static inline FLoadIdMapping GetMapping(int32 Idx) { unimplemented(); return {}; } }; //////////////////////////////////////////////////////////////////////////// struct FLoadBatch { FSchemaBatchId BatchId; // Needed to access schemas for custom struct loading uint32 NumReadSchemas; uint32 NumPlans; FLoadStructPlan Plans[0]; FLoadStructPlan operator[](FStructSchemaId LoadId) const { check(LoadId.Idx < NumPlans); return Plans[LoadId.Idx]; } FStructSchemaId GetReadId(FStructSchemaId LoadId) const { check(LoadId.Idx < NumPlans); static_assert(alignof(FLoadStructPlan) >= alignof(FStructSchemaId)); const FStructSchemaId* SaveIds = reinterpret_cast(Plans + NumPlans); return LoadId.Idx < NumReadSchemas ? LoadId : SaveIds[LoadId.Idx - NumReadSchemas]; } }; void FLoadBatchDeleter::operator()(FLoadBatch* Batch) const { FMemory::Free(Batch); } using SubsetByteArray = TArray>; //////////////////////////////////////////////////////////////////////////// struct FMemberLoadBinder : FMemberBinderBase { FMemberLoadBinder(FSchemaBinding& In) : FMemberBinderBase(In) , InnerSchemaIt(const_cast(FSchemaLoadPlan(In).GetInnerSchemas().GetData())) {} ~FMemberLoadBinder() { check(Align(InnerSchemaIt, alignof(FRangeBinding)) == (const void*)Schema.GetRangeBindings() || Schema.NumInnerRanges == 0); } void AddInnerSchema(FStructSchemaId InnermostSchema) { *InnerSchemaIt++ = InnermostSchema; } FStructSchemaId* InnerSchemaIt; }; static void CopyMemberBinding(FLeafMemberBinding Binding, /* in-out */ const FSchemaId*& InnerSchemaIt, FMemberLoadBinder& Out) { InnerSchemaIt += (Binding.Leaf.Type == ELeafBindType::Enum); // Skip enum schema Out.AddMember(Binding.Leaf.Pack(), static_cast(Binding.Offset)); } // TODO: Make all below free functions FLoadPlanner member functions and make LoadIds a member variable template void CopyMemberBinding(FStructMemberBinding Binding, /* in-out */ const FSchemaId*& InnerSchemaIt, LoadIdBinder& LoadIds, FMemberLoadBinder& Out) { check(!Binding.Type.IsSuper); check(!Binding.Type.IsDynamic); // TODO: Build out dynamic struct support FStructSchemaId LoadId = LoadIds.BindLoadId(static_cast(*InnerSchemaIt), Binding.Id); Out.AddMember(FMemberBindType(Binding.Type), static_cast(Binding.Offset)); Out.AddInnerSchema(LoadId); ++InnerSchemaIt; } template void CopyMemberBinding(FRangeMemberBinding Binding, /* in-out */ const FSchemaId*& InnerSchemaIt, LoadIdBinder& LoadIds, FMemberLoadBinder& Out) { FMemberBindType InnermostType = Binding.InnerTypes[Binding.NumRanges - 1]; Out.AddRange(MakeArrayView(Binding.RangeBindings, Binding.NumRanges), InnermostType, static_cast(Binding.Offset)); if (InnermostType.IsStruct()) { FStructSchemaId ReadId = static_cast(*InnerSchemaIt); FStructSchemaId LoadId = LoadIds.BindLoadId(ReadId, Binding.InnermostSchema.Get().AsStructBindId()); Out.AddInnerSchema(LoadId); ++InnerSchemaIt; } else { InnerSchemaIt += (InnermostType.AsLeaf().Bind.Type == ELeafBindType::Enum); // Skip enum schema } } template void CopyMemberBinding(/* in-out */ FMemberVisitor& BindIt, /* in-out */ const FSchemaId*& InnerSchemaIt, LoadIdBinder& LoadIds, FMemberLoadBinder& Out) { switch (BindIt.PeekKind()) { case EMemberKind::Leaf: CopyMemberBinding(BindIt.GrabLeaf(), InnerSchemaIt, Out); break; case EMemberKind::Range: CopyMemberBinding(BindIt.GrabRange(), InnerSchemaIt, LoadIds, Out); break; case EMemberKind::Struct: CopyMemberBinding(BindIt.GrabStruct(), InnerSchemaIt, LoadIds, Out); break; default: check(false); break; } } template static void CreateSubsetBindingWithoutEnumIds(const FStructSchema& From, const FSchemaBinding& To, TConstArrayView ToNames, uint16 NumEnums, LoadIdBinder& LoadIds, SubsetByteArray& Out) { check(To.NumMembers == ToNames.Num() + To.HasSuper()); check(To.NumMembers >= From.NumMembers); check(IsAligned(Out.Num(), alignof(FSchemaBinding))); int32 OutPos = Out.Num(); // Allocate and init header bool bSkipSuperSchema = SkipDeclaredSuperSchema(From.Inheritance); FSchemaBinding Header = { To.DeclId, From.NumMembers, From.NumInnerSchemas - NumEnums - bSkipSuperSchema, From.NumRangeTypes }; Out.AddUninitialized(Header.CalculateSize()); FSchemaBinding* Schema = new (&Out[OutPos]) FSchemaBinding {Header}; // Copy subset of member bindings... FMemberVisitor ToIt(To); FMemberLoadBinder Footer(*Schema); const FSchemaId* InnerSchemaIt = From.GetInnerSchemas() + bSkipSuperSchema; // ...first the unnamed super member... if (From.Inheritance != ESuper::No) { FBindId BindId = ToIt.GrabSuper(); if (UsesSuper(From.Inheritance)) { FStructType FromType = From.Footer[0].AsStruct(); // To.Members[0].AsStruct().IsDynamic isn't set check(FromType.IsSuper); Footer.AddMember(FMemberBindType(FromType), 0); if (!FromType.IsDynamic) { FStructSchemaId ReadId = static_cast(*InnerSchemaIt); FStructSchemaId LoadId = LoadIds.BindLoadId(ReadId, BindId); Footer.AddInnerSchema(LoadId); ++InnerSchemaIt; } } } // ...then a subset of named members const FMemberId* ToNameIt = ToNames.GetData(); for (FMemberId FromName : From.GetMemberNames()) { while (FromName != *ToNameIt++) { ToIt.SkipMember(); check(ToNameIt != ToNames.end()); } CopyMemberBinding(/* in-out */ ToIt, /* in-out */ InnerSchemaIt, /* in-out */ LoadIds, /* out */ Footer); } check(InnerSchemaIt == From.GetInnerSchemas() + From.NumInnerSchemas); } // @pre No enum members template static void CloneBindingWithReplacedStructIds(const FSchemaId* FromIds, const FSchemaBinding& To, LoadIdBinder& LoadIds, SubsetByteArray& Out) { check(IsAligned(Out.Num(), alignof(FSchemaBinding))); // Clone runtime FSchemaBinding including inner schema bind ids uint32 Size = To.CalculateSize(); Out.AddUninitialized(Size); FSchemaBinding* Schema = reinterpret_cast(&Out[Out.Num() - Size]); FMemory::Memcpy(Schema, &To, Size); // Replace inner bind ids with batch load ids const FStructSchemaId* ReadIdIt = static_cast(FromIds); FInnerId* InnerIt = const_cast(Schema->GetInnerSchemas()); for (FInnerId& Inner : MakeArrayView(InnerIt, To.NumInnerSchemas)) { FBindId MemcopiedBindId = Inner.AsStructBindId(); reinterpret_cast(Inner) = LoadIds.BindLoadId(*ReadIdIt++, MemcopiedBindId); } } template [[nodiscard]] static FLoadStructPlan MakeSchemaLoadPlan(const FStructSchema& From, const FSchemaBinding& To, TConstArrayView ToMemberIds, TConstArrayView ToStructIds, LoadIdBinder& LoadIds, SubsetByteArray& OutSubsetSchemas) { uint16 NumEnums = CountEnums(From); if (From.NumMembers < To.NumMembers || NumEnums || HasDifferentSupers(From, To, ToStructIds)) { CreateSubsetBindingWithoutEnumIds(From, To, ToMemberIds, NumEnums, LoadIds, OutSubsetSchemas); } else { check(From.NumMembers == To.NumMembers); check(From.NumInnerSchemas == To.NumInnerSchemas); check(From.NumRangeTypes == To.NumInnerRanges); if (From.NumInnerSchemas > 0) { CloneBindingWithReplacedStructIds(From.GetInnerSchemas(), To, LoadIds, OutSubsetSchemas); } // else reuse existing bindings } // Pointer to created subset load schema will be remapped later return FLoadStructPlan(To, ELeafWidth::B32, !From.IsDense); } [[nodiscard]] static TOptional TryMakeMemcpyPlan(const FStructSchema& From, const FSchemaBinding& To, TConstArrayView ToNames) { // Can't memcopy sparse members nor range-bound members nor super structs if (!From.IsDense || From.NumRangeTypes > 0 || UsesSuper(From.Inheritance)) { return NullOpt; } // Can't memcpy non-contiguous members TConstArrayView FromNames = From.GetMemberNames(); int32 SkipToIdx = ToNames.Find(FromNames[0]); check(SkipToIdx != INDEX_NONE); if (!Algo::Compare(FromNames, ToNames.Slice(SkipToIdx, FromNames.Num()))) { return NullOpt; } // Check all schema members are contiguous leaves const uint32* OffsetIt = To.GetOffsets() + SkipToIdx; const uint32 StartPos = *OffsetIt; uint32 EndPos = *OffsetIt; for (FMemberType Member : From.GetMemberTypes()) { // Note: By adding FStructType::IsDense flag and some struct size lookup, memcpying of nested non-dynamic non-custom-bound structs might be possible uint32 Offset = *OffsetIt++; if (Offset != EndPos || // Non-contiguous or padded Member.IsStruct() || // Nested dtructs have a skippable size prefix that can't be memcopied Member.AsLeaf().Type == ELeafType::Bool) // bool values are stored as bits, see FBitCacheReader { return NullOpt; } EndPos += SizeOf(Member.AsLeaf().Width); } return FMemcpyLoadPlan(EndPos - StartPos, StartPos); } template [[nodiscard]] static FLoadStructPlan MakeLoadPlan(const FStructSchema& From, const FSchemaBinding& To, TConstArrayView ToMemberIds, TConstArrayView ToStructIds, LoadIdBinder& LoadIds, SubsetByteArray& OutSubsetSchemas) { TOptional Memcpy = TryMakeMemcpyPlan(From, To, ToMemberIds); return Memcpy ? FLoadStructPlan(Memcpy.GetValue()) : MakeSchemaLoadPlan(From, To, ToMemberIds, ToStructIds, LoadIds, OutSubsetSchemas); } struct FLoadBindingBase : ICustomBinding { virtual void SaveCustom(FMemberBuilder& Dst, const void* Src, const void* Default, const FSaveContext& Ctx) override final { unimplemented(); } virtual bool DiffCustom(const void*, const void*, const FBindContext&) const override final { unimplemented(); return false; } }; struct FTypeErasedLoadBinding final : FLoadBindingBase { virtual void LoadCustom(void* Dst, FStructLoadView Src, ECustomLoadMethod) const override { checkf(false, TEXT("Can't load type-erased/lowered struct binding with load id %d"), Src.Schema.LoadId.Idx); } }; static FTypeErasedLoadBinding GLoadTypeErasedError; struct FNoopLoadBinding final : FLoadBindingBase { virtual void LoadCustom(void* Dst, FStructLoadView Src, ECustomLoadMethod Method) const override {} }; static FNoopLoadBinding GLoadNoop; template static TOptional> GetContiguousSubset(TConstArrayView View, const TBitArray<>& Keep) { check(Keep.Num() == View.Num()); const int32 Num = Keep.CountSetBits(); check(Num < Keep.Num()); if (Num == 0) { return TConstArrayView{}; } int32 StartIdx = Keep.Find(true); check(StartIdx != INDEX_NONE); if (StartIdx + 1 == Keep.Num()) { return View.Slice(StartIdx, Num); } int32 EndIdx = Keep.FindFrom(false, StartIdx + 1); check(EndIdx != INDEX_NONE); if (EndIdx - StartIdx == Num) { return View.Slice(StartIdx, Num); } return NullOpt; } struct FLoadPlanner { // Constructor parameters const FSchemaBatchId BatchId; const FCustomBindings& Customs; const FSchemaBindings& Schemas; const TConstArrayView RuntimeIds; // Temporary data structures TArray> Plans; TArray> SubsetSchemaSizes; SubsetByteArray SubsetSchemaData; TSet UnboundSaveIds; FLoadBatchPtr CreatePlans(ESchemaFormat Format) { check(NumStructSchemas(BatchId) == RuntimeIds.Num()); // Make load plans for saved schemas const uint32 NumPlans = RuntimeIds.Num(); Plans.SetNumUninitialized(NumPlans); SubsetSchemaSizes.SetNumUninitialized(NumPlans); if (Format == ESchemaFormat::InMemoryNames) { FLoadIdBinderDummy LoadIds{Schemas.GetDebug()}; for (uint32 Idx = 0; Idx < NumPlans; ++Idx) { FLoadIdMapping Mapping; Mapping.ReadId = { Idx }; Mapping.LoadId = { Idx }; Mapping.Id = FBindId(RuntimeIds[Idx]); CreatePlan(Mapping, LoadIds); } return CreateBatch(LoadIds); } TConstArrayView DeclIds(static_cast(RuntimeIds.GetData()), RuntimeIds.Num()); FLoadIdBinder LoadIds(DeclIds, Schemas.GetDebug()); for (uint32 Idx = 0; Idx < NumPlans; ++Idx) { FLoadIdMapping Mapping; Mapping.ReadId = { Idx }; Mapping.LoadId = { Idx }; Mapping.Id = UpCast(DeclIds[Idx]); CreatePlan(Mapping, LoadIds); } // Make load plans for type-erased/ExplicitBindName structs needed by already created load plans if (LoadIds.NumMappings() > 0) { Plans.Reserve(NumPlans + LoadIds.NumMappings()); SubsetSchemaSizes.Reserve(NumPlans + LoadIds.NumMappings()); for (int32 Idx = 0; Idx < LoadIds.NumMappings(); ++Idx) { check(LoadIds.GetMapping(Idx).LoadId.Idx == static_cast(Plans.Num())); Plans.AddUninitialized(); SubsetSchemaSizes.AddUninitialized(); CreatePlan(LoadIds.GetMapping(Idx), /* might grow */ LoadIds); } // Verify that all UnboundSaveIds were bound by some load plan for (int32 Idx = 0; !UnboundSaveIds.IsEmpty() && Idx < LoadIds.NumMappings(); ++Idx) { UnboundSaveIds.Remove(LoadIds.GetMapping(Idx).ReadId); } } for (FStructSchemaId UnboundSaveId : UnboundSaveIds) { checkf(false, TEXT("Unbound struct '%s' can't be loaded"), *Schemas.GetDebug().Print(RuntimeIds[UnboundSaveId.Idx])); } // Allocate load batch, copy plans and subset schemas, and fixup subset schema plans return CreateBatch(LoadIds); } private: template FLoadBatchPtr CreateBatch(const LoadIdBinder& LoadIds) { const uint32 NumPlans = Plans.Num(); const uint32 NumMappings = LoadIds.NumMappings(); const uint32 NumReadSchemas = RuntimeIds.Num(); check(NumPlans == NumReadSchemas + NumMappings); // Allocate plan, init header and copy plans SIZE_T Bytes = sizeof(FLoadBatch) + sizeof(FLoadStructPlan) * NumPlans + Align(sizeof(FStructSchemaId) * NumMappings, alignof(FSchemaBinding)) + SubsetSchemaData.Num(); FLoadBatch Header = { BatchId, NumReadSchemas, NumPlans }; FLoadBatch* Out = new (FMemory::Malloc(Bytes)) FLoadBatch{Header}; FMemory::Memcpy(Out->Plans, Plans.GetData(), sizeof(FLoadStructPlan) * NumPlans); // Copy LoadId -> ReadId mapping so custom-bound mapped plans can form FReadSchemaHandle and FStructView FStructSchemaId* OutReadId = reinterpret_cast(Out->Plans + NumPlans); for (uint32 Idx = 0; Idx < NumMappings; ++Idx) { *OutReadId++ = LoadIds.GetMapping(Idx).ReadId; check(OutReadId[-1].Idx < NumReadSchemas); } // Copy cloned subset schemas and patch up plan pointers if (SubsetSchemaData.Num() > 0) { void* OutSubsetData = Align(OutReadId, alignof(FSchemaBinding)); FMemory::Memcpy(OutSubsetData, SubsetSchemaData.GetData(), SubsetSchemaData.Num()); // Update plans with actual subset schema pointers const uint8* It = static_cast(OutSubsetData); for (uint32 Idx = 0; Idx < NumPlans; ++Idx) { if (int32 Size = SubsetSchemaSizes[Idx]) { if (Plans[Idx].IsSchema()) { check(IsAligned(Size, alignof(FSchemaBinding))); bool bSparse = Plans[Idx].IsSparseSchema(); Out->Plans[Idx] = FLoadStructPlan(*reinterpret_cast(It), ELeafWidth::B32, bSparse); } else { check(Plans[Idx].IsCustom()); check(IsAligned(Size, alignof(FCustomLoadPlan))); Out->Plans[Idx] = FLoadStructPlan(*reinterpret_cast(It)); } It += Size; } } check(It == (uint8*)OutSubsetData + SubsetSchemaData.Num()); check(It == (uint8*)Out + Bytes); } return FLoadBatchPtr(Out); } template void CreatePlan(FLoadIdMapping Mapping, LoadIdBinder& LoadIds) { int32 SubsetSchemaOffset = SubsetSchemaData.Num(); Plans[Mapping.LoadId.Idx] = CreatePlanInner(Mapping, LoadIds); SubsetSchemaSizes[Mapping.LoadId.Idx] = static_cast(SubsetSchemaData.Num() - SubsetSchemaOffset); } // Schema iterator that scans forward to locate inner struct schema ids class FInnerStructSchemaIterator { public: FInnerStructSchemaIterator(const FStructSchema& Schema) : FInnerStructSchemaIterator(Schema, UsesSuper(Schema.Inheritance)) {} FStructSchemaId GrabMemberStruct(FMemberId InName) { while (true) { FMemberId Name = Names[NameIdx]; FMemberType Type = NamedTypes[NameIdx]; FMemberType InnermostType = Type.IsRange() ? GrabInnerRangeTypes(RangeTypes, /* in-out */ RangeTypeIdx).Last() : Type; ++NameIdx; InnerSchemaIt += IsStructOrEnum(InnermostType); if (Name == InName) { check(InnermostType.IsStruct()); return static_cast(InnerSchemaIt[-1]); } } } private: FMemberType const* const NamedTypes; const TConstArrayView Names; const TConstArrayView RangeTypes; int32 NameIdx = 0; int32 RangeTypeIdx = 0; const FSchemaId* InnerSchemaIt; FInnerStructSchemaIterator(const FStructSchema& Schema, bool bSkipSuper) : NamedTypes(FStructSchema::GetMemberTypes(Schema.Footer) + bSkipSuper) , Names(Schema.GetMemberNames()) , RangeTypes(Schema.GetRangeTypes()) , InnerSchemaIt(Schema.GetInnerSchemas() + /* super schema */ (bSkipSuper && !Schema.GetMemberTypes()[0].AsStruct().IsDynamic)) {} }; [[nodiscard]] FCustomLoadPlan* CreateCustomLoadIdsPlan(const ICustomBinding* Custom, const FStructSchema& Schema, TConstArrayView Inners, /* in-out */ FLoadIdBinder& LoadIds) { const uint32 Num = static_cast(Inners.Num()); check(Num); check(Schema.NumInnerSchemas > 0); // Calculate padding and size needed uint32 Size = offsetof(FCustomLoadPlan, LoadIds) + sizeof(FStructSchemaId) * Num; uint32 Pad = Align(SubsetSchemaData.Num(), alignof(FCustomLoadPlan)) - SubsetSchemaData.Num(); SubsetSchemaData.AddUninitialized(Pad + Size); // Construct and init header FCustomLoadPlan Header = {Custom, Num}; FCustomLoadPlan* Out = new (&SubsetSchemaData[SubsetSchemaData.Num() - Size]) FCustomLoadPlan {Header}; check(IsAligned(Out, alignof(FCustomLoadPlan))); FStructSchemaId* OutLoadIdIt = Out->LoadIds; // Populate FLoadIdMapping and init Out->LoadIds FInnerStructSchemaIterator SchemaIt(Schema); for (FInnerStruct Inner : Inners) { FStructSchemaId ReadId = SchemaIt.GrabMemberStruct(Inner.Name); *OutLoadIdIt++ = LoadIds.BindLoadId(ReadId, Inner.Id); } check((void*)OutLoadIdIt == SubsetSchemaData.GetData() + SubsetSchemaData.Num()); // Return unstable pointer that's later replaced in CreateBatch() return Out; } // Get subset of Inners present in schema being read from static TConstArrayView GetLoweredMembers(TConstArrayView Inners, TConstArrayView Names, TArray>& TmpSubset) { bool bKeepAll = true; TBitArray<> Keep(true, Inners.Num()); TConstArrayView ScanNames = Names; for (int32 Idx = 0; Idx < Inners.Num(); ++Idx) { if (const FMemberId* Name = Algo::Find(ScanNames, Inners[Idx].Name)) { // Names must appear in order, limit future searches to later names ScanNames.RightChopInline(1 + Name - &ScanNames[0]); } else { bKeepAll = false; Keep[Idx] = false; } } if (bKeepAll) { return Inners; } else if (TOptional> ContiguousSubset = GetContiguousSubset(Inners, Keep)) { return ContiguousSubset.GetValue(); } for (int32 Idx = 0; Idx < Inners.Num(); ++Idx) { if (Keep[Idx]) { TmpSubset.Add(Inners[Idx]); } } return TmpSubset; } template FLoadStructPlan CreatePlanInner(FLoadIdMapping Mapping, LoadIdBinder& LoadIds) { const FStructSchema& From = ResolveStructSchema(BatchId, Mapping.ReadId); TConstArrayView LoweredInners; if constexpr (std::is_same_v) { if (const ICustomBinding* Custom = Customs.FindStruct(Mapping.Id)) { return FLoadStructPlan(*Custom); } } else if (const ICustomBinding* Custom = Customs.FindStruct(Mapping.Id, /* out */ LoweredInners)) { TArray> TmpSubset; TConstArrayView LoweredMembers = GetLoweredMembers(LoweredInners, From.GetMemberNames(), /* out */ TmpSubset); if (LoweredMembers.IsEmpty()) { return FLoadStructPlan(*Custom); } return FLoadStructPlan(*CreateCustomLoadIdsPlan(Custom, From, LoweredMembers, LoadIds)); } if (From.NumMembers) { const FStructDeclaration* Decl; if (const FSchemaBinding* To = Schemas.FindStruct(Mapping.Id, /* out */ Decl)) { // Possible optimization - some simple memcpy cases doesn't need to resolve the declaration TConstArrayView ToMemberIds = Decl->GetMemberOrder(); return MakeLoadPlan(From, *To, ToMemberIds, RuntimeIds, LoadIds, /* out */ SubsetSchemaData); } // Type-erased structs UnboundSaveIds.Add(Mapping.ReadId); return FLoadStructPlan(GLoadTypeErasedError); } return FLoadStructPlan(GLoadNoop); } }; FLoadBatchPtr CreateLoadPlans(FSchemaBatchId BatchId, const FCustomBindings& Customs, const FSchemaBindings& Schemas, TConstArrayView RuntimeIds, ESchemaFormat Format) { return FLoadPlanner{BatchId, Customs, Schemas, RuntimeIds}.CreatePlans(Format); } //////////////////////////////////////////////////////////////////////////// inline void SetBit(uint8& Out, uint8 Idx, bool bValue) { checkSlow(Idx < 8); uint8 Mask(1 << Idx); if (bValue) { Out |= Mask; } else { Out &= ~Mask; } } struct FLoadRangePlan { ERangeSizeType MaxSize; FOptionalStructSchemaId InnermostStruct; TConstArrayView InnerTypes; const FRangeBinding* Bindings = nullptr; FLoadRangePlan Tail() const { return { InnerTypes[0].AsRange().MaxSize, InnermostStruct, InnerTypes.RightChop(1), Bindings + 1 }; } }; inline static FMemberBindType ToBindType(FMemberType Member) { switch (Member.GetKind()) { case EMemberKind::Leaf: return FMemberBindType(Member.AsLeaf()); case EMemberKind::Range: return FMemberBindType(Member.AsRange()); default: return FMemberBindType(Member.AsStruct()); } } class FRangeLoader { public: static void LoadView(uint8* Member, FRangeLoadView Src, TConstArrayView Bindings) { TArray> InnerTypes; InnerTypes.Add(ToBindType(Src.Schema.ItemType)); if (Src.Schema.ItemType.IsRange()) { for (const FMemberType* It = Src.Schema.NestedItemTypes; It; It = It->IsRange() ? (It + 1) : nullptr) { InnerTypes.Add(ToBindType(*It)); } } check(Bindings.Num() == InnerTypes.Num()); FOptionalStructSchemaId InnermostStruct = InnerTypes.Last().IsStruct() ? static_cast(Src.Schema.InnermostId) : NoId; ERangeSizeType Unused = ERangeSizeType::Uni; FLoadRangePlan Plan = { Unused, InnermostStruct, InnerTypes, Bindings.GetData() }; LoadRangePlan(Member, Src.NumItems, Src.Values, Src.Schema.Batch, Plan); } // T is FByteReader& for normal load plans but FMemoryView for FRangeLoadView case where GrabRangeValues() is already called template static void LoadRangePlan(uint8* Member, uint64 Num, T& ByteItOrValues, const FLoadBatch& Batch, const FLoadRangePlan& Range) { FRangeBinding Binding = Range.Bindings[0]; FMemberBindType InnerType = Range.InnerTypes[0]; if (Binding.IsLeafBinding()) { LoadLeafRange(Member, Num, Binding.AsLeafBinding(), ByteItOrValues, UnpackNonBitfield(InnerType.AsLeaf())); } else if (Num) { const IItemRangeBinding& ItemBinding = Binding.AsItemBinding(); switch (InnerType.GetKind()) { case EMemberKind::Leaf: LoadRangeValues(Member, Num, ItemBinding, ByteItOrValues, Batch, UnpackNonBitfield(InnerType.AsLeaf())); break; case EMemberKind::Range: LoadRangeValues(Member, Num, ItemBinding, ByteItOrValues, Batch, Range.Tail()); break; case EMemberKind::Struct: LoadRangeValues(Member, Num, ItemBinding, ByteItOrValues, Batch, Range.InnermostStruct.Get()); break; } } else { FLoadRangeContext NoItemsCtx{.Request = {Member, 0}}; (Binding.AsItemBinding().MakeItems)(NoItemsCtx); } } static void LoadLeafRange(uint8* Member, uint64 Num, const ILeafRangeBinding& Binding, FByteReader& ByteIt, FUnpackedLeafType Leaf) { FMemoryView Values = Num ? GrabRangeValues(ByteIt, Num, Leaf) : FMemoryView(); LoadLeafRange(Member, Num, Binding, Values, Leaf); } static void LoadLeafRange(uint8* Member, uint64 Num, const ILeafRangeBinding& Binding, FMemoryView Values, FUnpackedLeafType Leaf) { Binding.LoadLeaves(Member, FLeafRangeLoadView(Values.GetData(), Num, Leaf)); } template static void LoadRangeValues(uint8* Member, uint64 Num, const IItemRangeBinding& Binding, FByteReader& ByteIt, const FLoadBatch& Batch, SchemaType&& Schema) { LoadRangeValues(Member, Num, Binding, GrabRangeValues(ByteIt, Num, Schema), Batch, Forward(Schema)); } template static void LoadRangeValues(uint8* Member, uint64 Num, const IItemRangeBinding& Binding, FMemoryView Values, const FLoadBatch& Batch, SchemaType&& Schema) { FByteReader ValueIt(Values); FBitCacheReader BitIt; // Only used by ranges of ERangeSizeType::Uni ranges FLoadRangeContext Ctx{.Request = {Member, Num}}; while (Ctx.Request.Index < Num) { (Binding.MakeItems)(Ctx); CopyRangeValues(Ctx.Items, ValueIt, BitIt, Batch, Schema); Ctx.Request.Index += Ctx.Items.Num; } ValueIt.CheckEmpty(); if (Ctx.Items.bNeedFinalize) { (Binding.MakeItems)(Ctx); } } static FMemoryView GrabRangeValues(FByteReader& ByteIt, uint64 Num, FUnpackedLeafType Leaf) { check(Num > 0); return ByteIt.GrabSlice(GetLeafRangeSize(Num, Leaf)); } template static FMemoryView GrabRangeValues(FByteReader& ByteIt, uint64, SchemaType&&) { return ByteIt.GrabSkippableSlice(); } static void CopyRangeValues(const FConstructedItems& Items, FByteReader& ByteIt, FBitCacheReader&, const FLoadBatch& Batch, FUnpackedLeafType Leaf) { if (Items.Size == SizeOf(Leaf.Width)) { if (Leaf.Type != ELeafType::Bool) { FMemory::Memcpy(Items.Data, ByteIt.GrabBytes(Items.NumBytes()), Items.NumBytes()); } else { FBoolRangeView Bits(ByteIt.GrabBytes(Align(Items.Num, 8)/8), Items.Num); uint8* It = Items.Data; for (bool bBit : Bits) { reinterpret_cast(*It++) = bBit; } } } else // Strided { check(Items.Size > SizeOf(Leaf.Width)); check(false); // todo } } static void CopyRangeValues(const FConstructedItems& Items, FByteReader& ByteIt, FBitCacheReader&, const FLoadBatch& Batch, FStructSchemaId Id) { uint64 ItemSize = Items.Size; uint8* End = Items.Data + Items.NumBytes(); if (Items.bUnconstructed) { for (uint8* It = Items.Data; It != End; It += ItemSize) { PlainProps::ConstructAndLoadStruct(It, FByteReader(ByteIt.GrabSkippableSlice()), Id, Batch); } } else { for (uint8* It = Items.Data; It != End; It += ItemSize) { PlainProps::LoadStruct(It, FByteReader(ByteIt.GrabSkippableSlice()), Id, Batch); } } } static void CopyRangeValues(const FConstructedItems& Items, FByteReader& ByteIt, FBitCacheReader& BitIt, const FLoadBatch& Batch, const FLoadRangePlan& Plan) { uint64 ItemSize = Items.Size; for (uint8* It = Items.Data, *End = It + Items.NumBytes(); It != End; It += ItemSize) { uint64 Num = GrabRangeNum(Plan.MaxSize, ByteIt, BitIt); LoadRangePlan(It, Num, ByteIt, Batch, Plan); } } }; //////////////////////////////////////////////////////////////////////////// template class TMemberLoader : public FRangeLoader { public: TMemberLoader(FByteReader Values, FSchemaLoadPlan Schema, const FLoadBatch& InBatch) : Types(Schema.GetMembers()) , Offsets(Schema.GetOffsets()) , InnerStructSchemas(Schema.GetInnerSchemas()) , InnerRangeTypes(Schema.GetInnerRangeTypes()) , RangeBindings(Schema.GetRangeBindings()) , Batch(InBatch) , ByteIt(Values) {} void Load(void* Struct) { SkipMissingSparseMembers(); while (MemberIdx < Types.Num()) { LoadMember(Struct); ++MemberIdx; SkipMissingSparseMembers(); } } private: const TConstArrayView Types; const OffsetType* const Offsets; const TConstArrayView InnerStructSchemas; const TConstArrayView InnerRangeTypes; const FRangeBinding* RangeBindings; const FLoadBatch& Batch; FByteReader ByteIt; FBitCacheReader BitIt; int32 MemberIdx = 0; int32 InnerRangeIdx = 0; int32 InnerStructIdx = 0; void SkipMissingSparseMembers() {} void SkipMissingSparseMembers() requires (bSparse) { // Make code changes in FMemberReader::SkipMissingSparseMembers() too while (MemberIdx < Types.Num() && BitIt.GrabNext(ByteIt)) { FMemberBindType Type = Types.GetData()[MemberIdx]; EMemberKind Kind = Type.GetKind(); if (Kind == EMemberKind::Struct) { InnerStructIdx += !(Type.AsStruct().IsDynamic); } else if (Kind == EMemberKind::Range) { FMemberBindType InnermostType = GrabInnerRangeTypes(InnerRangeTypes, /* in-out */ InnerRangeIdx).Last(); InnerStructIdx += InnermostType.IsStruct() && !(InnermostType.AsStruct().IsDynamic); } ++MemberIdx; } } void LoadMember(void* Struct) { FMemberBindType Type = Types[MemberIdx]; uint8* Member = static_cast(Struct) + Offsets[MemberIdx]; switch (Type.GetKind()) { case EMemberKind::Leaf: LoadMemberLeaf(Member, Type.AsLeaf()); break; case EMemberKind::Range: LoadMemberRange(Member, GrabInnerRanges(Type.AsRange())); break; case EMemberKind::Struct: LoadMemberStruct(Member, GrabInnerStruct(Type.AsStruct())); break; } } inline FStructSchemaId GrabInnerStruct(FStructBindType Type) { return Type.IsDynamic ? FStructSchemaId { ByteIt.Grab() } : InnerStructSchemas[InnerStructIdx++]; } FLoadRangePlan GrabInnerRanges(FRangeBindType Type) { const FRangeBinding* Bindings = RangeBindings + InnerRangeIdx; TConstArrayView InnerTypes = GrabInnerRangeTypes(InnerRangeTypes, /* in-out */ InnerRangeIdx); FOptionalStructSchemaId InnermostStruct = InnerTypes.Last().IsStruct() ? ToOptional(GrabInnerStruct(InnerTypes.Last().AsStruct())) : NoId; return { Type.MaxSize, InnermostStruct, InnerTypes, Bindings }; } void LoadMemberLeaf(uint8* Member, FLeafBindType Leaf) { switch (Leaf.Bind.Type) { case ELeafBindType::Bool: reinterpret_cast(*Member) = BitIt.GrabNext(ByteIt); break; case ELeafBindType::BitfieldBool: SetBit(*Member, Leaf.Bitfield.Idx, BitIt.GrabNext(ByteIt)); break; default: switch (Leaf.Basic.Width) { case ELeafWidth::B8: FMemory::Memcpy(Member, ByteIt.GrabBytes(1), 1); break; case ELeafWidth::B16: FMemory::Memcpy(Member, ByteIt.GrabBytes(2), 2); break; case ELeafWidth::B32: FMemory::Memcpy(Member, ByteIt.GrabBytes(4), 4); break; case ELeafWidth::B64: FMemory::Memcpy(Member, ByteIt.GrabBytes(8), 8); break; } break; } } void LoadMemberStruct(uint8* Member, FStructSchemaId Id) { PlainProps::LoadStruct(Member, FByteReader(ByteIt.GrabSkippableSlice()), Id, Batch); } void LoadMemberRange(uint8* Member, const FLoadRangePlan& Plan) { uint64 Num = GrabRangeNum(Plan.MaxSize, ByteIt, BitIt); LoadRangePlan(Member, Num, ByteIt, Batch, Plan); } }; //////////////////////////////////////////////////////////////////////////// void LoadStruct(void* Dst, FByteReader Src, FStructSchemaId LoadId, const FLoadBatch& Batch) { FLoadStructPlan Plan = Batch[LoadId]; if (Plan.IsSchema()) { if (Plan.IsSparseSchema()) { TMemberLoader< true, uint32>(Src, Plan.AsSchema(), Batch).Load(Dst); } else { TMemberLoader(Src, Plan.AsSchema(), Batch).Load(Dst); } } else if (Plan.IsMemcpy()) { static_assert(PLATFORM_LITTLE_ENDIAN); FMemcpyLoadPlan MemcpyPlan = Plan.AsMemcpy(); Src.CheckSize(MemcpyPlan.Size); FMemory::Memcpy(static_cast(Dst) + MemcpyPlan.Offset, Src.Peek(), MemcpyPlan.Size); } else { FSchemaLoadHandle Schema{LoadId, Batch}; Plan.AsCustom().LoadCustom(Dst, { Schema, Src }, ECustomLoadMethod::Assign); } } void ConstructAndLoadStruct(void* Dst, FByteReader Src, FStructSchemaId Id, const FLoadBatch& Batch) { FLoadStructPlan Plan = Batch[Id]; checkf(!Plan.IsSchema(), TEXT("Non-default constructible types requires ICustomBinding or in rare cases memcpying")); if (Plan.IsMemcpy()) { Src.CheckSize(Plan.AsMemcpy().Size); FMemory::Memcpy(static_cast(Dst) + Plan.AsMemcpy().Offset, Src.Peek(), Plan.AsMemcpy().Size); } else { FSchemaLoadHandle Schema{Id, Batch}; Plan.AsCustom().LoadCustom(Dst, { Schema, Src }, ECustomLoadMethod::Construct); } } //////////////////////////////////////////////////////////////////////////// FRangeLoadView FNestedRangeLoadIterator::operator*() const { FByteReader PeekBytes = ByteIt; FBitCacheReader PeekBits = BitIt; FRangeLoadSchema OutSchema = { Schema.NestedItemTypes[0], Schema.InnermostId, /* Only valid for nested ranges */ Schema.NestedItemTypes + 1, Schema.Batch }; uint64 OutNumItems = GrabRangeNum(Schema.ItemType.AsRange().MaxSize, /* in-out */ PeekBytes, /* in-out */ PeekBits); FMemoryView OutValues = GrabRangeValues(OutNumItems, OutSchema.ItemType, /* in-out */ PeekBytes); return { OutSchema, OutNumItems, OutValues }; } void FNestedRangeLoadIterator::operator++() { uint64 Num = GrabRangeNum(Schema.ItemType.AsRange().MaxSize, /* in-out */ ByteIt, /* in-out */ BitIt); (void)GrabRangeValues(Num, Schema.NestedItemTypes[0], /* in-out */ ByteIt); } /////////////////////////////////////////////////////////////////////////// FStructRangeLoadView FRangeLoadView::AsStructs() const { check(IsStructRange()); FStructSchemaId LoadId = static_cast(Schema.InnermostId.Get()); return { NumItems, Values, {LoadId, Schema.Batch} }; } FNestedRangeLoadView FRangeLoadView::AsRanges() const { check(IsNestedRange()); return { NumItems, Values, Schema }; } //////////////////////////////////////////////////////////////////////////// static FStructView ToReadView(FStructLoadView In) { FStructSchemaHandle ReadSchema = { In.Schema.Batch.GetReadId(In.Schema.LoadId), In.Schema.Batch.BatchId }; return { ReadSchema, In.Values }; } FMemberLoader::FMemberLoader(FStructLoadView In) : Reader(ToReadView(In)) , LoadIdIt(In.Schema.Batch[In.Schema.LoadId].GetInnerLoadIds()) , Batch(In.Schema.Batch) {} FRangeLoadView FMemberLoader::GrabRange() { const FRangeView In = Reader.GrabRange(); // Replace ReadId with LoadId FOptionalSchemaId InnermostId = In.Schema.InnermostSchema; if (LoadIdIt && InnermostId && GetInnermostType(In.Schema).IsStruct()) { check(LoadIdIt->Idx > InnermostId.Get().Idx); check(InnermostId == Batch.GetReadId(*LoadIdIt)); InnermostId = *LoadIdIt++; } FRangeLoadSchema OutSchema = { In.Schema.ItemType, InnermostId, In.Schema.NestedItemTypes, Batch }; return { OutSchema, In.NumItems, In.Values }; } FStructLoadView FMemberLoader::GrabStruct() { const FStructView In = Reader.GrabStruct(); FStructSchemaId LoadId = LoadIdIt ? *LoadIdIt++ : In.Schema.Id; check(In.Schema.Id == Batch.GetReadId(LoadId)); return { {LoadId, Batch}, In.Values }; } //////////////////////////////////////////////////////////////////////////// void LoadRange(void* Dst, FRangeLoadView Src, TConstArrayView Bindings) { FRangeLoader::LoadView(static_cast(Dst), Src, Bindings); } void LoadRange(void* Dst, FByteReader& SrcBytes, FBitCacheReader& SrcBits, ERangeSizeType MaxSize, FRangeLoadSchema Schema, TConstArrayView Bindings) { if (uint64 Num = GrabRangeNum(MaxSize, SrcBytes, SrcBits)) { FMemoryView Values = GrabRangeValues(Num, Schema.ItemType, SrcBytes); LoadRange(Dst, {Schema, Num, Values}, Bindings); } } void LoadStruct(void* Dst, FStructLoadView Src) { LoadStruct(Dst, Src.Values, Src.Schema.LoadId, Src.Schema.Batch); } void ConstructAndLoadStruct(void* Dst, FStructLoadView Src) { ConstructAndLoadStruct(Dst, Src.Values, Src.Schema.LoadId, Src.Schema.Batch); } void FSchemaLoadHandle::GetInnerLoadIds(TArrayView Out) const { const FStructSchema& ReadSchema = FStructSchemaHandle{ Batch.GetReadId(LoadId), Batch.BatchId }.Resolve(); TConstArrayView MemberTypes = ReadSchema.GetMemberTypes(); TConstArrayView RangeTypes = ReadSchema.GetRangeTypes(); check(Out.Num() == MemberTypes.Num()); FOptionalSchemaId* OutIt = Out.GetData(); uint32 RangeTypeIdx = 0; if (const FStructSchemaId* InnerLoadIds = Batch[LoadId].GetInnerLoadIds()) { for (FMemberType Member : MemberTypes) { FMemberType Innermost = Member.IsRange() ? GrabInnerRangeTypes(RangeTypes, /* in-out */ RangeTypeIdx).Last() : Member; *OutIt++ = Innermost.IsStruct() ? ToOptional(FSchemaId(*InnerLoadIds++)) : NoId; } } else { const FSchemaId* InnerSchemaIt = ReadSchema.GetInnerSchemas(); for (FMemberType Member : MemberTypes) { FMemberType Innermost = Member.IsRange() ? GrabInnerRangeTypes(RangeTypes, /* in-out */ RangeTypeIdx).Last() : Member; if (Innermost.IsStruct()) { *OutIt++ = *InnerSchemaIt++; } else { *OutIt++ = NoId; InnerSchemaIt += Innermost.AsLeaf().Type == ELeafType::Enum; } } } check (OutIt == Out.end()); } void LoadSoleStruct(void* Dst, FStructLoadView Src) { // Todo: Optimize LoadStruct(Dst, FMemberLoader(Src).GrabStruct()); //FLoadStructPlan Plan = Src.Schema.Batch[Src.Schema.LoadId]; } } // namespace PlainProps