#include "Def.h"
#include "Generated.h"

namespace Gen
{
    bool Save(const PuzzleElementType::Enum* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            auto val = (uint8_t)obj[i];
            isOk = Save(&val, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(PuzzleElementType::Enum* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            uint8_t& val = (uint8_t&)obj[i];
            isOk = Load(&val, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Save(const EMaterial::Enum* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            auto val = (int32_t)obj[i];
            isOk = Save(&val, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(EMaterial::Enum* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            int32_t& val = (int32_t&)obj[i];
            isOk = Load(&val, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Save(const int8_t* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(int8_t* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const int16_t* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(int16_t* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const int32_t* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(int32_t* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const int64_t* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(int64_t* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const uint8_t* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(uint8_t* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const uint16_t* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(uint16_t* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const uint32_t* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(uint32_t* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const uint64_t* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(uint64_t* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const bool* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(bool* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const float* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(float* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const double* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(double* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const char* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Write(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Load(char* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (int32_t i = 0; i < count; ++i)
        {
            isOk = serializer.Read(&obj[i], sizeof(obj[i])) && isOk;
        }
        return isOk;
    }
    bool Save(const Vec2* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].x, 1, serializer) && isOk;
            isOk = Save(&obj[i].y, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(Vec2* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[Vec2::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[Vec2::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].x, 1, serializer) && isOk;
                isOk = Load(&obj[i].y, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "x") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Vec2, x);
            }
            if (bx::strCmp(memberName, "y") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Vec2, y);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "x") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "y") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const Vec3* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].x, 1, serializer) && isOk;
            isOk = Save(&obj[i].y, 1, serializer) && isOk;
            isOk = Save(&obj[i].z, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(Vec3* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[Vec3::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[Vec3::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].x, 1, serializer) && isOk;
                isOk = Load(&obj[i].y, 1, serializer) && isOk;
                isOk = Load(&obj[i].z, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "x") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Vec3, x);
            }
            if (bx::strCmp(memberName, "y") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Vec3, y);
            }
            if (bx::strCmp(memberName, "z") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Vec3, z);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "x") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "y") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "z") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const Vec4* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].x, 1, serializer) && isOk;
            isOk = Save(&obj[i].y, 1, serializer) && isOk;
            isOk = Save(&obj[i].z, 1, serializer) && isOk;
            isOk = Save(&obj[i].w, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(Vec4* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[Vec4::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[Vec4::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].x, 1, serializer) && isOk;
                isOk = Load(&obj[i].y, 1, serializer) && isOk;
                isOk = Load(&obj[i].z, 1, serializer) && isOk;
                isOk = Load(&obj[i].w, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "x") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Vec4, x);
            }
            if (bx::strCmp(memberName, "y") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Vec4, y);
            }
            if (bx::strCmp(memberName, "z") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Vec4, z);
            }
            if (bx::strCmp(memberName, "w") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Vec4, w);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "x") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "y") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "z") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "w") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const Mat3* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(obj[i].M, 9, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(Mat3* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[Mat3::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[Mat3::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(obj[i].M, 9, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "M") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Mat3, M);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "M") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 9, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const Mat4* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(obj[i].M, 16, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(Mat4* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[Mat4::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[Mat4::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(obj[i].M, 16, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "M") == 0 && bx::strCmp(memberTypeName, "float") == 0)
            {
                WriteDestinations[i] = offsetof(Mat4, M);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "M") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<float*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 16, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const Transform* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].M, 1, serializer) && isOk;
            isOk = Save(&obj[i].MI, 1, serializer) && isOk;
            isOk = Save(&obj[i].Position, 1, serializer) && isOk;
            isOk = Save(&obj[i].Rotation, 1, serializer) && isOk;
            isOk = Save(&obj[i].Scale, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(Transform* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[Transform::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[Transform::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].M, 1, serializer) && isOk;
                isOk = Load(&obj[i].MI, 1, serializer) && isOk;
                isOk = Load(&obj[i].Position, 1, serializer) && isOk;
                isOk = Load(&obj[i].Rotation, 1, serializer) && isOk;
                isOk = Load(&obj[i].Scale, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "M") == 0 && bx::strCmp(memberTypeName, "Mat4") == 0)
            {
                WriteDestinations[i] = offsetof(Transform, M);
            }
            if (bx::strCmp(memberName, "MI") == 0 && bx::strCmp(memberTypeName, "Mat4") == 0)
            {
                WriteDestinations[i] = offsetof(Transform, MI);
            }
            if (bx::strCmp(memberName, "Position") == 0 && bx::strCmp(memberTypeName, "Vec3") == 0)
            {
                WriteDestinations[i] = offsetof(Transform, Position);
            }
            if (bx::strCmp(memberName, "Rotation") == 0 && bx::strCmp(memberTypeName, "Mat4") == 0)
            {
                WriteDestinations[i] = offsetof(Transform, Rotation);
            }
            if (bx::strCmp(memberName, "Scale") == 0 && bx::strCmp(memberTypeName, "Vec3") == 0)
            {
                WriteDestinations[i] = offsetof(Transform, Scale);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "M") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Mat4*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "MI") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Mat4*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Position") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Vec3*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Rotation") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Mat4*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Scale") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Vec3*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const AssetHandle* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].Idx, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(AssetHandle* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[AssetHandle::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[AssetHandle::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].Idx, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "Idx") == 0 && bx::strCmp(memberTypeName, "uint32_t") == 0)
            {
                WriteDestinations[i] = offsetof(AssetHandle, Idx);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "Idx") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint32_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const ModelHandle* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].ModelIdx, 1, serializer) && isOk;
            isOk = Save(&obj[i].Asset, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(ModelHandle* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[ModelHandle::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[ModelHandle::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].ModelIdx, 1, serializer) && isOk;
                isOk = Load(&obj[i].Asset, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "ModelIdx") == 0 && bx::strCmp(memberTypeName, "uint16_t") == 0)
            {
                WriteDestinations[i] = offsetof(ModelHandle, ModelIdx);
            }
            if (bx::strCmp(memberName, "Asset") == 0 && bx::strCmp(memberTypeName, "AssetHandle") == 0)
            {
                WriteDestinations[i] = offsetof(ModelHandle, Asset);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "ModelIdx") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint16_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Asset") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<AssetHandle*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const TextureHandle* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].TextureIdx, 1, serializer) && isOk;
            isOk = Save(&obj[i].Asset, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(TextureHandle* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[TextureHandle::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[TextureHandle::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].TextureIdx, 1, serializer) && isOk;
                isOk = Load(&obj[i].Asset, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "TextureIdx") == 0 && bx::strCmp(memberTypeName, "uint16_t") == 0)
            {
                WriteDestinations[i] = offsetof(TextureHandle, TextureIdx);
            }
            if (bx::strCmp(memberName, "Asset") == 0 && bx::strCmp(memberTypeName, "AssetHandle") == 0)
            {
                WriteDestinations[i] = offsetof(TextureHandle, Asset);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "TextureIdx") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint16_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Asset") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<AssetHandle*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const PuzPos* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].X, 1, serializer) && isOk;
            isOk = Save(&obj[i].Y, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(PuzPos* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[PuzPos::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[PuzPos::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].X, 1, serializer) && isOk;
                isOk = Load(&obj[i].Y, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "X") == 0 && bx::strCmp(memberTypeName, "int8_t") == 0)
            {
                WriteDestinations[i] = offsetof(PuzPos, X);
            }
            if (bx::strCmp(memberName, "Y") == 0 && bx::strCmp(memberTypeName, "int8_t") == 0)
            {
                WriteDestinations[i] = offsetof(PuzPos, Y);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "X") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<int8_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Y") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<int8_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const StaticPuzzleCard* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(obj[i].Elements, 4, serializer) && isOk;
            isOk = Save(&obj[i].ModelHandle, 1, serializer) && isOk;
            isOk = Save(&obj[i].ModelTextureHandle, 1, serializer) && isOk;
            isOk = Save(&obj[i].BoardTextureHandle, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(StaticPuzzleCard* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[StaticPuzzleCard::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[StaticPuzzleCard::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(obj[i].Elements, 4, serializer) && isOk;
                isOk = Load(&obj[i].ModelHandle, 1, serializer) && isOk;
                isOk = Load(&obj[i].ModelTextureHandle, 1, serializer) && isOk;
                isOk = Load(&obj[i].BoardTextureHandle, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "Elements") == 0 && bx::strCmp(memberTypeName, "PuzzleElementType") == 0)
            {
                WriteDestinations[i] = offsetof(StaticPuzzleCard, Elements);
            }
            if (bx::strCmp(memberName, "ModelHandle") == 0 && bx::strCmp(memberTypeName, "ModelHandle") == 0)
            {
                WriteDestinations[i] = offsetof(StaticPuzzleCard, ModelHandle);
            }
            if (bx::strCmp(memberName, "ModelTextureHandle") == 0 && bx::strCmp(memberTypeName, "TextureHandle") == 0)
            {
                WriteDestinations[i] = offsetof(StaticPuzzleCard, ModelTextureHandle);
            }
            if (bx::strCmp(memberName, "BoardTextureHandle") == 0 && bx::strCmp(memberTypeName, "TextureHandle") == 0)
            {
                WriteDestinations[i] = offsetof(StaticPuzzleCard, BoardTextureHandle);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "Elements") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<PuzzleElementType::Enum*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 4, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "ModelHandle") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<ModelHandle*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "ModelTextureHandle") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<TextureHandle*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "BoardTextureHandle") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<TextureHandle*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const StaticPuzzleCardHandle* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].Idx, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(StaticPuzzleCardHandle* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[StaticPuzzleCardHandle::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[StaticPuzzleCardHandle::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].Idx, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "Idx") == 0 && bx::strCmp(memberTypeName, "uint16_t") == 0)
            {
                WriteDestinations[i] = offsetof(StaticPuzzleCardHandle, Idx);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "Idx") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint16_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const PuzzleVisualSettings* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].TileBaseColor, 1, serializer) && isOk;
            isOk = Save(&obj[i].TileDotColor, 1, serializer) && isOk;
            isOk = Save(&obj[i].Test, 1, serializer) && isOk;
            isOk = Save(&obj[i].DisabledCardTint, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(PuzzleVisualSettings* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[PuzzleVisualSettings::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[PuzzleVisualSettings::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].TileBaseColor, 1, serializer) && isOk;
                isOk = Load(&obj[i].TileDotColor, 1, serializer) && isOk;
                isOk = Load(&obj[i].Test, 1, serializer) && isOk;
                isOk = Load(&obj[i].DisabledCardTint, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "TileBaseColor") == 0 && bx::strCmp(memberTypeName, "Vec4") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleVisualSettings, TileBaseColor);
            }
            if (bx::strCmp(memberName, "TileDotColor") == 0 && bx::strCmp(memberTypeName, "Vec4") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleVisualSettings, TileDotColor);
            }
            if (bx::strCmp(memberName, "Test") == 0 && bx::strCmp(memberTypeName, "Vec3") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleVisualSettings, Test);
            }
            if (bx::strCmp(memberName, "DisabledCardTint") == 0 && bx::strCmp(memberTypeName, "Vec4") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleVisualSettings, DisabledCardTint);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "TileBaseColor") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Vec4*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "TileDotColor") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Vec4*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Test") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Vec3*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "DisabledCardTint") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Vec4*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const StaticPuzzleData* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(obj[i].Cards, 64, serializer) && isOk;
            isOk = Save(&obj[i].Visuals, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(StaticPuzzleData* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[StaticPuzzleData::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[StaticPuzzleData::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(obj[i].Cards, 64, serializer) && isOk;
                isOk = Load(&obj[i].Visuals, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "Cards") == 0 && bx::strCmp(memberTypeName, "StaticPuzzleCard") == 0)
            {
                WriteDestinations[i] = offsetof(StaticPuzzleData, Cards);
            }
            if (bx::strCmp(memberName, "Visuals") == 0 && bx::strCmp(memberTypeName, "PuzzleVisualSettings") == 0)
            {
                WriteDestinations[i] = offsetof(StaticPuzzleData, Visuals);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "Cards") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<StaticPuzzleCard*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 64, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Visuals") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<PuzzleVisualSettings*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const PuzzleCardStack* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].RefCard, 1, serializer) && isOk;
            isOk = Save(&obj[i].MaxAvailableCount, 1, serializer) && isOk;
            isOk = Save(&obj[i].UsedCount, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(PuzzleCardStack* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[PuzzleCardStack::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[PuzzleCardStack::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].RefCard, 1, serializer) && isOk;
                isOk = Load(&obj[i].MaxAvailableCount, 1, serializer) && isOk;
                isOk = Load(&obj[i].UsedCount, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "RefCard") == 0 && bx::strCmp(memberTypeName, "StaticPuzzleCardHandle") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleCardStack, RefCard);
            }
            if (bx::strCmp(memberName, "MaxAvailableCount") == 0 && bx::strCmp(memberTypeName, "uint8_t") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleCardStack, MaxAvailableCount);
            }
            if (bx::strCmp(memberName, "UsedCount") == 0 && bx::strCmp(memberTypeName, "uint8_t") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleCardStack, UsedCount);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "RefCard") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<StaticPuzzleCardHandle*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "MaxAvailableCount") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint8_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "UsedCount") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint8_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const PlacedPuzzleCard* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].RefCard, 1, serializer) && isOk;
            isOk = Save(&obj[i].Position, 1, serializer) && isOk;
            isOk = Save(&obj[i].Rotation, 1, serializer) && isOk;
            isOk = Save(&obj[i].IsLocked, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(PlacedPuzzleCard* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[PlacedPuzzleCard::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[PlacedPuzzleCard::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].RefCard, 1, serializer) && isOk;
                isOk = Load(&obj[i].Position, 1, serializer) && isOk;
                isOk = Load(&obj[i].Rotation, 1, serializer) && isOk;
                isOk = Load(&obj[i].IsLocked, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "RefCard") == 0 && bx::strCmp(memberTypeName, "StaticPuzzleCardHandle") == 0)
            {
                WriteDestinations[i] = offsetof(PlacedPuzzleCard, RefCard);
            }
            if (bx::strCmp(memberName, "Position") == 0 && bx::strCmp(memberTypeName, "PuzPos") == 0)
            {
                WriteDestinations[i] = offsetof(PlacedPuzzleCard, Position);
            }
            if (bx::strCmp(memberName, "Rotation") == 0 && bx::strCmp(memberTypeName, "uint8_t") == 0)
            {
                WriteDestinations[i] = offsetof(PlacedPuzzleCard, Rotation);
            }
            if (bx::strCmp(memberName, "IsLocked") == 0 && bx::strCmp(memberTypeName, "bool") == 0)
            {
                WriteDestinations[i] = offsetof(PlacedPuzzleCard, IsLocked);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "RefCard") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<StaticPuzzleCardHandle*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Position") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<PuzPos*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Rotation") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint8_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "IsLocked") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<bool*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const PuzzleData* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].ID, 1, serializer) && isOk;
            isOk = Save(obj[i].PuzzleName, 64, serializer) && isOk;
            isOk = Save(&obj[i].WidthTiles, 1, serializer) && isOk;
            isOk = Save(&obj[i].HeightTiles, 1, serializer) && isOk;
            isOk = Save(&obj[i].AvailableCardCount, 1, serializer) && isOk;
            isOk = Save(obj[i].AvailableCards, 16, serializer) && isOk;
            isOk = Save(obj[i].PlacedCards, 256, serializer) && isOk;
            isOk = Save(obj[i].BackgroundTiles, 1024, serializer) && isOk;
            isOk = Save(&obj[i].GoalPositionCount, 1, serializer) && isOk;
            isOk = Save(obj[i].GoalPositions, 16, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(PuzzleData* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[PuzzleData::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[PuzzleData::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].ID, 1, serializer) && isOk;
                isOk = Load(obj[i].PuzzleName, 64, serializer) && isOk;
                isOk = Load(&obj[i].WidthTiles, 1, serializer) && isOk;
                isOk = Load(&obj[i].HeightTiles, 1, serializer) && isOk;
                isOk = Load(&obj[i].AvailableCardCount, 1, serializer) && isOk;
                isOk = Load(obj[i].AvailableCards, 16, serializer) && isOk;
                isOk = Load(obj[i].PlacedCards, 256, serializer) && isOk;
                isOk = Load(obj[i].BackgroundTiles, 1024, serializer) && isOk;
                isOk = Load(&obj[i].GoalPositionCount, 1, serializer) && isOk;
                isOk = Load(obj[i].GoalPositions, 16, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "ID") == 0 && bx::strCmp(memberTypeName, "uint16_t") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, ID);
            }
            if (bx::strCmp(memberName, "PuzzleName") == 0 && bx::strCmp(memberTypeName, "char") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, PuzzleName);
            }
            if (bx::strCmp(memberName, "WidthTiles") == 0 && bx::strCmp(memberTypeName, "uint8_t") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, WidthTiles);
            }
            if (bx::strCmp(memberName, "HeightTiles") == 0 && bx::strCmp(memberTypeName, "uint8_t") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, HeightTiles);
            }
            if (bx::strCmp(memberName, "AvailableCardCount") == 0 && bx::strCmp(memberTypeName, "uint32_t") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, AvailableCardCount);
            }
            if (bx::strCmp(memberName, "AvailableCards") == 0 && bx::strCmp(memberTypeName, "PuzzleCardStack") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, AvailableCards);
            }
            if (bx::strCmp(memberName, "PlacedCards") == 0 && bx::strCmp(memberTypeName, "PlacedPuzzleCard") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, PlacedCards);
            }
            if (bx::strCmp(memberName, "BackgroundTiles") == 0 && bx::strCmp(memberTypeName, "PuzzleElementType") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, BackgroundTiles);
            }
            if (bx::strCmp(memberName, "GoalPositionCount") == 0 && bx::strCmp(memberTypeName, "uint32_t") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, GoalPositionCount);
            }
            if (bx::strCmp(memberName, "GoalPositions") == 0 && bx::strCmp(memberTypeName, "PuzPos") == 0)
            {
                WriteDestinations[i] = offsetof(PuzzleData, GoalPositions);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "ID") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint16_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "PuzzleName") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<char*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 64, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "WidthTiles") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint8_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "HeightTiles") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint8_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "AvailableCardCount") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint32_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "AvailableCards") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<PuzzleCardStack*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 16, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "PlacedCards") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<PlacedPuzzleCard*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 256, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "BackgroundTiles") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<PuzzleElementType::Enum*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1024, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "GoalPositionCount") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<uint32_t*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "GoalPositions") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<PuzPos*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 16, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
    bool Save(const SavedEntityRenderData* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            isOk = Save(&obj[i].BaseColor, 1, serializer) && isOk;
            isOk = Save(&obj[i].HighlightColor, 1, serializer) && isOk;
            isOk = Save(&obj[i].TF, 1, serializer) && isOk;
            isOk = Save(&obj[i].Material, 1, serializer) && isOk;
            isOk = Save(&obj[i].Texture, 1, serializer) && isOk;
            isOk = Save(&obj[i].Model, 1, serializer) && isOk;
            isOk = Save(&obj[i].Visible, 1, serializer) && isOk;
        }
        return isOk;
    }
    bool Load(SavedEntityRenderData* obj, uint32_t count, Deserializer& serializer)
    {
        const char* typeName = Meta::Metadata.TypeDefinitions[SavedEntityRenderData::TypeIdx].Name;

        // Quick match
        int32_t matchedHashIdx =
            serializer.TypeBuf.FindHash(Meta::Metadata.TypeDefinitions[SavedEntityRenderData::TypeIdx].Hash);
        if (matchedHashIdx >= 0)
        {
            assert(bx::strCmp(serializer.TypeBuf.Defs[matchedHashIdx].Name, typeName) == 0);
            bool isOk = true;
            for (uint32_t i = 0; i < count; ++i)
            {
                isOk = Load(&obj[i].BaseColor, 1, serializer) && isOk;
                isOk = Load(&obj[i].HighlightColor, 1, serializer) && isOk;
                isOk = Load(&obj[i].TF, 1, serializer) && isOk;
                isOk = Load(&obj[i].Material, 1, serializer) && isOk;
                isOk = Load(&obj[i].Texture, 1, serializer) && isOk;
                isOk = Load(&obj[i].Model, 1, serializer) && isOk;
                isOk = Load(&obj[i].Visible, 1, serializer) && isOk;
            }
            // if we're not ok here, something went really wrong
            assert(isOk);
            return isOk;
        }

        // Failed to resolve hash, the type definition chaned since the file was saved! try to match by name.
        int32_t nameMatchIdx = serializer.TypeBuf.FindDefByName(typeName);
        if (nameMatchIdx < 0)
        {
            // Name match failed, caller has to handle this and potentially skip some bytes
            return false;
        }

        // Successfully matched name, but we need to follow the definition of the file now!
        const Meta::TypeDef& matchedDef = serializer.TypeBuf.Defs[nameMatchIdx];

        // Figure out new member mapping
        uint64_t WriteDestinations[64];
        for (int32_t i = 0; i < BX_COUNTOF(WriteDestinations); ++i)
        {
            WriteDestinations[i] = UINT64_MAX;
        }
        for (uint32_t i = 0; i < matchedDef.ChildCount; ++i)
        {
            const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[i].Offset], matchedDef.MemberNameIndices[i].Size};
            const char* memberTypeName = serializer.TypeBuf.Defs[matchedDef.ChildIndices[i]].Name;
            if (bx::strCmp(memberName, "BaseColor") == 0 && bx::strCmp(memberTypeName, "Vec4") == 0)
            {
                WriteDestinations[i] = offsetof(SavedEntityRenderData, BaseColor);
            }
            if (bx::strCmp(memberName, "HighlightColor") == 0 && bx::strCmp(memberTypeName, "Vec4") == 0)
            {
                WriteDestinations[i] = offsetof(SavedEntityRenderData, HighlightColor);
            }
            if (bx::strCmp(memberName, "TF") == 0 && bx::strCmp(memberTypeName, "Transform") == 0)
            {
                WriteDestinations[i] = offsetof(SavedEntityRenderData, TF);
            }
            if (bx::strCmp(memberName, "Material") == 0 && bx::strCmp(memberTypeName, "EMaterial") == 0)
            {
                WriteDestinations[i] = offsetof(SavedEntityRenderData, Material);
            }
            if (bx::strCmp(memberName, "Texture") == 0 && bx::strCmp(memberTypeName, "TextureHandle") == 0)
            {
                WriteDestinations[i] = offsetof(SavedEntityRenderData, Texture);
            }
            if (bx::strCmp(memberName, "Model") == 0 && bx::strCmp(memberTypeName, "ModelHandle") == 0)
            {
                WriteDestinations[i] = offsetof(SavedEntityRenderData, Model);
            }
            if (bx::strCmp(memberName, "Visible") == 0 && bx::strCmp(memberTypeName, "bool") == 0)
            {
                WriteDestinations[i] = offsetof(SavedEntityRenderData, Visible);
            }
        }

        // Start reading in file order, skipping things that we don't know by name and type
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
            uint8_t* objBasePtr = reinterpret_cast<uint8_t*>(&obj[i]);

            for (uint32_t j = 0; j < matchedDef.ChildCount; ++j)
            {
                const Meta::TypeDef& childDef = serializer.TypeBuf.Defs[matchedDef.ChildIndices[j]];
                const bx::StringView memberName = {&serializer.MemberNameBuf[matchedDef.MemberNameIndices[j].Offset], matchedDef.MemberNameIndices[j].Size};
                if (WriteDestinations[j] == UINT64_MAX)
                {
                    // Unknown member name or type changed
                    serializer.Skip(childDef.Size);
                    continue;
                }

                if (bx::strCmp(memberName, "BaseColor") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Vec4*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "HighlightColor") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Vec4*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "TF") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<Transform*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Material") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<EMaterial::Enum*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Texture") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<TextureHandle*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Model") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<ModelHandle*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                if (bx::strCmp(memberName, "Visible") == 0)
                {
                    auto* fieldPtr = reinterpret_cast<bool*>(objBasePtr + WriteDestinations[j]);
                    isOk = Load(fieldPtr, 1, serializer) && isOk;
                    continue;
                }
                assert(false);
            }
        }
        assert(isOk);
        return isOk;
    }
}
