[C++] Serialization, how it's done

Started by Blizzard, December 02, 2012, 08:09:33 am

Previous topic - Next topic

Blizzard

December 02, 2012, 08:09:33 am Last Edit: December 02, 2012, 08:34:47 am by Blizzard
Serialization in C++ is quite a problem as there is no reflection. Months ago I was able to come up with a somewhat clean solution for this problem, but I am still not satisfied with the result. I want it to be even better. The solution is currently in form of a library called Lite Serializer. You can get some more info here: http://forum.chaos-project.com/index.php/topic,8708.0.html

Today I will talk a little bit about serialization. I will refer to v1.0 and v1.1 of Lite Serializer as reference for the method that I used to create it and I will talk about how v2.x will be implemented in the future. v2.0 will work in a significantly different way than v1.x so data won't be compatible.




What is serialization? It's the process of transforming binary data into a data stream either for storage and/or for network transmission and it can even be used as a convenient way for making a deep-copy of an object (cloning).

Before I begin, I want to mention that I use a version specification for serialized data as it allows me to add exception cases during serialization and deserialization when reading data from older formats. I am not simply talking about the serializer version, I am also talking about the game or application version as well. I will explain this part a bit more clearly further below when I talk about organization of object data.

Note that a lot of these approaches and problems are not related to C++ serialization directly but serialization in general. There are things that are exclusive to C++, but I will explain them.




Ways to organize serialized objects


There are a few concepts and requirements that are important to mention as they can greatly influence how the code for serialization and deserialization is written as well as complexity and speed of the process.

Direct serialization

The simplest way to serialize data is just to dump everything linearly into a stream. The problem here is that only consecutive blocks can be serialized and referenced or pointers will only be serialized as their current address which won't work during deserialization. In this case cloning an object would actually cause to be a simple shallow-copy as referenced data is not really copied.

Serialization with referenced data

Obviously the previous way to serialize stuff is not good enough for most applications so we need something better. The next requirement is that referenced data is serialized and deserialized properly as well. If an object points to another somewhere in the memory, we will serialize that object directly as well (using the previous method direct serialization). There are also limits here as it's not possible to properly serialize objects that are being pointed to by more than one object (as they will be separate copies) and references back to objects will create an infinite loop during runtime as the code is not aware of already serialized objects. A similar problem is explain in the ARC Data Format specification under "Problem regarding shared data and serialization" which you can read here: http://forum.chaos-project.com/index.php/topic,11920.0.html In the thread, the problem arises when you serialize shared data as you will get two different objects upon deserialization while in this particular case the problem is an inherit part of the implementation. Obviously this method is also limited.

Serialization with an index

When serializing data in such a way that every actual object is given an ID within an index map, each object can be indexed the first time it is serialized and then later only referred with its ID when another object points to it. This approach offers the greatest flexibility as any kind of organized data structure (including multiple objects references and back-references) can be serialized without problems. The trade off in this case is speed and complexity. Implementing such a mechanism is far more complex than a simple method and the speed will suffer often greatly as each time an object is serialized, first the index map has to be checked whether the new object already exists. Regardless of the implementation of the index map (e.g. an array of data, std::vector or similar, a hash map, std::map or similar), there is a limit to how much speed one can save because of the look-up overhead.

