Stories
Slash Boxes
Comments

News for nerds, stuff that matters

Object-Oriented 'Save Game' Techniques?

Posted by Cliff on Tue Feb 15, 2005 06:41 AM
from the dumping-data dept.
GreyArtist asks: "I took a course in C++ a year ago in which the instructor claimed that global (file-scope or inter-file-scope) variables were antiquated and not to be used under any circumstances. I immediately thought of a counter argument that involved the method I use for saving game data. The games (and many of the other programs) I write use not only global variables, but consecutive global variables declared in their own separate module. To save the game (or user settings) to file, I simply save a single large segment of data that contains all the necessary information. How do other coders do it? Would they create a 'MyObject.savemyself()' method for every object in their game? Do they save all the game code along with the data? Either way, it seems like a horrid case of code (or data) bloat. What do you die-hard object-oriented fanatics have to say about this, and what method they would you use for saving games?"
This discussion has been archived. No new comments can be posted.
The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.

Object-Oriented 'Save Game' Techniques? 25 Comments More | Login /

 Full
 Abbreviated
 Hidden
More | Login
Keybindings Beta
Q W E
A S D
Loading ... Please wait.
  • They're called singletons now (Score:5, Interesting)

    by mrami (664567) on Tuesday February 15 2005, @06:49AM (#11675962) Homepage
    :) You make a singleton called "Prefs" and each module stores its values in a map under its own key (and in GCC, the key could be as easy as __PRETTY_FUNCTION__)
    • by mystran (545374) <mystran@gmail.com> on Tuesday February 15 2005, @09:10AM (#11676372) Homepage
      While I'd argue that global variables are usually a bad idea, I don't see a reason agaist file-scope variables. The rationale goes like this: In OOP you use Singletons when you only need a single variable of a given type. You do it, because while the Singleton gives you global access to the single variable, it still acts as an encapsulation method. This eliminates one of the problems of global variables: unpredicatable modifications, by allowing the Singleton to define what is allowed and what is not. Class variables can do the same, since only the method of the class can touch the variables (provided they are private). But file-scope (static) variables have the same property; the only thing different is that access is restricted not by a class, but by the compilation unit. You still get the same encapsulation. Another question is whether Singletons should be avoided too. Most of the time I like writing code in such a way, that you can run two (separated) instances of the program within the same process by simply create two objects of the main application class. Unfortunately Singletons are often needed to cope with libraries and APIs that assume there's only client within a process. IMHO Singletons (and class variables and methods) are to OOP what IO-monads are to functional programming: they are "hacks" that try to work around the limitations of a given programming model, and trying to minimize the damage caused.
      [ Parent ]
  • Uhhh.. (Score:3, Interesting)

    by QuantumG (50515) <qg@biodome.org> on Tuesday February 15 2005, @06:51AM (#11675967) Homepage Journal
    Preferably your language has persistence built in. Obviously if it doesn't, *cough* C++ *cough*, you can't do that. In such situations I believe the Memo GoF pattern would be appropriate.
  • by jamsho (721796) on Tuesday February 15 2005, @06:56AM (#11675985)
    Globals are simply a first order abstraction when it comes to storing a program's state.

    They get unwieldly fairly fast - as soon as you start hitting any complexity.

    Try state machines (see GOF) and lots of singleton classes with 'Context' in the name.

    The state machines and context objects can save their own state as the change - and read the state back in as necessary. They can save their state to an in-memory object or straight to a database whether it be a file or otherwise. A 'load' would just work the other way...

    Just how I'd approach it.... (it and about anything else non-trivial ....)

  • Serialize the objects in question... (Score:4, Interesting)

    by stoborrobots (577882) on Tuesday February 15 2005, @06:57AM (#11675989)
    Just implement java.io.Serializable [sun.com]

    From the guide [sun.com]:
    Object Serialization supports the encoding of objects, and the objects reachable from them, into a stream of bytes; and it supports the complementary reconstruction of the object graph from the stream. Serialization is used for lightweight persistence ...


    And serialization has been available from java 1.1 at least...
  • Actually... (Score:3, Funny)

    by orangesquid (79734) <os.udel@edu> on Tuesday February 15 2005, @06:59AM (#11675994) Homepage Journal
    The proper way to do this is:
    int main(int argc, char *argv[])
    {
    Game *theGame = new Game;
    theGame->setup(argc, argv);
    int retval = theGame->run();
    delete theGame;
    return retval;
    }

    Then, theGame can have private variables that are effectively global variables ;)
  • eh? mygame.savemethod()?? (Score:5, Informative)

    by torpor (458) <jayv@NOspAm.synth.net> on Tuesday February 15 2005, @07:10AM (#11676021) Homepage Journal
    Would they create a 'MyObject.savemyself()' method for every object in their game?

    isn't the purpose of 'object oriented programming' that you don't have to think like this? you just call the one big 'Game Object' save method, and .. all other derived/related objects, do their big save?

    seems a bit wonky to me.

    "game saves" is not just a game problem, of course. there are many, many parallels in other types of application .. embedded data loggers, for example, getting a warning that the shack is about to flood for winter, need to save their state too ..

    for me, the 'global context save and restore' is a 'built-in' to the design. i'm rather fond of libs and services which provide persistence natively .. though i think that some would argue that mmap's to flash RAM are cheating ... ;)
  • Single instance (Score:5, Insightful)

    by Bluelive (608914) on Tuesday February 15 2005, @07:17AM (#11676044)
    This only makes sense if you have a single instance. (ie. Singleton) class. Sounds like your a C user lost in OO land
    • Re:Single instance (Score:4, Funny)

      by Anonymous Coward on Tuesday February 15 2005, @07:58AM (#11676159)
      Sounds like your a C user lost in OO land

      Yes my am.

      [ Parent ]
  • easy (Score:5, Funny)

    by DrSkwid (118965) on Tuesday February 15 2005, @07:22AM (#11676058) Homepage Journal
    just ignore the instructor

    there are no rules !

    • Re:easy (Score:3, Insightful)

      Dear Miss Manners:

      My home economics teacher says that one must never place one's elbows on the table. However, I have read that one elbow, in between courses, is all right. Which is correct?

      Gentle Reader:
      For the purpose of answering examinations in y

  • Object-based approach... (Score:3, Informative)

    by genneth (649285) on Tuesday February 15 2005, @07:34AM (#11676086) Homepage
    I'm not a CompSci student -- so I don't know the strict definitions of things, but I think this, below, counts as more of an object based approach as opposed to true OOP.

    The basic idea is that the thing you're trying to do, ie. have saved game state, ought be a first class thing. So have a global singleton that manages this, and have objects register themselves to that class, then most of the boilerplate can be collected in the global object.

    In C++, a better approach would be something like that taken by Boost.Serialization [boost.org], which provide a template (STL style) framework, so that you can plug in different ways to marshal data as well as different output formats, etc.
  • Passcodes (Score:5, Funny)

    by FLAGGR (800770) on Tuesday February 15 2005, @07:36AM (#11676090) Homepage
    Screw save games, just give the user a password to get back to the level. Simple. (Just kidding)
  • whatever works for you (Score:3, Insightful)

    by jeif1k (809151) on Tuesday February 15 2005, @07:42AM (#11676117)
    Don't change something just because someone tells you it's not the latest and greatest way of doing things. If saving the way you do works for you, just stick with it. It has a number of potential problems (portability, maintainability, version and architecture dependence), but if those don't bother you right now, there is no need to change until they do.
    • Re:whatever works for you (Score:4, Insightful)

      by arkanes (521690) <arkanesNO@SPAMgmail.com> on Tuesday February 15 2005, @10:59AM (#11677162) Homepage
      This is the largest load of crap I've heard in a long time, especially directed to someone who's a student. The whole point of being a student is that you learn something, not that you just do whatever until you run into all the problems. Sure, what he has works. But he wants to know if there's a better way. He's been introduced to something that he doesn't understand, and doesn't see how it can make his programs better. So he's asking for some examples. This makes him an intelligent person (unless he's just got an axe to grind against OO and is trying to troll, but I'm assuming good faith and even if he is, this is a lousy example to pick), as opposed to being a reactionary turd who'll refuse to better himself.
      [ Parent ]
  • "Serialize" (Score:5, Interesting)

    by Ninja Programmer (145252) on Tuesday February 15 2005, @07:54AM (#11676150) Homepage
    You simply have to model the the essential game state variables, then create a method for "serializing" them into something that can be thrown out to disk. There is no need at all for these variables to be global, just make sure you pass a "game context" down the call stack to any function which can modify the game state.

    The reason why its important to have this abstraction, is that its required in order to make in-game demos, and to have any hope of writing a networked version of your game. It can also let you do strange things like split screen the game and let two people play independent games if you like (a speed contest, for example.)
  • Configuration Object (Score:4, Insightful)

    by sporty (27564) on Tuesday February 15 2005, @08:00AM (#11676161) Homepage
    Just use a configuration object. I would argue NOT to use a singleton. Come the day you need to migrate away from the singleton pattern, you will have a bit of work. Why migrate away? What if you wished to work with two configuration files at once? I know, it's not normal for all applications, but I've had an instance where one was the old config and one was the new. Short version, config object, no singleton.

    -s
  • Beware of Memory Dumps. (Score:5, Informative)

    by Jason Pollock (45537) on Tuesday February 15 2005, @08:08AM (#11676176) Homepage
    If you use a memory dump save (as it sounds to me) you will eventually notice several things:

    1) The files aren't easily loaded between versions of the software.
    2) The files aren't platform independent.
    3) The files are very fragile, and very dependent on compiler options.

    This is one of the complaints about Word document files - they can contain memory dumps. :)

    However, for simple ease of implementation, nothing beats getting a pointer and writing a block of memory to disk.

    Jason
  • In this case... (Score:3, Insightful)

    by dmayle (200765) * on Tuesday February 15 2005, @08:11AM (#11676186) Homepage Journal

    In the case you mentioned, with each module having global save state, what you might prefer to do would be to create a GameState base class, mostly virtual, with static methods for registering into a list of modules, and for iterating that list to actually save that data.

    In each module specific subclass, you implement the necessary storage, interfaces for the module, and the virtuals for actually performing the save or load.

    With proper helper functions, you can save yourself some code, and avaid any namespace issues. Plus, you'll have a framework that will be easily reusable for the next game you write, rather than having to write it all from scratch again.

  • Serialization (Score:5, Informative)

    by SteveX (5640) on Tuesday February 15 2005, @08:13AM (#11676190) Homepage
    It's called serialization, and most OO frameworks support it in some way or another.

    Usually it's a way for an object to render itself to a stream, and reconstitute itself from a stream.

    That way you can save the objects to disk, or send them over the network, or whatever else you need to do with them.

    Every object serializes itself, and all of it's immediate children. Once every object does this, you can save the whole tree of objects with one call.

  • Are you serious? (Score:5, Insightful)

    by ttsalo (126195) on Tuesday February 15 2005, @08:28AM (#11676219)
    You save settings/games by essentially dumping data straight from memory to disk? How large projects have you implmented this way? How do you figure out the right parts to write/read? How you ensure that the program memory (data segment, stack and heap) will all be in a consistent state after a load?
  • by wowbagger (69688) on Tuesday February 15 2005, @08:53AM (#11676297) Homepage Journal
    Summary of your technique:
    Declare various global variables.
    Save game state into them.
    On Save Game, write block of memory out.

    How can this fail, let me count the ways:
    1. Layout of global section can change from link to link due to project changes, thus making saves version dependant.
    2. Non-save game global data can exist between save items, bloating save file.
    3. You can get the size of the save block wrong, and end up not saving the data you need. e.g. you use &foo and &bar as your start and end pointers, but due to a change there are variables after &bar that need to be saved.
    4. If the items you are saving become full-blown objects with compiler generated information (virtual function tables) you are overwriting that data with possibly incorrect data.
    5. No error checking in file - so a recovered file may screw the game up.

    And that's just what I can come up with before my morning coffee.

    Look, I disagree with your instructer about "global variables are NEVER needed" - what, then are stdout/stderr/stdin/cout/cin/cerr, if not global variables?

    However, global variables are like salt - a little may be needed, but too much will raise your blood pressure.

    Again, this is before my morning coffee, but here's a couple of techniques that are better:
    • Define a function to allocate a block of "saved space" - sort of a "save_malloc()" function. Allocate the objects you wish to save via that function. In that function, you grab a block of N bytes at initialization, you initialize an end pointer, and you "malloc" by moving the end pointer (no freeing allowed). You now know exactly what you need to save. You can also write a version # at the beginning of the file, and you can compute a checksum of the data. For OO types, you can fancy this up by writing a "Save_game" base class, and implementing new() for that base class.
    • The more OO approach: Implement a "Save_game" base class. The base class implements a linked-list, with a static member as the head pointer. The class has Register/Deregister functions, and a pure virtual Size() method. Derived classes implement Size() { return sizeof(*this);} and call Register in their ctor. To save game, walk the list. This also allows you to save the size of the object (even better if you use RTTI, you can save the actual type of the object), and to checksum each object.

  • Not "under any circumstances" eh? (Score:4, Interesting)

    by snorklewacker (836663) on Tuesday February 15 2005, @11:43AM (#11677618)
    Here's the deal: when your professor tells you that you're never ever ever ever ... ever ever ever ... ever ever (etc) supposed to do something, then as the poster who quoted Miss Manners mentioned, you've got a pretty good idea what the answer to the exam question is. Any professor who's been in the real world (usually people who retire into teaching) will tell you that there's exceptions to any rule.

    Games, with their relentless demands for resource efficiency, will have you breaking lots of rules. Game saves are one of the first walls a junior game designer hits. They've written this fabulously interesting game, unpolished of course, but it's got real potential. But the saved games are two megs each, and take 15 seconds to write out. There goes your console version. You now have to start cutting all kinds of corners to get those save times and sizes down, and that may mean a sacrifice of architectural purity.

    To wrap it up, you probably do not want to blindly serialize all your stateful objects into persistent storage and leave it at that. You can and probably should do that while developing the game (be sure to version your objects while you're at it), but when you need to get efficient, you need to start relentlessly trimming the "serialized" form, and seeing what you can build up, recreate, or even just leave out (e.g. a save game in a RTS probably doesn't need all the scorch marks saved). Then instead of serializing to a stream to persistent storage, you want them to simply notify a "state container" with a reference to themselves (the container can egregiously violate encapsulation -- use inner class adaptors or private inheritance if you're paranoid) and that container can index into a memory segment. Then you just write that segment out to disk. Version the damn thing, so if you patch the game, you're not completely hosed. Keep in mind that you're getting RAM 4K at a time, and writing it to disk in bigger chunks, so don't be too stingy.

    Now go do the rest of your homework yourself.
    • Re:how about.. (Score:3, Informative)

      If you are initiating a save by pressing save, you may as well save what your variables are at NOW.

      You don't need to log changes as they occur, like a journalled fs does, unless you wanted to eliminate the need to press save, and have everything save auto
    • Re:how about.. (Score:5, Informative)

      by vinsci (537958) on Tuesday February 15 2005, @07:49AM (#11676137)
      You mean object prevalence like in Prevayler [prevayler.org]? See also here [froglogic.com] for a general presentation (intro here: Object Prevalence in C++ [kde.org]).
      [ Parent ]