Custom Serialized Structure Does Not Save/Load to map in editor

Alright, 1 week later and no one saved me from having to dive into the source code to find out what I was missing :frowning:. But I did figure out the problem :slight_smile:!

Turns out, it was a pretty simple thing I was missing. In order for the engine to detect if a non-property member (i.e. member variables that are not decorated with the UPROPERTY macro) of a struct decorated with the USTRUCT macro that you want to be serialized via a custom serializer (for example, because that struct is being used as a property in some other object) has changed (and therefore it needs to be serialized), you need to provide a comparison function and notify the engine of that comparison function’s existence.

That comparison function can either be an operator== member function or an Identical(const T* Other, uint32 PortFlags) member function in the struct class. Once you have one of these, you can inform the engine of its existence by adding WithIdenticalViaEquality = true or WithIdentical = true (respectively) to your TStructOpsTypeTraits class (alongside the WithSerializer = true you added to inform the engine of your custom serialization function’s existence).

For the example project I posted, this means making the following modifications:

  • In our definition of FStructTest, we can simply add a default comparison operator like this:
USTRUCT( )
struct FStructTest
{
    GENERATED_BODY( )
public:
    FStructTest( ) = default;

    FStructTest( uint8 unValue )
    {
        m_oCustomSerializedMember = TTemplateType{ unValue };
    }

    // !! Add this function !!
    bool operator==( const FStructTest& rOtherStruct ) const = default;
    // !! Add this function !!

    bool Serialize( FArchive& Ar )
    {
        Ar << m_oCustomSerializedMember;
        return true;
    };

    TTemplateType<uint8> m_oCustomSerializedMember;
};

In order to support this default comparison operator, we also add a default comparison operator to TTemplateType’s definition, like this:

template<typename T>
struct TTemplateType
{
    constexpr TTemplateType& operator=( const TTemplateType& rOther ) = default;

    // !! Add this function !!
    bool operator==( const TTemplateType<T>& rOtherStruct ) const = default;
    // !! Add this function !!

    T m_tTemplatedMember;

    friend FArchive& operator<< <>( FArchive& rArchive, TTemplateType<T>& rBitSet );
};

Then we notify the engine of FStructTest’s new comparison operator by modifying our TStructOpsTypeTraits specialization to set WithIdenticalViaEquality = true, like this:

template<> struct TStructOpsTypeTraits<FStructTest> : public TStructOpsTypeTraitsBase2<FStructTest>
{
    enum
    {
        WithSerializer = true,

        // !! Add this line !!
        WithIdenticalViaEquality = true
        // !! Add this line !!
    };
};

And that’s it! Our custom property now properly saves and loads when you make changes to it in the details panel.

3 Likes