FDuplicateDataReader Overread when adding a member to a struct

I’ve built the engine from Github and have everything working. I’m trying to step through These instructions for exposing a per-instance parameter in an InstancedStaticMeshComponent to Blueprint.

I’ve changed the definition for FInstancedStaticMeshInstanceData (in InstancedStaticMeshComponent.h line 32) to this:



USTRUCT()
struct FInstancedStaticMeshInstanceData
{
    GENERATED_USTRUCT_BODY()

        UPROPERTY(EditAnywhere, Category=Instances)
        FMatrix Transform;

        UPROPERTY(EditAnywhere, Category = Instances)
        float PackedUVs;

    FInstancedStaticMeshInstanceData()
        : Transform(FMatrix::Identity)
        , PackedUVs(0)
    {
    }

    FInstancedStaticMeshInstanceData(const FMatrix& InTransform)
        : Transform(InTransform)
        , PackedUVs(0)
    {
    }

    friend FArchive& operator<<(FArchive& Ar, FInstancedStaticMeshInstanceData& InstanceData)
    {
        // @warning BulkSerialize: FInstancedStaticMeshInstanceData is serialized as memory dump
        // See TArray::BulkSerialize for detailed description of implied limitations.
        Ar << InstanceData.Transform;
        Ar << InstanceData.PackedUVs;
        return Ar;
    }
};

This adds a property, PackedUVs, and adds it to both constructors and the serialize operator thing. Obviously this float doesn’t do anything yet, but it’s in the struct.

When I run the editor, I can add an InstancedStaticMeshComponent to a BP, add an instance to it, and it shows up in the BP viewport. I can add it to a level, and it shows up in the level. But when try to PIE, the editor immediately crashes with this:

The instructions are from a year ago for an old version of the engine, so I’m not surprised it needs tweaking. I’ve tried this without editing the constructors and/or without editing the operator<<, and nothing works. How do I make this struct serialize properly?

Hi [USER=“10065”]Shivan Hunter[/USER] , have you found a solution to this problem? I have the same issue but I don’t find anything online! :frowning:

I have a similar issue where adding an instance will throw that exception. After stepping through the code, I think the function:
void UInstancedStaticMeshComponent::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
is getting fired twice, once for each element in the struct. I think this causes two instances to be added, instead of just one, and that creates the serialization error. I’m having trouble understanding how to use PostEditChangeChainProperty and FPropertyChangedChainEvent. Googling just leads to a bunch of unanswered questions about this topic.

Update: After removing the extra element from the struct, it looks like PostEditChangeChainProperty still gets called twice, so that’s probably not the actual issue :frowning:

I figured it out! :slight_smile:

TArray::BulkSerialize has some restrictions, one of which is sizeof(ElementType) must be equal to the sum of sizes of it’s member variables. FMatrix is 4x4 (each row has 4 floats) and needs to be aligned to 16 bytes. Additional members in a structure containing FMatrix will also need to be aligned to 16 bytes. Instead of adding a single float, you’ll need to add 3 more to pad the structure. This is what I came up with:


USTRUCT()
struct FInstancedStaticMeshInstanceData
{
    GENERATED_USTRUCT_BODY()

        UPROPERTY(EditAnywhere, Category=Instances)
        FMatrix Transform;

    UPROPERTY(EditAnywhere, Category = Instances)
    float PerInstanceParameter;

    float Padding0;
    float Padding1;
    float Padding2;

    FInstancedStaticMeshInstanceData()
        : Transform(FMatrix::Identity)
        , PerInstanceParameter(0)
    {
    }

    FInstancedStaticMeshInstanceData(const FMatrix& InTransform)
        : Transform(InTransform)
        , PerInstanceParameter(0)
    {
    }

    FInstancedStaticMeshInstanceData(const FMatrix& InTransform, float InPerInstanceParameter)
        : Transform(InTransform)
        , PerInstanceParameter(InPerInstanceParameter)
    {
    }

    friend FArchive& operator<<(FArchive& Ar, FInstancedStaticMeshInstanceData& InstanceData)
    {
        // @warning BulkSerialize: FInstancedStaticMeshInstanceData is serialized as memory dump
        // See TArray::BulkSerialize for detailed description of implied limitations.
        Ar << InstanceData.Transform << InstanceData.PerInstanceParameter << InstanceData.Padding0 << InstanceData.Padding1 << InstanceData.Padding2;
        return Ar;
    }
};

Obviously, the major drawback to this approach is the 25% increase in memory requirements for adding a single float property. I think it’s possible to force alignment without the padding (the comments in TArray::BulkSerialize states “use pragma pack (push,1)/ (pop) to ensure alignment”), but then I think reading and writing performance will be impacted. Another approach would be keeping a separate array of floats instead of jamming the extra property into FInstancedStaticMeshInstanceData. This would reduce the memory footprint and would have negligible impact on projects using the current per instance random implementation, but it would require two array lookups rather than one when using the custom per instance parameter. It also makes editing the instances in the editor more clunky, since you’ll have to deal with two arrays. And it’s also just more difficult to implement properly compared to hijacking FInstancedStaticMeshInstanceData. I might submit a pull request and see what Epic thinks.

Thank You @vle07!!! This was driving me insane!