How to combine templates and polymorphism?
JakkuSakura opened this issue · comments
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);
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);
};
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,
- I could have vtables, which is vital in some cases
- 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
- I could have name of type, in const char * format
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 *>
?
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
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
}
}]
}
Finally I combined templates and polymorphism with user-friendly type names by doing these steps.
- Define a basic class with vtable.
- 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)
- Use macro to partially specialize the templated class to generate user-friendly type names. Unwrap several macros manually to set up type names.
- Define
InlinedSerializer<T>
andInlinedDeSerializer<T>
for POD class. - Define
SerializerForBlock<TraitType::Pointer, Wrapper<T>>
andDeSerializationForBlock<TraitType::Pointer, Wrapper<T>>
to callInlinedSerializer<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();
}
};
}
}
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.