GPWiki.org
GPWiki.org
It is currently Thu May 23, 2013 8:57 pm

All times are UTC




Post new topic Reply to topic  [ 18 posts ] 
Author Message
PostPosted: Sat Jun 02, 2012 7:14 pm 
Bytewise

Joined: Sun Oct 16, 2011 3:09 pm
Posts: 277
Location: Here (where else?)
I am thinking about how to write a game loading routine.

The scenario is that a user wants to save the state of the level, so he can continue from there later.
Saving the actual relevant data onto a file, and getting it back into a playable state will probably not give any problems.
However, are there clever techniques to reduce the amount of code that you must write (ie saving/loading a struct/class X is not so different from saving/loading a struct/class Y, just the fields change. Copy/pasting lots and lots of the same patterns does not look like the right solution.)

What I am also wondering about is how to handle the many gamefile versions. I expect that the actual data that needs to be stored will change often. New features will add new data to store or modify storage requirements of existing data. I'd like to be able to load every version I created in the past. Obviously, I should save the version number in the file, so you know what to expect when loading, but how do you load one of a dozen or more different files without writing a load function for each version which are all are the same for about 90+% ?

_________________
My project: Messing about in FreeRCT, dev blog, and IRC #freerct at oftc.net


Top
 Profile  
 
PostPosted: Sat Jun 02, 2012 7:28 pm 
Source Code Swashbuckler
User avatar

Joined: Wed Nov 09, 2011 3:58 am
Posts: 199
Location: Brazil
Well, I would assume that the newer version will have to check if that saved XML does have the specified field that you want to load. If don't, the game will have to create an default value for that field.

a) This default value could be instilled by the algorithm.
b) Or you could create another XML, which will contain all default values. If the algorithm don't find the field, then the game will check for a default value in that XML.
This XML gonna be updated for each version of the game, with the new(and old) fields default values. So, you will just need to update one file for each version update.

_________________
"Life finds a way." - Ian Malcolm
My WebBlog: PixelDeveloper
English is not my native language, so excuse me for any writing mistakes.


Top
 Profile  
 
PostPosted: Sat Jun 02, 2012 7:42 pm 
Bytewise

Joined: Sun Oct 16, 2011 3:09 pm
Posts: 277
Location: Here (where else?)
FelipeFS wrote:
Well, I would assume that the newer version will have to check if that saved XML does have the specified field that you want to load. If don't, the game will have to create an default value for that field.

a) This default value could be instilled by the algorithm.
b) Or you could create another XML, which will contain all default values. If the algorithm don't find the field, then the game will check for a default value in that XML.
This XML gonna be updated for each version of the game, with the new(and old) fields default values. So, you will just need to update one file for each version update.
:O you think I do everything in XML :)
Sorry, but no, XML is terrible to handle in C++, so I just write binary files :D

I agree with your observation that the new values needs to be supplied from somewhere. I was thinking to just hard-code it in the program (since it is fixed in game-file version X, and not ever going to change).
My first worry however is how to code loading of a single version in a sane way, without thousands of lines that do exactly the same, except the field and class are different. (that is, at C++ function/method level).
My second is the same, except between versions. From one version to the next is not having a lot of changes, so code from 'loadVersionX()' and 'loadVersion(X+1)' is going to be the same for abut 90+%. It can't be good to duplicate that code.

_________________
My project: Messing about in FreeRCT, dev blog, and IRC #freerct at oftc.net


Top
 Profile  
 
PostPosted: Sat Jun 02, 2012 8:51 pm 
Source Code Swashbuckler
User avatar

Joined: Wed Nov 09, 2011 3:58 am
Posts: 199
Location: Brazil
I will insist that you should use XML in this case :rofl ! Well, it will pretty hard to make an intelligent algorithm to work with binary-code. If you make a implementation to read the binary file of a previou version, your game gonna read the wrong piece of bytes. With a parser and a data-base language(like XML) you could create something like:
Code:
string GetValueFromField(string fieldname)
{
  // load the value to a string;
  return the string;
}

