Compile Time List?

Started by
4 comments, last by Tocs1001 12 years, 1 month ago
I was working on making it easier to define objects sent across the network for use in a multiplayer game. However this is more a general C++ question than game programming question.

So I have "Attributes" which are basically just a class containing information that needs to be synchronized across the network.

A "Synchronizable" has a bunch of attributes, and the attributes and read and written from packets and neccessary. Modifying an attribute flags an object as dirty and the update is sent.

The trouble is attributes are known at compile time since they are just member variables, but I end up using runtime to build a list of attributes to perform read and write functions.

For example:

class Foo : public Synchronizable
{
Attribute <float> Number; //Compile time woo!
Attribute <Vector3> Position;
Attribute <int> Count;
CustomAttrib WeirdAttrib;
};


Currently the only way I know the handle calling the appropriate Read () / Write () would be to have a std::vector / std::list and in the constructor of Foo add these to the list. Which is great, but it wastes memory on a per object basis.

OR,

write a specific Read () / Write () where I manually call each one's Read () / Write ()

I was hoping there would be some template solution, but the only one I can picture looks something like this. NOTE: I didn't even compile this, its a sketch and probably presents enormous issues with polymorphic objects...

template <int AttrOffset, class List>
class AttrList
{
public:
static void Read (Synchronizable *obj)
{
reinterperet_cast <IAttribute *>(static_cast <void *> (obj) + Attr)->Read ();
List::Read ();
}
};
template <int AttrOffset>
class AttrList
{
public:
static void Read (Synchronizable *obj)
{
reinterperet_cast <IAttribute *>(static_cast <void *> (obj) + Attr)->Read ();
}
};

///Usage
Attribute <int> A,B,C;
AttrList <offsetof (Foo,A),AttrList <offsetof (Foo,B), AttrList <offsetof (Foo,A)>>>::Read (this);


This horrifying bit of code is probably the same length to write as a hand made Read () and much more convoluted and complicated.

This isn't the first time I've encountered the need for this sort of data structure, so I was hoping someone would know an elegant solution to keep the object's source nice and clean.

Further more it would be nice to preserve the concept of inheritance. Its nice to define attributes at different levels.

So for instance, an Entity base class could define attributes for position and direction. Then derive Entity with Monster and supply more attributes for things like Health and Level.
Advertisement
C++ really isn't well-suited to this kind of automatic code generation. In C# for instance you have reflection or automatic serialization via DataContract, which is a very nice feature, but requires a lot of language and compiler infrastructure that C++ lacks. You could potentially store a list of attributes in a boost::fusion::vector but even then you're looking at a lot of boilerplate code and some truly hideous compiler errors when (not if) something breaks.

Honestly, you might get the most mileage out of X-macros or some similar solution.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Template meta-programming and/or abusing inheritance would likely get you there, but those kinds of solutions are going to be convoluted. If you were to go down that route, I'd recommend using a meta-programming library rather than rolling your own.

As Apoch also said, macros, while generally evil, have traditionally been used to solve similar problems.

You might also simplify the problem by introducing aggregation -- in other words, Inherit from synchronizable, but also have an internal (likely template) class instance that collects together the synchronizable attributes.

throw table_exception("(? ???)? ? ???");

Agreeing with the existing sentiments, I'd imagine that any C++-only solution is going to be something of a monstrosity.

I haven't spent much time thinking about this, but my first thought would be to do something like:


// Serialization.h

#define DECLARE_SERIALIZABLE(type) \
struct type; \
\
bool Read(DataSource &source, type &obj); \
bool Write(DataSink &sink, type &obj); \
\
struct type


Then you'd declare each serializable type as follows:


// Foo.h

#include <Serialization.h>

DECLARE_SERIALIZABLE(Foo)
{
Attribute<float> attrib1;
Attribute<int> attrib2;
// etc
};


A Python script (or a script in your preferred 'scripting language') would be run as part of the build in order to generate source files containing the definitions of the Read() and Write() functions. The script only needs to do a minimal amount of parsing, starting at points where it finds 'DECLARE_SERIALIZABLE' tokens.

You could have the script generate the headers themselves from some kind of input file, too, but I think defining the serializable structures in C++ would make things easier in the long run and reduce overall redundancy.

EDIT: perhaps you could even drop the "Attribute<>" wrappers with a little more work...
I asked myself essentially this same question about 13 years ago, and have yet to see a really good answer.

I've looked at mining debug data - generated by the compiler for the debugger - to implement reflection within C++ for cases like this. I've looked at meta-tagging member variables and processing the code with a tool which generates serialzation code. I've looked at building objects dynamically at run time rather than using member variables. I don't like any of them.

Hand writting serialization has obvious maintenance issues, which would theoretically become prohibitive when the number of object types and attributes becomes very large. But in practice, it's never become unwieldly enough to justify committing to one of the other approaces on projects I've worked on.
I hate to bump this, but I wanted to post the resolution I came to.

