Loki-Astari / ThorsMongo

C++ MongoDB API and BSON/JSON Serialization library
GNU General Public License v3.0
316 stars 71 forks source link

How to combine templates and polymorphism? #61

Closed JakkuSakura closed 2 months ago

JakkuSakura commented 4 years ago

I was trying to make a game using ECS framework. It's better to use POD as components, in order to improve performance and increase stability of the game. So I decided to use some wrapper to provide virtual table and to keep my components intact. However I got some errors. I'm looking for a way to print my wrapped POD, as well as to scan POD using the library's methods.

In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:425,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp: In instantiation of ‘class ThorsAnvil::Serialize::DeSerializeMember<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:508:73:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanEachMember(const string&, T&, const Members&, std::index_sequence<_Idx ...>&) [with T = TerrainData; Members = std::tuple<std::pair<const char*, TerrainType TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*> >; long unsigned int ...Seq = {0, 1, 2, 3, 4}; std::string = std::__cxx11::basic_string<char>; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1, 2, 3, 4>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:515:95:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanMembers(const string&, T&, const std::tuple<_Elements ...>&) [with T = TerrainData; Members = {std::pair<const char*, TerrainType TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>}; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:531:17:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanObjectMembers(const I&, T&) [with T = TerrainData; I = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:196:21:   required from ‘void ThorsAnvil::Serialize::DeSerializationForBlock<traitType, T>::scanObject(T&) [with ThorsAnvil::Serialize::TraitType traitType = ThorsAnvil::Serialize::TraitType::Map; T = TerrainData]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:541:9:   required from ‘void ThorsAnvil::Serialize::DeSerializer::parse(T&) [with T = TerrainData]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:456:9:   [ skipping 3 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:515:95:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanMembers(const string&, T&, const std::tuple<_Elements ...>&) [with T = Wrapper<TerrainData>; Members = {std::pair<const char*, TerrainData Wrapper<TerrainData>::*>}; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:531:17:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanObjectMembers(const I&, T&) [with T = Wrapper<TerrainData>; I = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:196:21:   required from ‘void ThorsAnvil::Serialize::DeSerializationForBlock<traitType, T>::scanObject(T&) [with ThorsAnvil::Serialize::TraitType traitType = ThorsAnvil::Serialize::TraitType::Parent; T = Wrapper<TerrainData>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:345:5:   required from ‘void ThorsAnvil::Serialize::parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&, T&) [with T = Wrapper<TerrainData>]’
/home/jack/IdeaProjects/Escape/src/serialization.cpp:68:5:   required from ‘void Wrapper<T>::parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&) [with T = TerrainData]’
/home/jack/IdeaProjects/Escape/src/serialization.cpp:68:5:   required from here
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:484:7: error: invalid use of incomplete type ‘struct ThorsAnvil::Serialize::TraitsInfo<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’
  484 | class DeSerializeMember: public TraitsInfo<T, M, Type>::DeSerializeMember
      |       ^~~~~~~~~~~~~~~~~
In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:301:8: note: declaration of ‘struct ThorsAnvil::Serialize::TraitsInfo<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’
  301 | struct TraitsInfo;
      |        ^~~~~~~~~~
In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:425,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:486:11: error: invalid use of incomplete type ‘struct ThorsAnvil::Serialize::TraitsInfo<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’
  486 |     using Parent = typename TraitsInfo<T, M, Type>::DeSerializeMember;
      |           ^~~~~~
In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:301:8: note: declaration of ‘struct ThorsAnvil::Serialize::TraitsInfo<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’
  301 | struct TraitsInfo;
      |        ^~~~~~~~~~
In file included from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.h:425,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonParser.h:21,
                 from /home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/JsonThor.h:15,
                 from /home/jack/IdeaProjects/Escape/src/serialization.cpp:19:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:488:23: error: using-declaration for non-member at class scope
  488 |         using Parent::Parent;
      |                       ^~~~~~
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp: In instantiation of ‘bool ThorsAnvil::Serialize::DeSerializer::scanEachMember(const string&, T&, const Members&, std::index_sequence<_Idx ...>&) [with T = TerrainData; Members = std::tuple<std::pair<const char*, TerrainType TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*> >; long unsigned int ...Seq = {0, 1, 2, 3, 4}; std::string = std::__cxx11::basic_string<char>; std::index_sequence<_Idx ...> = std::integer_sequence<long unsigned int, 0, 1, 2, 3, 4>]’:
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:515:95:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanMembers(const string&, T&, const std::tuple<_Elements ...>&) [with T = TerrainData; Members = {std::pair<const char*, TerrainType TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>, std::pair<const char*, float TerrainData::*>}; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:531:17:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanObjectMembers(const I&, T&) [with T = TerrainData; I = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:196:21:   required from ‘void ThorsAnvil::Serialize::DeSerializationForBlock<traitType, T>::scanObject(T&) [with ThorsAnvil::Serialize::TraitType traitType = ThorsAnvil::Serialize::TraitType::Map; T = TerrainData]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:541:9:   required from ‘void ThorsAnvil::Serialize::DeSerializer::parse(T&) [with T = TerrainData]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:456:9:   required from ‘ThorsAnvil::Serialize::DeSerializeMemberContainer<T, M>::DeSerializeMemberContainer(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&, const string&, T&, const std::pair<const char*, M T::*>&) [with T = Wrapper<TerrainData>; M = TerrainData; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:488:23:   [ skipping 2 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:515:95:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanMembers(const string&, T&, const std::tuple<_Elements ...>&) [with T = Wrapper<TerrainData>; Members = {std::pair<const char*, TerrainData Wrapper<TerrainData>::*>}; std::string = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:531:17:   required from ‘bool ThorsAnvil::Serialize::DeSerializer::scanObjectMembers(const I&, T&) [with T = Wrapper<TerrainData>; I = std::__cxx11::basic_string<char>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:196:21:   required from ‘void ThorsAnvil::Serialize::DeSerializationForBlock<traitType, T>::scanObject(T&) [with ThorsAnvil::Serialize::TraitType traitType = ThorsAnvil::Serialize::TraitType::Parent; T = Wrapper<TerrainData>]’
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:345:5:   required from ‘void ThorsAnvil::Serialize::parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&, T&) [with T = Wrapper<TerrainData>]’
/home/jack/IdeaProjects/Escape/src/serialization.cpp:68:5:   required from ‘void Wrapper<T>::parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer&, ThorsAnvil::Serialize::ParserInterface&) [with T = TerrainData]’
/home/jack/IdeaProjects/Escape/src/serialization.cpp:68:5:   required from here
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:508:33: error: invalid static_cast from type ‘ThorsAnvil::Serialize::DeSerializeMember<TerrainData, TerrainType, ThorsAnvil::Serialize::TraitType::Invalid>’ to type ‘bool’
  508 |     CheckMembers memberCheck = {static_cast<bool>(make_DeSerializeMember(*this, parser, key, object, std::get<Seq>(member)))...};
      |                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/jack/IdeaProjects/Escape/vendor/ThorsSerializer/ThorSerialize/Serialize.tpp:508:18: error: could not convert ‘{<expression error>}’ from ‘<brace-enclosed initializer list>’ to ‘CheckMembers’ {aka ‘std::initializer_list<bool>’}
  508 |     CheckMembers memberCheck = {static_cast<bool>(make_DeSerializeMember(*this, parser, key, object, std::get<Seq>(member)))...};
      |                  ^~~~~~~~~~~
      |                  |
      |                  <brace-enclosed initializer list>

enum class TerrainType {
    BOX,
    CIRCLE
};

struct TerrainData {
    TerrainType type;
    float argument_1;
    float argument_2;
    float argument_3;
    float argument_4;
};

ThorsAnvil_MakeTrait(TerrainData, type, argument_1, argument_2, argument_3, argument_4);

struct WrapperBase {
    virtual ~WrapperBase() {};

    virtual void *getData() { return 0; };

    ThorsAnvil_PolyMorphicSerializer(WrapperBase);
};

template<typename T>
struct Wrapper : public WrapperBase {
    Wrapper() : data() {}

    Wrapper(const Wrapper<T> &wrapper) : data(wrapper.data) {}
    Wrapper(const T &d) : data(d) {}
    Wrapper(T &&d) : data(std::move(d)) {}
    T data;
    Wrapper &operator=(const Wrapper &wrapper) {
        data = wrapper.data;
        return *this;
    }

    void *getData() {
        return &data;
    }
// The next line will not let the compiler go
    ThorsAnvil_PolyMorphicSerializer(Wrapper<T>);

};
ThorsAnvil_MakeTrait(WrapperBase);
ThorsAnvil_Template_ExpandTrait(1, WrapperBase, Wrapper , data);
Loki-Astari commented 4 years ago

Currently not in a place to diagnose. Will look closer tonight.

BUT: I do see ThorsAnvil::Serialize::TraitType::Invalid in the error messages. Which means one of the types has not been declared as serializable.

// You may need to add this.
ThorsAnvil_MakeEnum(TerrainType, BOX, CIRCLE);

But I am still not sure that would work. The Polymorphic code would not be able to unwrap the T in Wrapper<T> to plant the correct code and would result in the code generating:

    "__type": "Wrapper<T>"

I might be able to fix that given some time.

In the meantime you may need to do:

class WrapperInt: public Wrapper<int>
{
    ThorsAnvil_PolyMorphicSerializer(WrapperInt);
};
JakkuSakura commented 4 years ago

I fixed it by adding MakeEnum(...). It's quite strange because it was working on boost serialization. Unspecified enum/enum class will be simply numbers. I did some tricks with macros to generate names

template <typename T>
struct Wrapper
{
};
#define MAKE_WRAPPER(T)                                                                        \
    template <>                                                                                \
    struct Wrapper<T> : public WrapperBase                                                     \
    {                                                                                          \
        Wrapper() : data() {}                                                                  \
        Wrapper(const Wrapper<T> &wrapper) : data(wrapper.data) {}                             \
        Wrapper(const T &d) : data(d) {}                                                       \
        Wrapper(T &&d) : data(std::move(d)) {}                                                 \
        T data;                                                                                \
        Wrapper &operator=(const Wrapper &wrapper)                                             \
        {                                                                                      \
            data = wrapper.data;                                                               \
            return *this;                                                                      \
        }                                                                                      \
        void *getData()                                                                        \
        {                                                                                      \
            return &data;                                                                      \
        }                                                                                      \
        virtual void printPolyMorphicObject(ThorsAnvil::Serialize::Serializer &parent,         \
                                            ThorsAnvil::Serialize::PrinterInterface &printer)  \
        {                                                                                      \
            ThorsAnvil::Serialize::printPolyMorphicObject<Wrapper<T>>(parent, printer, *this); \
        }                                                                                      \
        virtual void parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer &parent,       \
                                            ThorsAnvil::Serialize::ParserInterface &parser)    \
        {                                                                                      \
            ThorsAnvil::Serialize::parsePolyMorphicObject<Wrapper<T>>(parent, parser, *this);  \
        }                                                                                      \
        static constexpr char const *polyMorphicSerializerName() { return #T; };               \
    }

ThorsAnvil_MakeTrait(WrapperBase);
ThorsAnvil_Template_ExpandTrait(1, WrapperBase, Wrapper, data);
FOREACH_COMPONENT_TYPE(MAKE_WRAPPER);

where

#define FOREACH_COMPONENT_TYPE(func) \
    func(Position);          \
    func(Name);              \
    ...

By doing so, 1) I could have vtables, which is vital in some cases 2) I could have template arguments outside the wrapper, which is useful to obtain the underlaying data type, i.e. , typeid(Wrapper) == typeid(some_pointer). Moreover, typeid could be obtained at both runtime and compiling time 3) I could have name of type, in const char format

JakkuSakura commented 4 years ago

Now I have the result of exportJSON(new Wrapper(Position(42, 6)));

 { 
    "__type": "Position", 
    "data": 
    { 
        "x": 42, 
        "y": 6
    },
},

But I would like it to be

{ 
        "__type": "Position", 
        "x": 42, 
        "y": 6
}

or preferably

"Position" : {
        "x": 42, 
        "y": 6
}

since any component will be not appear multiple times in one entity. Is there a way to hack the procedure of containers like std::vector<WrapperBase *>?

JakkuSakura commented 4 years ago

Another problem while parsing json file:

static constexpr char const *polyMorphicSerializerName() { return "Wrapped"#T; };  

terminate called after throwing an instance of 'std::runtime_error'
  what():  ThorsAnvil::Serialize::PolyMorphicRegistry::getNamedTypeConvertedTo: Non polymorphic type Wrapper<TimeServerInfo>

Here my TimeServerInfo is just a POD containing one integer, and Wrapper is obviously my wrapper and the name I assigned. It seems that my name doens't match the typename name I typed in MakeTrait or TemplateMakeTrait

JakkuSakura commented 4 years ago

Problem partially solved: I used macro in argument of ExpandTrait, instead of TemplateExpandTrait. Now I can serialize and deserialize. One problem remains: I have to implement my own version of ThorsAnvil_ExpandTrait_Base() to specify arbitrary register type name, instead of Wrapper<my_inner_type_name>


struct WrapperBase {
    virtual ~WrapperBase() {};

    virtual void *getData() { return 0; };

    ThorsAnvil_PolyMorphicSerializer(WrapperBase);
};

template<typename T>
struct Wrapper {
};
#define MAKE_WRAPPER(T, name)                                                                        \
    template <>                                                                                \
    struct Wrapper<T> : public WrapperBase                                                     \
    {                                                                                          \
        Wrapper() : data() {}                                                                  \
        Wrapper(const Wrapper<T> &wrapper) : data(wrapper.data) {}                             \
        Wrapper(const T &d) : data(d) {}                                                       \
        Wrapper(T &&d) : data(std::move(d)) {}                                                 \
        T data;                                                                                \
        Wrapper &operator=(const Wrapper &wrapper)                                             \
        {                                                                                      \
            data = wrapper.data;                                                               \
            return *this;                                                                      \
        }                                                                                      \
        void *getData()                                                                        \
        {                                                                                      \
            return &data;                                                                      \
        }                                                                                      \
        virtual void printPolyMorphicObject(ThorsAnvil::Serialize::Serializer &parent,         \
                                            ThorsAnvil::Serialize::PrinterInterface &printer)  \
        {                                                                                      \
            ThorsAnvil::Serialize::printPolyMorphicObject<Wrapper<T>>(parent, printer, *this); \
        }                                                                                      \
        virtual void parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer &parent,       \
                                            ThorsAnvil::Serialize::ParserInterface &parser)    \
        {                                                                                      \
            ThorsAnvil::Serialize::parsePolyMorphicObject<Wrapper<T>>(parent, parser, *this);  \
        }                                                                                      \
        static constexpr char const *polyMorphicSerializerName() { return #name; };            \
    };                                                                                         \
    ThorsAnvil_ExpandTrait(WrapperBase, Wrapper<T>, data);

ThorsAnvil_MakeTrait(WrapperBase);
#define MAKE_WRAPPER2(type) MAKE_WRAPPER(type, Wrapper<type>)
FOREACH_COMPONENT_TYPE(MAKE_WRAPPER2);

    { 
        "0": [ 
            { 
                "__type": "Wrapper<TimeServerInfo>", 
                "data": 
                { 
                    "tick": 87
                }
            }], 
        "1": [ 
            { 
                "__type": "Wrapper<Position>", 
                "data": 
                { 
                    "x": -13.5215, 
                    "y": 0.0
                }
            }, 
            { 
                "__type": "Wrapper<Name>", 
                "data": 
                { 
                    "name": "agent"
                }
            }, 
            { 
                "__type": "Wrapper<Velocity>", 
                "data": 
                { 
                    "x": -0.895253, 
                    "y": 0.0
                }
            }, 
            { 
                "__type": "Wrapper<Health>", 
                "data": 
                { 
                    "max_health": 100
                }
            }, 
            { 
                "__type": "Wrapper<Weapon>", 
                "data": 
                { 
                    "weapon": "HANDGUN", 
                    "last": 0.0, 
                    "next": 0.0
                }
            }, 
            { 
                "__type": "Wrapper<Hitbox>", 
                "data": 
                { 
                    "radius": 1
                }
            }, 
            { 
                "__type": "Wrapper<AgentData>", 
                "data": 
                { 
                    "id": 1, 
                    "player": 1
                }
            }], 
        "2": [ 
            { 
                "__type": "Wrapper<Position>", 
                "data": 
                { 
                    "x": 5, 
                    "y": 20
                }
            }, 
            { 
                "__type": "Wrapper<Name>", 
                "data": 
                { 
                    "name": "agent"
                }
            }, 
            { 
                "__type": "Wrapper<Velocity>", 
                "data": 
                { 
                    "x": 0.0, 
                    "y": 0.0
                }
            }, 
            { 
                "__type": "Wrapper<Health>", 
                "data": 
                { 
                    "max_health": 100
                }
            }, 
            { 
                "__type": "Wrapper<Weapon>", 
                "data": 
                { 
                    "weapon": "HANDGUN", 
                    "last": 0.0, 
                    "next": 0.0
                }
            }, 
            { 
                "__type": "Wrapper<Hitbox>", 
                "data": 
                { 
                    "radius": 1
                }
            }, 
            { 
                "__type": "Wrapper<AgentData>", 
                "data": 
                { 
                    "id": 2, 
                    "player": 0
                }
            }], 
        "3": [ 
            { 
                "__type": "Wrapper<Position>", 
                "data": 
                { 
                    "x": 0.0, 
                    "y": 20
                }
            }, 
            { 
                "__type": "Wrapper<Name>", 
                "data": 
                { 
                    "name": "agent"
                }
            }, 
            { 
                "__type": "Wrapper<Velocity>", 
                "data": 
                { 
                    "x": 0.0, 
                    "y": 0.0
                }
            }, 
            { 
                "__type": "Wrapper<Health>", 
                "data": 
                { 
                    "max_health": 100
                }
            }, 
            { 
                "__type": "Wrapper<Weapon>", 
                "data": 
                { 
                    "weapon": "HANDGUN", 
                    "last": 0.0, 
                    "next": 0.0
                }
            }, 
            { 
                "__type": "Wrapper<Hitbox>", 
                "data": 
                { 
                    "radius": 1
                }
            }, 
            { 
                "__type": "Wrapper<AgentData>", 
                "data": 
                { 
                    "id": 3, 
                    "player": 0
                }
            }], 
        "4": [ 
            { 
                "__type": "Wrapper<Position>", 
                "data": 
                { 
                    "x": -5, 
                    "y": 20
                }
            }, 
            { 
                "__type": "Wrapper<Name>", 
                "data": 
                { 
                    "name": "agent"
                }
            }, 
            { 
                "__type": "Wrapper<Velocity>", 
                "data": 
                { 
                    "x": 0.0, 
                    "y": 0.0
                }
            }, 
            { 
                "__type": "Wrapper<Health>", 
                "data": 
                { 
                    "max_health": 100
                }
            }, 
            { 
                "__type": "Wrapper<Weapon>", 
                "data": 
                { 
                    "weapon": "HANDGUN", 
                    "last": 0.0, 
                    "next": 0.0
                }
            }, 
            { 
                "__type": "Wrapper<Hitbox>", 
                "data": 
                { 
                    "radius": 1
                }
            }, 
            { 
                "__type": "Wrapper<AgentData>", 
                "data": 
                { 
                    "id": 4, 
                    "player": 0
                }
            }], 
        "5": [ 
            { 
                "__type": "Wrapper<Position>", 
                "data": 
                { 
                    "x": 0.0, 
                    "y": 10
                }
            }, 
            { 
                "__type": "Wrapper<Name>", 
                "data": 
                { 
                    "name": "box"
                }
            }, 
            { 
                "__type": "Wrapper<Rotation>", 
                "data": 
                { 
                    "radian": 0.523599
                }
            }, 
            { 
                "__type": "Wrapper<TerrainData>", 
                "data": 
                { 
                    "type": "BOX", 
                    "argument_1": 20, 
                    "argument_2": 2, 
                    "argument_3": 0.0, 
                    "argument_4": 0.0
                }
            }], 
        "6": [ 
            { 
                "__type": "Wrapper<Position>", 
                "data": 
                { 
                    "x": 0.0, 
                    "y": 10
                }
            }, 
            { 
                "__type": "Wrapper<Name>", 
                "data": 
                { 
                    "name": "box"
                }
            }, 
            { 
                "__type": "Wrapper<Rotation>", 
                "data": 
                { 
                    "radian": 2.0944
                }
            }, 
            { 
                "__type": "Wrapper<TerrainData>", 
                "data": 
                { 
                    "type": "BOX", 
                    "argument_1": 20, 
                    "argument_2": 2, 
                    "argument_3": 0.0, 
                    "argument_4": 0.0
                }
            }]
    }
JakkuSakura commented 4 years ago

Finally I combined templates and polymorphism with user-friendly type names by doing these steps. 1) Define a basic class with vtable. 2) Define a templated wrapper/smart pointer class(or what ever), derived from basic class. (It's like boost::any, but we need the vtable, not typeid). Define Traits<Wrapper> as pointer manually. This is not needed if you do step 5 in another way) 3) Use macro to partially specialize the templated class to generate user-friendly type names. Unwrap several macros manually to set up type names. 4) Define InlinedSerializer<T> and InlinedDeSerializer<T> for POD class. 5) Define SerializerForBlock<TraitType::Pointer, Wrapper<T>> and DeSerializationForBlock<TraitType::Pointer, Wrapper<T>> to call InlinedSerializer<T>/InlinedDeserializer<T>

That's it. The core step is to use pointers with virtual table to wrap PODs, use macro to generate type names in string format, and specialize [De]Serializers and Inline[De]Serializers. It's quite annoying but the result is very satisfying.


    { 
        "0": [ 
            { 
                "__type": "TimeServerInfo", 
                "tick": 69
            }], 
        "1": [ 
            { 
                "__type": "Position", 
                "x": 0.0, 
                "y": 0.0
            }, 
            { 
                "__type": "Name", 
                "name": "agent"
            }, 
            { 
                "__type": "Velocity", 
                "x": 0.0, 
                "y": 0.0
            }, 
            { 
                "__type": "Health", 
                "max_health": 100
            }, 
            { 
                "__type": "Weapon", 
                "weapon": "HANDGUN", 
                "last": 0.0, 
                "next": 0.0
            }, 
            { 
                "__type": "Hitbox", 
                "radius": 1
            }, 
            { 
                "__type": "AgentData", 
                "id": 1, 
                "player": 1
            }], 
        "2": [ 
            { 
                "__type": "Position", 
                "x": 5, 
                "y": 20
            }, 
            { 
                "__type": "Name", 
                "name": "agent"
            }, 
            { 
                "__type": "Velocity", 
                "x": 0.0, 
                "y": 0.0
            }, 
            { 
                "__type": "Health", 
                "max_health": 100
            }, 
            { 
                "__type": "Weapon", 
                "weapon": "HANDGUN", 
                "last": 0.0, 
                "next": 0.0
            }, 
            { 
                "__type": "Hitbox", 
                "radius": 1
            }, 
            { 
                "__type": "AgentData", 
                "id": 2, 
                "player": 0
            }], 
        "3": [ 
            { 
                "__type": "Position", 
                "x": 0.0, 
                "y": 20
            }, 
            { 
                "__type": "Name", 
                "name": "agent"
            }, 
            { 
                "__type": "Velocity", 
                "x": 0.0, 
                "y": 0.0
            }, 
            { 
                "__type": "Health", 
                "max_health": 100
            }, 
            { 
                "__type": "Weapon", 
                "weapon": "HANDGUN", 
                "last": 0.0, 
                "next": 0.0
            }, 
            { 
                "__type": "Hitbox", 
                "radius": 1
            }, 
            { 
                "__type": "AgentData", 
                "id": 3, 
                "player": 0
            }], 
        "4": [ 
            { 
                "__type": "Position", 
                "x": -5, 
                "y": 20
            }, 
            { 
                "__type": "Name", 
                "name": "agent"
            }, 
            { 
                "__type": "Velocity", 
                "x": 0.0, 
                "y": 0.0
            }, 
            { 
                "__type": "Health", 
                "max_health": 100
            }, 
            { 
                "__type": "Weapon", 
                "weapon": "HANDGUN", 
                "last": 0.0, 
                "next": 0.0
            }, 
            { 
                "__type": "Hitbox", 
                "radius": 1
            }, 
            { 
                "__type": "AgentData", 
                "id": 4, 
                "player": 0
            }], 
        "5": [ 
            { 
                "__type": "Position", 
                "x": 0.0, 
                "y": 10
            }, 
            { 
                "__type": "Name", 
                "name": "box"
            }, 
            { 
                "__type": "Rotation", 
                "radian": 0.523599
            }, 
            { 
                "__type": "TerrainData", 
                "type": "BOX", 
                "argument_1": 20, 
                "argument_2": 2, 
                "argument_3": 0.0, 
                "argument_4": 0.0
            }],
    }

struct WrapperBase {
    WrapperBase() {};

    virtual ~WrapperBase() {};

    ThorsAnvil_PolyMorphicSerializer(WrapperBase);

};

#define My_RegisterPolyMorphicType(DataType, name)                    \
    namespace ThorsAnvil                                              \
    {                                                                 \
    namespace Serialize                                               \
    {                                                                 \
    namespace                                                         \
    {                                                                 \
    ThorsAnvil_InitPolyMorphicType<DataType> THOR_UNIQUE_NAME(#name); \
    }                                                                 \
    }                                                                 \
    }

template<typename T>
struct Wrapper {
};
#define MAKE_WRAPPER(T, name)                                                                       \
    template <>                                                                                     \
    struct Wrapper<T> : public WrapperBase                                                          \
    {                                                                                               \
        using element_type = T;                                                                     \
        T *pointer;                                                                                 \
        Wrapper() { pointer = nullptr; }                                                            \
        Wrapper(T *p) {pointer = p;}                                                                \
        Wrapper(const T *p) {pointer = const_cast<T *>(p);}                                         \
        Wrapper &operator=(const Wrapper &wrapper)                                                  \
        {                                                                                           \
            pointer = wrapper.pointer;                                                              \
            return *this;                                                                           \
        }                                                                                           \
        Wrapper &operator=(std::nullptr_t nil)                                                      \
        {                                                                                           \
            return *this;                                                                           \
        }                                                                                           \
        T &operator*() const {return *pointer;}                                                     \
        T* operator->(){return pointer;}                                                            \
        virtual void printPolyMorphicObject(ThorsAnvil::Serialize::Serializer &parent,              \
                                            ThorsAnvil::Serialize::PrinterInterface &printer)       \
        {                                                                                           \
            ThorsAnvil::Serialize::printPolyMorphicObject<Wrapper<T>>(parent, printer, *this);      \
        }                                                                                           \
        virtual void parsePolyMorphicObject(ThorsAnvil::Serialize::DeSerializer &parent,            \
                                            ThorsAnvil::Serialize::ParserInterface &parser)         \
        {                                                                                           \
            ThorsAnvil::Serialize::parsePolyMorphicObject<Wrapper<T>>(parent, parser, *this);       \
        }                                                                                           \
        static constexpr char const *polyMorphicSerializerName() { return #name; };                 \
    };                                                                                              \
    My_RegisterPolyMorphicType(Wrapper<T>, name);

ThorsAnvil_MakeTrait(WrapperBase);
#define MAKE_WRAPPER2(type) MAKE_WRAPPER(type, type)

FOREACH_COMPONENT_TYPE(MAKE_WRAPPER2);

namespace ThorsAnvil {
    namespace Serialize {

        template<typename T>
        class InlinedSerializer {
            Serializer &parent;
            PrinterInterface &printer;
            T const &object;
        public:
            InlinedSerializer(Serializer &parent, PrinterInterface &printer, T const &object)
                    : parent(parent), printer(printer), object(object) {
            }

            ~InlinedSerializer() {
            }

            void printMembers() {
                parent.printObjectMembers(object);
            }
        };

        template<typename T>
        class Traits<Wrapper<T>> {
        public:
            static constexpr TraitType type = TraitType::Pointer;

            static Wrapper<T> alloc() { return Wrapper<T>(new T()); }

            static void release(Wrapper<T> &p) {
                delete p.pointer;
                p.pointer = nullptr;
            }
        };

        template<typename T>
        class DeSerializationForBlock<TraitType::Pointer, Wrapper<T>> {
            DeSerializer &parent;
            ParserInterface &parser;
        public:
            DeSerializationForBlock(DeSerializer &parent, ParserInterface &parser)
                    : parent(parent), parser(parser) {}

            void scanObject(Wrapper<T> &object) {
                T *t = new T();
                try {
                    parent.parse(*t);
                } catch (...) {
                    delete t;
                    throw;
                }
                object.pointer = t;
            }
        };

        template<typename T>
        class SerializerForBlock<TraitType::Pointer, Wrapper<T>> {
            Serializer &parent;
            PrinterInterface &printer;
            Wrapper<T> const &object;
        public:
            SerializerForBlock(Serializer &parent, PrinterInterface &printer, Wrapper<T> const &object)
                    : parent(parent), printer(printer), object(object) {
                printer.openMap();
            }

            ~SerializerForBlock() {
                printer.closeMap();
            }

            void printMembers() {
                if (object.pointer == nullptr) {
                    printer.addNull();
                } else {
                    InlinedSerializer inlined(parent, printer, *object);
                    inlined.printMembers();
                }
            }

            void printPolyMorphicMembers(std::string const &type) {
                printer.addKey(printer.config.polymorphicMarker);
                printer.addValue(type);
                printMembers();
            }

        };

    }
}
JakkuSakura commented 4 years ago

Would you like to add the wrapper, or let me do it by craete a PR? Currently it relies on the inner implemtation. Without proper hacking(c++ template system is hackable), it can't be done.

Loki-Astari commented 2 months ago

Sorry it tooks so long. But I now have a fix that that I like.

De-Serializer
Serializer

With updates to the Parser and Printer to keep track of shared pointers that have been serialized.
Parser
Printer

Loki-Astari commented 2 months ago

Tests: https://github.com/Loki-Astari/ThorsSerializer/blob/master/src/Serialize/test/SharedPtrUniqueTest.cpp

Loki-Astari commented 2 months ago

Brew build in progress https://github.com/Homebrew/homebrew-core/pull/182823