if the field is of another type, you gonna overload the function and convert the string to be returned to the type you desire; This way, plus what I said before, you will neither have to worry about versions.

Let's say that your map has a function "SaveToFile()"
Code:
void TMap::SaveToFile()
{
  // Save the basic information like daytime, weather, player information(money, etc.);
  // Save the terrain tileset;

  // for each object in the map, call the ancestor function TObject::SaveToFile() of each object;
  //* in this case, since each type of object has their own class,  *\\
  //* you will have a function in the objects that will save the    *\\
  //* fields according to the type of the object                    *\\ 
}


I was using this kind of method to my game engine(the difference was the data-base language). And it works fine to load and save objects of different classes and versions.

_________________
"Life finds a way." - Ian Malcolm
My WebBlog: PixelDeveloper
English is not my native language, so excuse me for any writing mistakes.


Top
 Profile  
 
PostPosted: Sun Jun 03, 2012 8:29 am 
Bytewise

Joined: Sun Oct 16, 2011 3:09 pm
Posts: 277
Location: Here (where else?)
FelipeFS wrote:
I will insist that you should use XML in this case :rofl ! Well, it will pretty hard to make an intelligent algorithm to work with binary-code. If you make a implementation to read the binary file of a previou version, your game gonna read the wrong piece of bytes. With a parser and a data-base language(like XML) you could create something like:
Ah, I see. You are using the parser to sort out the mess of fieldname selection because you add a meta-class description (in the form of xml tags). Nice idea.

FelipeFS wrote:
if the field is of another type, you gonna overload the function and convert the string to be returned to the type you desire; This way, plus what I said before, you will neither have to worry about versions.
This is true to the extent that old values are interpretable as new values without problems. Eg
Code:
<field name="flag" value="4"/>
works both times, whether my flag value is a byte (in an old version) or a long number (in a new version) in memory.
Code:
<field name="flag" value="4"/>
fails if the old meaning of "4" is different from the new meaning of "4".
Code:
<field name="flag" value="true"/>
will give trouble if the old version was a boolean, and the new value is an integer (reading 'true' as integer is not going to work easily).
Code:
<field name="flag" value="256"/>
will give trouble if the value in the new version is a byte (and 256 thus does not fit).
There are probably more edge cases, at least it does not feel like an exhaustive list to me.

The question however is whether these cases ever happen, perhaps there is an easy way around them. I'll have to ponder about that for a few days.

[Deeper insight: Ah, so the version number in a file is also a meta-class description, except the mapping from number to class is in the program instead of in the file]


FelipeFS wrote:
Let's say that your map has a function "SaveToFile()" ...
Yeah, saving is the easy part, but you have to do it right if you are ever going to be able to load it back.

Thanks for your insights, it pushed me out of my box of thinking. Now I have to decide what to do :)

_________________
My project: Messing about in FreeRCT, dev blog, and IRC #freerct at oftc.net


Top
 Profile  
 
PostPosted: Sun Jun 03, 2012 9:45 am 
Funky Monkey

Joined: Thu Sep 09, 2004 1:17 pm
Posts: 1552
Location: burrowed
I don't know if c++ offers reflection as feature. you can basically get the names of members and functions in your code as string in runtime. This makes serialization of objects pretty handy, which is a feature i always use when saving/loading an objects state to files.

It basically goes through all public members of a class and saves them all into an xml file (or binary if you want).

Not sure if that helps though :P

_________________
Long pork is people!

wzl's burrow


Top
 Profile  
 
PostPosted: Sun Jun 03, 2012 11:03 am 
Bytewise