I decided to look down the road of macros to generate my read function. Which turned out to be significantly more tricky than I anticipated.

I wanted syntax like...

class Foo : Synchronizable
{
Attribute <float> Value;
Attribute <Vector2> Position;
Attribute <int> Count;
public:
Attributes (Value,Position,Count);
};


Which the "Attributes" macro, expands to..

void Read () { Value.Read (); Position.Read (); Count.Read (); }

This required using variable arguments for a macro and some shenanigans to iterate through them, that I pulled from a macro implementation of __VA_NARG__. Which incidentally didn't work in VS2010... After fixing that I found http://stackoverflow...variadic-macros which also didn't work in VS2010 without improvement.

It's rather large since I wanted support for 32 attributes, it still requires more work to handle calling the base class's Read (). But I wanted to post it in case anyone found it worth while.

#pragma once
#define Attributes(...) \
void Read ()\
{\
ITERATE_LIST(ATTRIBUTE,__VA_ARGS__)\
}\
#define ATTRIBUTE(X) X.Read ();
#define STRINGIFY(X) STRINGIFY_(X)
#define STRINGIFY_(X) STRINGIFY__(X)
#define STRINGIFY__(X) #X
#define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2) arg1##arg2
#define EXPAND(X) EXPAND_(X)
#define EXPAND_(X) EXPAND__(X)
#define EXPAND__(X) X

#define SYNC_NARG(...)\
EXPAND (SYNC_ARG_N(__VA_ARGS__,31,30,29,28,27,26,25,24,23,22,21,19,18,17,16,15,14,113,12,11,10,9,8,7,6,5,4,3,2,1,0))
#define SYNC_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,N,...) N
#define SYNC_RSEQ_N 32,31,30,29,28,27,26,25,24,23,22,21,19,18,17,16,15,14,113,12,11,10,9,8,7,6,5,4,3,2,1,0
#define ITERATE_LIST(macro, ...) ITERATE_LIST__(SYNC_NARG(element,__VA_ARGS__), macro,__VA_ARGS__)
#define ITERATE_LIST__(N,macro, ...) EXPAND(CONCATENATE(ITERATE_LIST_,N)(macro,__VA_ARGS__))
#define ITERATE_LIST_1(macro, element) macro(element)
#define ITERATE_LIST_2(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_1(macro, __VA_ARGS__))
#define ITERATE_LIST_3(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_2(macro, __VA_ARGS__))
#define ITERATE_LIST_4(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_3(macro, __VA_ARGS__))
#define ITERATE_LIST_5(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_4(macro, __VA_ARGS__))
#define ITERATE_LIST_6(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_5(macro, __VA_ARGS__))
#define ITERATE_LIST_7(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_6(macro, __VA_ARGS__))
#define ITERATE_LIST_8(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_7(macro, __VA_ARGS__))
#define ITERATE_LIST_9(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_8(macro, __VA_ARGS__))
#define ITERATE_LIST_10(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_9(macro, __VA_ARGS__))
#define ITERATE_LIST_11(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_10(macro, __VA_ARGS__))
#define ITERATE_LIST_12(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_11(macro, __VA_ARGS__))
#define ITERATE_LIST_13(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_12(macro, __VA_ARGS__))
#define ITERATE_LIST_14(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_13(macro, __VA_ARGS__))
#define ITERATE_LIST_15(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_14(macro, __VA_ARGS__))
#define ITERATE_LIST_16(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_15(macro, __VA_ARGS__))
#define ITERATE_LIST_17(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_16(macro, __VA_ARGS__))
#define ITERATE_LIST_18(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_17(macro, __VA_ARGS__))
#define ITERATE_LIST_19(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_18(macro, __VA_ARGS__))
#define ITERATE_LIST_20(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_19(macro, __VA_ARGS__))
#define ITERATE_LIST_21(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_20(macro, __VA_ARGS__))
#define ITERATE_LIST_22(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_21(macro, __VA_ARGS__))
#define ITERATE_LIST_23(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_22(macro, __VA_ARGS__))
#define ITERATE_LIST_24(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_23(macro, __VA_ARGS__))
#define ITERATE_LIST_25(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_24(macro, __VA_ARGS__))
#define ITERATE_LIST_26(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_25(macro, __VA_ARGS__))
#define ITERATE_LIST_27(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_26(macro, __VA_ARGS__))
#define ITERATE_LIST_28(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_27(macro, __VA_ARGS__))
#define ITERATE_LIST_29(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_28(macro, __VA_ARGS__))
#define ITERATE_LIST_30(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_29(macro, __VA_ARGS__))
#define ITERATE_LIST_31(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_30(macro, __VA_ARGS__))
#define ITERATE_LIST_32(macro, element, ...) \
macro (element) \
EXPAND(ITERATE_LIST_31(macro, __VA_ARGS__))

This topic is closed to new replies.

Advertisement