Both Lite Serializer and the ARC Data Format use this method. (You can read more under "Implementation Details" in the ARC Data Format Specification: http://forum.chaos-project.com/index.php/topic,11920.0.html )

How the index map is created and saved within the serialized stream is a problem in itself. I, personally, prefer the approach of an implicitly defined index by simply scattering IDs around with the objects. That way I can build the index map during serialization and deserialization without the need to additionally specify the index map or save it with the serialized data. This makes the data a bit safer as well, because implementation details of the serialization method are hidden.




Organizing data within an object


How to serialized objects is only part of the problem. Another important problem is how the data within an object will actually be organized. There are a few ways how this can be done and yet again it comes down to flexibility vs. and speed and simplicity of the code and data. But there is another problem here as well and that is exposure of implementation details (which I mentioned previously in regards to using an index map).

Before I begin here, I first want a special exception of data which are strings.

Strings and serialization

Serializing strings in their natural form automatically make data easier to understand and read. It does not actually expose implementation details, but the data is still visible from the outside which makes it easier to modify and/or hack it outside of the application. Sometimes this does not pose a problem, but in other cases it can have devastating consequences.

To avoid this problem, strings can be encrypted. In this case it is highly recommended that encryption information is not stored with the actual serialized data. While it adds flexibility so that any application reading it can decrypt it, it actually only obfuscates how the strings and modification or hacking can still be done relatively easily. If the encryption data is stored in the application and the application automatically knows how to handle the data, it's a bit safer as application hacking is much more difficult. But this also limits the number and type of applications that actually read the data so yet again it's a trade-off between flexibility and safety.

Plain straight serialization

You can simply go through all the variables an object has an serialize them directly into the stream. This method is the simplest and fastest by far, but the problem here is that data always has to be in the same order and has the same structure. Adding or removing the variable requires you to write additional exceptions to the code in order to make it compatible with previous versions of your application and/or serializer. I mentioned earlier that I serialize the version of the application and this is basically why.
This is a method often used in C++, because data is usually not often required to be that organized or won't change much. Another disadvantage of this method is that it's not automated. You have to know the structure of the data in order to serialize and deserialize it. But this also has the advantage of exposing very few implementation details, because the serialized data does not contain any information about the meaning and context of the serialized data.

Data type information

If you want to index the data that is being stored, it can be read much easier. If you know that a specific piece of data is a stored integer, it may be easier to understand the context and deserialize the object properly. This is usually used in conjunction with the next method as it rarely makes sense to add data type information by itself. Of course this is actually adding implementation details into the data.

Indexed variable names

This approach allows for the greatest flexibility because you store the actual names of the variables (in some form). Adding, removing or even reorganizing variables in an object has little impact as the serializer knows what goes where. This is a great problem in C++ as C++ does not support any reflection which is crucial for this method. If you don't have any meta data about the object structure, it's simply not possible to use this method. There are a few ways this can be handled and some meta data can be added to the objects to help them be serialized.

Note that this approach has also been used in the ARC Data Format.

As of writing of this post, I am planning out a method for Lite Serializer v2.x how to handle this problem. This approach has a disadvantage, because changing variable names will cause data to be lost or mixed up as the meta data would differ from the one contained in the serialized data. I am planning to solve this problem by allowing the programmer to add aliases for data so it's compatible with previous versions. I have not added any code related to this in my example further below.




Lite Serializer Specifics


How Lite Serializer does it

Indexing variable names is a must if you want to use the latter method for serializing data. C++ has no meta data stored about the objects and adding that kind of functionality can be very difficult. Either one has to automate it which adds a lot of complexity to data or write the meta data manually for every object which is tedious and prone to mistakes. Because Lite Serializer v1.x does not use the latter method (it uses plain serialization), you have to manually specify the order in which data is serialized and deserialized. This is a big issue as you have to be careful to always update the order if you change something and you have to make sure that the serialization code and deserialization code match. Even though I have added macros to make things much easier to overview, you still have to specify the order and data type that you are serializing. Lite Serializer v2.x is supposed to fix that problem.

Meta data within an object for v.2x

My current idea for Lite Serializer v2.x is that the manual code for serialization and deserialization is rendered obsolete or at least simplified heavily and that duplicate and data type aware code can be removed. To achieve this, I have to find a convenient way to specify the meta data for the object. I was thinking about being able to "register" object types within the library. This "registering" would call a static method which contains code for generating the meta data for that object, so objects could be serialized easily. The static method declaration and definition should be as simplistic as possible. I am thinking about using a macro that takes care of that and additional macros for defining the meta data.

Spoiler: ShowHide
Code: somewhere in liteser.h

#define LS_DEFINE_META_DATA(dataDefinitions) void _lsRegister() \
   { \
       here_goes_some_code_for_mapping_the_meta_data; \
       dataDefinitions
   }
#define LS_META_VALUE(name, type) ... // values require a type specification
#define LS_META_REFERENCE(name) ...
#define LS_META_OBJECT(name) ...
#define LS_META_SUPERCLASS(superclass) ... // used in cases where we have B that inherits A and A inherits liteser::Serializable


Code: some sort of X.h
class X : public liteser::Serializable
{
...
public:
   LS_DEFINE_META_DATA
   (
       LS_META_VALUE(a, int);
       LS_META_VALUE(b, float);
       LS_META_REFERENCE(y1); /* type not necessary here as Y inherits liteser::Serializable */
       LS_META_OBJECT(y2); /* type not necessary here as Y inherits liteser::Serializable */
   );

protected:
   int a;
   float b;
   Y* y1; // Y is declared somewhere as serializable as well
   Y y2;
...
};


I am trying to simplify this as much as possible so the user of the library can simply express something in the lines of "I want this, this and this data to be serialized", make sure that the object type is registered and that's it.
This is just some concept code and has a lot of problems as such. This will look very different when I'm done with it.




Other things worth mentioning


Inheriting from a base object

To make serialization more flexible, it can be a good idea that your serializable objects inherit from a base object as it makes organization far easier. The disadvantage is that data cannot be serialized in a convenient way unless it inherits the base object. In Lite Serializer v1.x and earlier code I have attempted other ways and it turns out that not using a base class complicates things a lot. Having to inherit a base serializable object is a small trade-off compared to the problems you have to face if you don't. If I remember right, even Boost uses this kind of approach.

Automated code generation with tools

I have also attempted an alternative where Lite Serializer was only part of the solution. The other part was a code generation tool which you can get from here: http://forum.chaos-project.com/index.php/topic,8494.0.html
Class Maker allows you to specify object structure and can generate code for serialization and deserialization for C++ automatically. The disadvantage to this approach is that you have to define all your data in it which can be bothersome as you have to do it twice (once in your code headers, once in the tool), but the problem was supposed to be solved by being able to generate headers as well. I didn't abandon the idea of using a tool like this, but currently I have no time to continue development on it.
Another advantage was that you could import the created data model into another tool called Data Maker which allows you to create a database of objects based on the given model and generate serialized files which Lite Serializer then could read without problems. These two tools used in conjunction with Lite Serializer can already save you a lot of work even though they may not even be fully functional or have all the features implemented that I planned for it.




Conclusion


Long story short, you are usually better off finding an already present solution for serialization. This problem has been plaguing me for over 3 years, ever since I started programming really professionally in C++ exactly because of that reason: There isn't a good solution so far, at least none that satisfied my requirements. The greatest problem is posed by the fact that C++ does not have any meta data for the objects. I want a good solution for this problem and the iterations of serialization code that I went through so far have always been unsatisfying, but good enough at the time. At least each one of them brought me a step closer to my final goal: To make a serializer that works and works well.

Today I am wiser, I understand better how C+ works and I have more experience in that particular area so I believe that I can finally solve this problem once and for all.

If you have questions or suggestions, feel free to post them. I wrote all of this in one go so I am not 100% sure if I covered everything. If I remember something, I will add it afterwards.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

Apidcloud

December 02, 2012, 10:44:05 am #1 Last Edit: December 02, 2012, 10:47:53 am by Apidcloud
It's quite an interesting topic, specially because I'm about to need something like that xD


Indexed variable names
Quote
This approach has a disadvantage, because changing variable names will cause data to be lost or mixed up as the meta data would differ from the one contained in the serialized data.

Isn't it possible to update the previous versions' files? I mean, the problem with indexed variable names, as you stated, is the data loss or its mix up, but wouldn't you be able to remove a specific variables(if that occurs at the new version) so it would still be compatible? I believe it would require some work around, since you would need to know specifically which variables you added or removed, isn't that right? How about having some automatic process that compares previous and new versions in order to remove or add those variables? In case of adding, that specific variable would be initialized with a standard value(e.g int -> 0, string -> ""). I believe it's a possible solution even though it may be slow.

For the record, I may be saying something really amiss  :^_^':
Instead of wanting to be somebody else, rather become somebody else



"I will treasure the knowledge like a squirrel treasures acorns."


Gibbo Glast 2D Engine - The sky is no longer a limit

Blizzard

No, adding and removing would work automatically. Only renaming would cause problems and I want to solve that with some kind of aliasing subsystem that allows you to specify how variables were renamed. The part with the mix up would be something like changing a variable name and then adding another variable that has the same name like the the changed variable had before.

Code: before
float permille; // how much permille of alcohol one has in their blood


Code: after
float bac; // blood alcohol content, previously named "permille"
float permille; // how much permille of alcohol the drink contains


This would obviously cause a mix up.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

Apidcloud

I didn't think about mixing in that way, sorry.

Quote from: Blizzard on December 02, 2012, 10:55:41 am
Code: before
float permille; // how much permille of alcohol one has in their blood


Code: after
float bac; // blood alcohol content, previously named "permille"
float permille; // how much permille of alcohol the drink contains


This would obviously cause a mix up.


It's even harder that way. Just replacing the variable's name wouldn't be that hard, but using previous name in another variable would really mix up all the process  :facepalm:
It would act like adding a new variable called bac and previous version's permille value would be lost?

Instead of wanting to be somebody else, rather become somebody else



"I will treasure the knowledge like a squirrel treasures acorns."


Gibbo Glast 2D Engine - The sky is no longer a limit

Blizzard

Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

Blizzard

Here are some of the crazy shit macros that I will be using. Though, they are not fully functional yet. I still have to iron out some bugs.

Spoiler: ShowHide
#define __LS_EXPAND(x) x
#define __LS_FIRSTARG(x, ...) (x)
#define __LS_RESTARGS(x, ...) (__VA_ARGS__)

#define __LS_REM(...) __VA_ARGS__
#define __LS_EAT(...)
// Retrieve the type
#define __LS_TYPEOF(x) __LS_DETAIL_TYPEOF(__LS_DETAIL_TYPEOF_PROBE x,)
#define __LS_DETAIL_TYPEOF(...) __LS_DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define __LS_DETAIL_TYPEOF_HEAD(x, ...) __LS_REM x
#define __LS_DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__)
// Strip off the type
#define __LS_STRIP(x) __LS_EAT x
// Show the type without parenthesis
#define __LS_PAIR(x) __LS_REM x


#define __LS_VA_ARGC_INDEX( \
_0,  _1,  _2,  _3,  _4,  _5,  _6,  _7,  _8,  _9, \
_10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \
_20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \
_30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \
_40, _41, _42, _43, _44, _45, _46, _47, _48, _49, \
_50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \
_60, _61, _62, _63, size, ...) size
#define __LS_VA_ARGC(...) __LS_VA_ARGC_INDEX((__VA_ARGS__), \
64, 63, 62, 61, \
60, 59, 58, 57, 56, 55, 54, 53, 52, 51, \
50, 49, 48, 47, 46, 45, 44, 43, 42, 41, \
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, \
30, 29, 28, 27, 26, 25, 24, 23, 22, 21, \
20, 19, 18, 17, 16, 15, 14, 13, 12, 11, \
10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0)

