Sign in to follow this  
ViperG

Game Saving/Loading

Recommended Posts

So I've already made a few posts trying to understand how to save structs and vectors. is it normal practice for struct saving to use ofs.write((char *)&one, sizeof(one)); when dealing with saving/loading games. or do most games write serializers? the only problem with the former that I see is if they want to send their save games to other people... basically I have 2 main questions. Do i have to serialize? (because ive looked a few examples and it looks like several hundred lines of code to make your own and I've never done it before) and if I have to serialize, how the hell do i do it? I'm using vectors that contain stucts, but I think I'm going to just loop through the vector my self and attempt to simply save the structs and not worry about the vector it's self. then when i load i can re-populate the vector. only because serializing a vector is probably 10x more code than serializing a struct, and also, some of my structs contain vectors of structs, so i would be clueless on how to serialize that correctly, or even write a template/generic serialization function that could handle it all. I'm starting to think I should just do it manually by hand and save evertyhing in a text file, even though it will take me way way longer to code(a week? for saving and loading functions?), i would understand it (the code) and can even edit the save game files outside the application.

Share this post


Link to post
Share on other sites
boost::serialization

Because ugly hacks that only do shallow copies break down very fast, and/or require all sorts of maintenance.

It's still nontrivial, nothing like the one-liner you can do with Python's pickle module. I suppose that's just the language limitations of C++.

P.S. Nifty game concept, and nice screenshots. I don't think there's been anything like Star Control 2 or Privateer since...well, SC2 and Privateer.

Share this post


Link to post
Share on other sites
would this work and be any good? or is this crap?
also is there anyway I can turn this into a template so I can use other datatypes(mystruct1, mystruct2...)

struct mystruct{
vars..
}

vector<mystruct> vi;

ofstream out("out.txt");
ostream_iterator<mystruct> oi(out, " ");

copy(vi.begin(), vi.end(), oi);

out.close();

Share this post


Link to post
Share on other sites
Well, you may not be using your own classes, but you are using classes. The big deal about serialization is that just writing something like 'ofs.write((char*)&one,sizeof(one))' [which by the way, is a method of serialization, though not a particularly robust one] rarely stores just the right amount of information. For every pointer you have, that method will save an address [extra information] and not the data the pointer points to [too little information]. There may also be data that you want to store in run-time but not in the save [or the other way around], like for example a running sum of some other numbers [which will be faster to recompute than to read from disk]. There are also instances when you may be storing information that is only immediately useful [like a timestamp, which is meaningless when you reload the structure 4 weeks later].

There are the things that cause people to concern themselves with implementing a robust and controlled way of serializing.

As far as how to do it, it's often something that is sewn into the various classes. If you absolutely insist on not using classes [by the way, structs are pretty similar to classes in c++. Very, very similar. Matter of fact, you could write member functions and such into structs, which makes the following recommendation moot. But this is a 'straight C' way to do it.], you could technically have the first data member of all of your structs be a pointer to a function that does the serialization, thus you just pass the struct via pointer to a function that casts the struct to a serialization pointer and passes the struct address to it. Seriously though, this is re-inventing the wheel [and crafting that wheel out of hand grenades and uncut 6-pack plastic rings]. The only reason I'm even typing this is because you asked how to do it the ol' fashion way, which was awful [thus the reason why it's the old fashion way, and not the new hip way]. You're using a powerful language, use it's power, and don't imprison yourself by refusing to use certain features that were provided for a good reason.

Share this post


Link to post
Share on other sites
Quote:
Original post by drakostar
It's still nontrivial, nothing like the one-liner you can do with Python's pickle module.


A common misconception. If you just pickle an object to save it, it will bring all its references along. Some of these might be wanted (other entities, transform matrix, and so on) while others are actually quite unwanted (the OpenGL context, the texture data, the networking sockets, and so on), which forces you to discriminate over what every object is allowed to reference just so that it serializes correctly.

I'm using O'Caml myself, which also has a serialization module (Marshal) and still writing my own serialization system for performance, compression and especially reference-discrimination issues.

Share this post


Link to post
Share on other sites
if I used ostream_iterator i would have to overload operators for each struct/class so that sounds like a really bad idea.

anyone have a really easy to understand serialization tutorial for boost or something even easier than that?

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
A common misconception. If you just pickle an object to save it, it will bring all its references along. Some of these might be wanted (other entities, transform matrix, and so on) while others are actually quite unwanted (the OpenGL context, the texture data, the networking sockets, and so on)

Why would you have any of that mixed in with your game world representation? Good OO practice and/or a proper multi-threaded game engine requires an independent model, from which the representations in the renderer, physics engine, etc. are derived. There are a number of threads in the OGRE forums discussing this kind of engine and its implementation at length, if you think that's too idealistic. No, I'm quite happy with the one-liner.

Share this post


Link to post
Share on other sites
Quote:
if I used ostream_iterator i would have to overload operators for each struct/class so that sounds like a really bad idea.
anyone have a really easy to understand serialization tutorial for boost or something even easier than that?


In C++, you will need to describe serialization behaviour for every class that needs to be serialized. This is a direct consequence of the fact that C++ provides no reflection or introspection (the two main ways of achieving generic serialization algorithms).