Joined: Sun Oct 16, 2011 3:09 pm
Posts: 277
Location: Here (where else?)
weezl wrote:
I don't know if c++ offers reflection as feature. you can basically get the names of members and functions in your code as string in runtime.
No it doesn't :p
At runtime you only have the size of the object, and a bunch of bytes. Some of the bytes are 'real' data, other bytes are part of the runtime system or are unused.
(I think it is good in the sense it fits with the idea of the language "be as quick as you can", you don't pick c++ when you want to do a lot of reflective stuff).

You can of course make some table containing offsets and so.

weezl wrote:
This makes serialization of objects pretty handy, which is a feature i always use when saving/loading an objects state to files.
Saving is not the problem. The puzzle is during loading. You get data from an old version and you have to 'upgrade' it to the current version (sort of like you have fall-through cases, from one version to the next, to the next, to ..., until you are at 'current').

Perhaps I should just make something, and refine later, once I have some experience with the subject.

_________________
My project: Messing about in FreeRCT, dev blog, and IRC #freerct at oftc.net


Top
 Profile  
 
PostPosted: Sun Jun 03, 2012 11:11 am 
Shake'n'Baker

Joined: Sun May 27, 2012 6:01 pm
Posts: 62
hi,

boost offers reflection and also serialisation so might be worth checking out.

As for fields values changing, they have to be read in context, there is no other way of determining there correctness.

What you could do is have an abstract class, and seperate implementation classes with version numbers on. Id suggest version numbers for each object (as well as the whole save data).

You can then mark old classes as deprecated to help you out alittle.

Code:
class ICECREAM_SHOP {
  virtual ask_for_available_list();
  virtual buy_icecream(ICECREAM choice);
  ...
};

class ICECREAM_SHOP_V2: ICECREAM_SHOP {
  ...
};

class ICECREAM_SHOP_V1: ICECREAM_SHOP {
  ~ICECREAM_SHOP_V1() {
    std::cout << "icecream shop v1 is deprecated.\n";
  }


Code:
<icecream_shop version="1">
  ... We now have a context. we can use specific implementation details etc.
</icecream_shop>


This should also illustrate that if your abstract class changes, your have to retrofit features to old implementations otherwise it wont work (and your get a compiler error)

hope this helps!
cxzuk


Top
 Profile  
 
PostPosted: Sun Jun 03, 2012 4:12 pm 
Source Code Swashbuckler
User avatar

Joined: Wed Nov 09, 2011 3:58 am
Posts: 199
Location: Brazil
I really wish I could help with a way to load the binary files, the only thing that came to mind is to create a vector which have as element something like this--But you gonna have to re-write the code, which is a discouragement--:
Code:
structure TProperty
{
  string FFieldName; // The name of the property;
  byte FType; // 0=BYTE; 1=WORD; 2=INTEGER; 3=STRING;
  string FValue; // The value of the property;
}

Then you will write/read the string, type, and value in sequence in the binary file.

Yesterday I made a download of the first RoolerCoaster, then, at random, I discovered that the first RC was made almost entirely in assembly. So, somehow they managed the loading, I just don't know if in a good way to support updated versions.

_________________
"Life finds a way." - Ian Malcolm
My WebBlog: PixelDeveloper
English is not my native language, so excuse me for any writing mistakes.


Top
 Profile  
 
PostPosted: Sun Jun 03, 2012 4:25 pm 
Bytewise

Joined: Sun Oct 16, 2011 3:09 pm
Posts: 277
Location: Here (where else?)
FelipeFS wrote:
I really wish I could help with a way to load the binary files, the only thing that came to mind is to create a vector which have as element something like this--But you gonna have to re-write the code, which is a discouragement--:
Oh, rewriting code is not a problem. I expect to do that a few times before everything is right.

FelipeFS wrote:
Yesterday I made a download of the first RoolerCoaster, then, at random, I discovered that the first RC was made almost entirely in assembly. So, somehow they managed the loading, I just don't know if in a good way to support updated versions.
Euhm, we don't use any code or graphics of the original Rollercoaster program. Everything is made from scratch. We don't want to get sue-ed by the owners of the original.

You don't have to copy them too (I even prefer that you don't, as it makes the program more recognizable as a different game).
Just make pretty graphics :p

_________________
My project: Messing about in FreeRCT, dev blog, and IRC #freerct at oftc.net


Top
 Profile  
 
PostPosted: Sun Jun 03, 2012 4:29 pm 
Source Code Swashbuckler
User avatar

Joined: Wed Nov 09, 2011 3:58 am
Posts: 199
Location: Brazil
Alberth wrote:
Euhm, we don't use any code or graphics of the original Rollercoaster program. Everything is made from scratch. We don't want to get sue-ed by the owners of the original.

You don't have to copy them too (I even prefer that you don't, as it makes the program more recognizable as a different game).
Just make pretty graphics :p