#define __LS_FOREACH(m, args) __LS_FOREACH_(m, __LS_VA_ARGC args, args)
#define __LS_FOREACH_(m, n, args) __LS_FOREACH__(m, n, args)
#define __LS_FOREACH__(m, n, args) __LS_FOREACH_ ## n(m, n, args)

#define  __LS_FOREACH_1(m, n, args) m(n, args)
#define  __LS_FOREACH_2(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args)  __LS_FOREACH_1(m, (n - 1), __LS_RESTARGS args)
#define  __LS_FOREACH_3(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args)  __LS_FOREACH_2(m, (n - 1), __LS_RESTARGS args)
#define  __LS_FOREACH_4(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args)  __LS_FOREACH_3(m, (n - 1), __LS_RESTARGS args)
#define  __LS_FOREACH_5(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args)  __LS_FOREACH_4(m, (n - 1), __LS_RESTARGS args)
#define  __LS_FOREACH_6(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args)  __LS_FOREACH_5(m, (n - 1), __LS_RESTARGS args)
#define  __LS_FOREACH_7(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args)  __LS_FOREACH_6(m, (n - 1), __LS_RESTARGS args)
#define  __LS_FOREACH_8(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args)  __LS_FOREACH_7(m, (n - 1), __LS_RESTARGS args)
#define  __LS_FOREACH_9(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args)  __LS_FOREACH_8(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_10(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args)  __LS_FOREACH_9(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_11(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_10(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_12(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_11(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_13(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_12(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_14(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_13(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_15(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_14(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_16(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_15(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_17(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_16(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_18(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_17(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_19(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_18(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_20(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_19(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_21(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_20(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_22(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_21(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_23(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_22(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_24(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_23(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_25(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_24(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_26(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_25(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_27(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_26(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_28(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_27(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_29(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_28(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_30(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_29(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_31(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_30(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_32(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_31(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_33(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_32(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_34(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_33(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_35(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_34(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_36(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_35(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_37(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_36(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_38(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_37(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_39(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_38(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_40(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_39(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_41(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_40(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_42(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_41(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_43(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_42(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_44(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_43(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_45(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_44(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_46(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_45(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_47(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_46(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_48(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_47(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_49(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_48(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_50(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_49(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_51(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_50(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_52(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_51(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_53(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_52(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_54(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_53(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_55(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_54(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_56(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_55(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_57(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_56(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_58(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_57(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_59(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_58(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_60(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_59(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_61(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_60(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_62(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_60(m, (n - 1), __LS_RESTARGS args)
#define __LS_FOREACH_63(m, n, args) __LS_EXPAND(m n __LS_FIRSTARG args) __LS_FOREACH_60(m, (n - 1), __LS_RESTARGS args)


And some article on how I can get reflection to work somewhat in C++ with template voodoo magic.
http://stackoverflow.com/questions/41453/how-can-i-add-reflection-to-a-c-application

This is gonna take some while. >.< But it will be so worth it when you can define a class like this and it just works.

#ifndef GAME_STATE_H
#define GAME_STATE_H

class GameState : public liteser::Serializable
{
public:
LS_SERIALIZABLE_CLASS;
GameState();
~GameState();

protected:
LS_SERIALIZABLE
(
(bool) loaded,
(bool) running,
(bool) finished,
(float) time
)
};
#endif
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.