Closed JakkuSakura closed 2 months 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);
};
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
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
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.
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<WrapperInlinedSerializer<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();
}
};
}
}
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.
Brew build in progress https://github.com/Homebrew/homebrew-core/pull/182823
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.