:thumbs It is more to know what objects are lacking.

_________________
"Life finds a way." - Ian Malcolm
My WebBlog: PixelDeveloper
English is not my native language, so excuse me for any writing mistakes.


Top
 Profile  
 
PostPosted: Sun Jun 03, 2012 6:20 pm 
All scenery objects, rides, fences, shops, etc. are currently missing :) I suggest you start with shops or scenery objects. Which would you like to try? I can send you a list of suggestions...


Top
  
 
PostPosted: Sun Jun 03, 2012 6:22 pm 
Rookie

Joined: Sat Jun 02, 2012 10:46 pm
Posts: 3
Oops, realised I was posting anonymously. Posting to subscribe to this topic.


Top
 Profile  
 
PostPosted: Sun Jun 03, 2012 6:35 pm 
Source Code Swashbuckler
User avatar

Joined: Wed Nov 09, 2011 3:58 am
Posts: 199
Location: Brazil
I'm starting with the shops. Later I will work with some park objects(seats, trash, dinner seats etc.)

_________________
"Life finds a way." - Ian Malcolm
My WebBlog: PixelDeveloper
English is not my native language, so excuse me for any writing mistakes.


Top
 Profile  
 
PostPosted: Sat Jun 16, 2012 12:27 pm 
Bytewise

Joined: Sun Oct 16, 2011 3:09 pm
Posts: 277
Location: Here (where else?)
The topic derailed a bit, but to give you an update with my current ideas, which I hope will work.

The file is split into blocks, with a header consisting of a name (4 letters), a version, and a length. After the header comes the binary data with the promised size. You can have blocks inside blocks.
The blocks make clear what kind of data is coming, and what version to expect. Also, the length has to match between saving and loading.
Theoretically, you can also skip blocks, but I doubt that will ever be useful.

Saving is fairly trivial, just write the header and the data, in the right order, ensure sizes are correct, etc.
Loading is a little harder. The principle that I use is as follows:
Code:
// The header has been read ([i]Loader[/i] gives out the data,
// keeps block header information,
// and tracks number of bytes remaining in the block.
bool LoadXYZ(Loader *ld)
{
    // Create local variables
    uint8 type;  // Data of the XYZ block
    uint8 slope;
    int current_version = 0;

    // Load the first version
    if (ld->version == 1) {
        // Load type & slope as defined in version 1 of the XYZ block.
        current_version = 1;
    }

    // Load/upgrade to version 2
    if (ld->version == 2) {
        // Load type & slope as defined in version 2 of the XYZ block.
        current_version = 2;
    }
    if (current_version == 1) {
        // Upgrade from version 1 to version 2
       if (upgrade_is_ok) current_version = 2;
    }

    // Add (future) version 3, 4, ... here in the same way

    // Is the data in the right version? If so, great, save it
    if (current_version == 2) {
        // Save type and slope into the game data
        return true;
    }
    return false; // No good version number found, or upgrade failed
}
The whole idea is that you have the data in local variables, and a current_version, which represents the version in the local variables.
While executing the code, the ld->version will match one case, and the data gets loaded as the right version.
After that, the branches which test the current_version are taken each time, upgrading the data to the next version.