Quote:
Original post by drakostar
Why would you have any of that mixed in with your game world representation? Good OO practice and/or a proper multi-threaded game engine requires an independent model, from which the representations in the renderer, physics engine, etc. are derived.


Good OO practice and/or a proper multi-threaded game requires a model that is statically independent: model functionality may not explicitly access view functionality, and any interaction between them must be handled by controller objects.

However, must the controller objects necessarily interact with the model through polling? Is there any reason to forbid the Observer design pattern from being used? Listeners? Callbacks? The C# Event-and-Delegate pattern?

Share this post


Link to post
Share on other sites
Quote:
Original post by Drigovas
...[by the way, structs are pretty similar to classes in c++. Very, very similar...


Just wanted to clarify this a little. Classes and structs only have one difference: the members of a class have the default protection-label of private, whereas a struct has the default protection-label of public.

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
However, must the controller objects necessarily interact with the model through polling? Is there any reason to forbid the Observer design pattern from being used? Listeners? Callbacks?


Not at all. Think of separate GameModel and GameModelData objects. The GameModel object applies changes to the data, updates observers, etc. It's only the data itself that gets saved.

Say you have AI methods in Character objects. Whatever is calling those methods (eg, GameModel::run) should be a publisher, not the Character itself (except perhaps in an intra-model context). This makes sense for a number of reasons, including efficiency, lack of extra complexity, and decoupling.

And just to be clear, pickle handles this kind of pattern without issue:

import pickle

class Observer:
def __init__(self, name):
self.name = name
def update(self, data):
print "%s: %s" % (self.name, data)

class Publisher:
def __init__(self):
self.observers = []
def attach(self, o):
self.observers.append(o)
def notify(self, data):
for o in self.observers:
o.update(data)

class World:
def init(self):
self.pub = Publisher()
self.ob1 = Observer('One')
self.ob2 = Observer('Two')
self.ob3 = Observer('Three')
self.pub.attach(self.ob1)
self.pub.attach(self.ob2)
self.pub.attach(self.ob3)

def do_something(self):
self.pub.notify('foo')

def test_pickle():
w = World()
w.init()

fout = file('test.pkl', 'wb')
pickle.dump(w, fout)
fout.close()

def test_unpickle():
fin = open('test.pkl', 'rb')
w = pickle.load(fin)
w.do_something()

test_pickle()
test_unpickle()





(edit) Sorry for mixing my terms...Observer/Subject, Publisher/Subscriber, it's all the same anyway.

Share this post


Link to post
Share on other sites
Quote:
Original post by drakostar


You seem to have misunderstood me. I never said anything about using callbacks/observers/publishers within the scope of the model—this is something which every single serialization package should get right, or burn a fiery death at my hands.

The problem comes when using callbacks as a lazy means of communication between a model object and a controller object. That is, instead of the controller polling a grenade object every update tick to see if an "explosion" sound needs to be played, he registers an "OnExplode" callback with the the grenade object that plays the sound. This kind of design (event-based) is both easier to understand and faster. An O'Caml example:
grenade # on_explode ++
(fun () -> sound # play "explosion.wav")


However, as soon as model objects start holding callbacks which refer to things outside the model, boom. This leaves you with three options:
  • Configure or alter the serialization system to ignore handlers somehow marked as "external". This is the approach I use.
  • Use some kind of indirect addressing-and-messaging system to handle callbacks.
  • Use only polling from controller to model.

Share this post


Link to post
Share on other sites
Quote:
Use some kind of indirect addressing-and-messaging system to handle callbacks.

That's more or less what follows from any kind of multi-threaded engine. To oversimplify a bit, the model thread would push a GrenadeExplode event to the input queues of any interested parties (renderer, sound, and physics).

Even in a single-threaded engine, it shouldn't be the Grenade object that's telling the sound manager it exploded...otherwise you're talking about each individual object knowing what kind of data format the sound manager expects. The Model would inform it of the event, perhaps after being notified by the Grenade object.

Share this post


Link to post
Share on other sites
hi ViperG...
Why you don't save the values in file type a INI?
this is simple.
You don´t have problem with this, so you have be careful with the values you pass to this file...Ok?and you have interpreted this values where the exe is access again.
Ok?

good luck.
have a nice day.

Share this post


Link to post
Share on other sites
hey jack,

thanks for the advice. I'm still researching the best way to do this (serialization vs standard text)

I can still save everything as a text file, it will just take longer me to to code that part, as i have to manually go through each struct and save all the variable types.

but with serialization, i have to set the serialization behavior anyways, so it would probably be more difficult to get working, but probably in less time.

Share this post


Link to post
Share on other sites
Quote:
Original post by drakostar
Even in a single-threaded engine, it shouldn't be the Grenade object that's telling the sound manager it exploded...otherwise you're talking about each individual object knowing what kind of data format the sound manager expects.


You're confusing dynamic and static coupling. The Grenade object isn't telling the sound manager it exploded, it tells "whomever might care about it" that it exploded (just like any other event-based object), which creates absolutely zero coupling between grenade and sound manager.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this