At the end, test that the data is in the right version, and save it into the game.

_________________
My project: Messing about in FreeRCT, dev blog, and IRC #freerct at oftc.net


Top
 Profile  
 
PostPosted: Sat Jun 16, 2012 6:50 pm 
Shake'n'Baker

Joined: Sun May 27, 2012 6:01 pm
Posts: 62
Alberth wrote:
The topic derailed a bit, but to give you an update with my current ideas, which I hope will work.

The file is split into blocks, with a header consisting of a name (4 letters), a version, and a length. After the header comes the binary data with the promised size. You can have blocks inside blocks.
The blocks make clear what kind of data is coming, and what version to expect. Also, the length has to match between saving and loading.
Theoretically, you can also skip blocks, but I doubt that will ever be useful.

Saving is fairly trivial, just write the header and the data, in the right order, ensure sizes are correct, etc.
Loading is a little harder. The principle that I use is as follows:
Code:
// The header has been read ([i]Loader[/i] gives out the data,
// keeps block header information,
// and tracks number of bytes remaining in the block.
bool LoadXYZ(Loader *ld)
{
    // Create local variables
    uint8 type;  // Data of the XYZ block
    uint8 slope;
    int current_version = 0;

    // Load the first version
    if (ld->version == 1) {
        // Load type & slope as defined in version 1 of the XYZ block.
        current_version = 1;
    }

    // Load/upgrade to version 2
    if (ld->version == 2) {
        // Load type & slope as defined in version 2 of the XYZ block.
        current_version = 2;
    }
    if (current_version == 1) {
        // Upgrade from version 1 to version 2
       if (upgrade_is_ok) current_version = 2;
    }

    // Add (future) version 3, 4, ... here in the same way

    // Is the data in the right version? If so, great, save it
    if (current_version == 2) {
        // Save type and slope into the game data
        return true;
    }
    return false; // No good version number found, or upgrade failed
}
The whole idea is that you have the data in local variables, and a current_version, which represents the version in the local variables.
While executing the code, the ld->version will match one case, and the data gets loaded as the right version.
After that, the branches which test the current_version are taken each time, upgrading the data to the next version.

At the end, test that the data is in the right version, and save it into the game.


Hi Alberth,

Im not sure what your after, do you want comments?

Quote:
how do you load one of a dozen or more different files without writing a load function for each version which are all are the same for about 90+% ?


Your original question seems to be the method you've gone for? I really think you'd benefit from a more OO approach to this.

cxzuk


Top
 Profile  
 
PostPosted: Sat Jun 16, 2012 8:43 pm 
Bytewise

Joined: Sun Oct 16, 2011 3:09 pm
Posts: 277
Location: Here (where else?)
I mainly posted to update the thread in case someone was looking for the same problem.

However, I noticed too that my original question was different from my current answer while re-reading the topic. My ponderings seem to have shifted in time.
I think I made progress though. While writing the first post, I had no idea of how to solve the problem in a somewhat nice way in actual code.
My post above at least gives a solution (I think). I agree however with you that it is a solution with a lot of duplicated code.

I see however also problems with the solution you posted where you have several versions of an API available during playing. Suppose I have 5 icecream shop versions, and I change the API of the guest.
Then in your solution you need to change 5 shop objects. In my solution, change the API of the current shop, and that's it. That is, I have 4 less classes to change. Note that this holds for every type of object that the guest interacts with.
Testing also may become an interesting exercise, trying all versions of eg a shop sounds already very complicated. Trying all combinations of all versions of all objects is..... well, no idea, but it does not look right to me. Reproducing a bug may also be a problem, as the reporter may have used an old save-game (and thus have old shop objects), which you don't use when making the same setup from srcatch.
So, while it may solve the duplicated loading code, I fear it will explode in other areas.

I currently don't see an easy solution to the duplicated code problem. The solution by FelipeFS goes in that direction, but I am not convinced all edge cases work properly, and I don't want to risk problems. Mistakes that you make here will haunt you forever due to old versions that keep lingering around. With the version number, I can reproduce the field-names if necessary, so I should have sufficient information in the save game to reproduce things, should the need arise.

The current conclusion seems to be that I will re-visit this topic again somewhen in the future, when I have a bit of experience with the subject, and some actual versions with messy code that needs a clean-up.

Albert

_________________
My project: Messing about in FreeRCT, dev blog, and IRC #freerct at oftc.net


Top
 Profile  
 
PostPosted: Sun Jun 17, 2012 12:35 pm 
Shake'n'Baker

Joined: Sun May 27, 2012 6:01 pm
Posts: 62
Alberth wrote:
I mainly posted to update the thread in case someone was looking for the same problem.

However, I noticed too that my original question was different from my current answer while re-reading the topic. My ponderings seem to have shifted in time.
I think I made progress though. While writing the first post, I had no idea of how to solve the problem in a somewhat nice way in actual code.
My post above at least gives a solution (I think). I agree however with you that it is a solution with a lot of duplicated code.

I see however also problems with the solution you posted where you have several versions of an API available during playing. Suppose I have 5 icecream shop versions, and I change the API of the guest.
Then in your solution you need to change 5 shop objects. In my solution, change the API of the current shop, and that's it. That is, I have 4 less classes to change. Note that this holds for every type of object that the guest interacts with.
Testing also may become an interesting exercise, trying all versions of eg a shop sounds already very complicated. Trying all combinations of all versions of all objects is..... well, no idea, but it does not look right to me. Reproducing a bug may also be a problem, as the reporter may have used an old save-game (and thus have old shop objects), which you don't use when making the same setup from srcatch.
So, while it may solve the duplicated loading code, I fear it will explode in other areas.

I currently don't see an easy solution to the duplicated code problem. The solution by FelipeFS goes in that direction, but I am not convinced all edge cases work properly, and I don't want to risk problems. Mistakes that you make here will haunt you forever due to old versions that keep lingering around. With the version number, I can reproduce the field-names if necessary, so I should have sufficient information in the save game to reproduce things, should the need arise.

The current conclusion seems to be that I will re-visit this topic again somewhen in the future, when I have a bit of experience with the subject, and some actual versions with messy code that needs a clean-up.

Albert


Hi Albert,

Below is a quick summary of what i thought you were after;

Summary wrote:
During the development cycle, You would like to be able to save game states. In order to save game states, you need to record the internal details of objects.

The problem then arises that these internal details may change when implementation details change within your code. You wish to be able to support backward compatibility to these game states.


There are two reasons why a Class would change;
1) The implementation details of the class change

When you change the implementation details of a class, you break compatibility with your game states. So you need to keep a revision of the Old Class implementation. - You dont want to maintain these classes, They will not exist in your final project, They are for history purposes only. It is in fact unlikely that you can maintain them as you may change implementation details, destroying backward compatibility.

You could even wrap them up in a preprossor like;
Code:
#define COMPAT_MODE

#ifdef COMPAT_MODE
// Store old versions of classes here
#endif


2) The Interface (/Public Methods/API/"messages) change
If the interface changes, it is impossible to remain backward compatible without altering all versions of the class. You must extend your old classes, Id recommend inheritance to keep duplicate information to a minimum.

If you want to be able to support game state backward compatibility (Which i think is a good idea), your going to need to manage it in a way that is not too taxing on you. I feel not keeping this historic information in one central place (classes), you may find yourself spending large amounts of time on managing this feature (Debugging, large code edits).

In the bigger picture, you may need to "update/convert" game state files. Having the two versions side by side may aid with the design of that tool. Once a version is no longer needed, you want to be able to clean up your code fairly easily.

A revisit seems like a good idea, Maybe once you've got a complete list of classes and their interfaces, and a reconsider if you require this feature just yet.

Cxzuk


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 18 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group