Object-Oriented 'Save Game' Techniques?
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?"
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 automagically. This may be impractical, depending on the number of variables and states you are saving in your log.
i hate pansy republicans
Would they create a 'MyObject.savemyself()' method for every object in their game?
.. all other derived/related objects, do their big save?
.. embedded data loggers, for example, getting a warning that the shack is about to flood for winter, need to save their state too ..
.. though i think that some would argue that mmap's to flash RAM are cheating ... ;)
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
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
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
; -- the corruption of government starts with its secrets. a truly free people keep no secrets. --
You mean the memento pattern, right?
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, 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.
You mean object prevalence like in Prevayler? See also here for a general presentation (intro here: Object Prevalence in C++).
Trusted Computing FAQ | Free Dawit Isaak!
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
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.
Boost has boost.serialization which takes care of such things as pointers. Check it out.
// ville
Personally, I have never seen a functional or procedural technique which could not easily be translated into OO. I've seen lots of OO features that can't be translated into functional or procedural, if only because the lack of language support makes using them that way awkward. So in my (admittedly limited) experience, OO is objectively better. I wouldn't make that as a general claim, of course, but I will make a general claim that encapsulating something like global state as an object is superior to using it as a bunch of global variables. Even making a single global struct would be better.
Interesting you should mention that. Bungie used to be a big fan of this technique, which they used in all of their Marathon and Myth games. The game basically just recorded the player's actions, and saved this record into the file when you hit save. Restore was a matter of loading the level and then replaying the save file until the end. How they managed not to break save files every time they released a new version, I will never know.
One big advantage of this approach is that you can use the exact same code to create game films. Create a file the exact same way, but display the results when you play it back, and presto! Bungie did this for Marathon and Myth as well.
The big disadvantage is that you have to be absolutely sure that the game will play back exactly the same way every time. As far as I know, the Aleph One open-source version of the Marathon engine has never, and probably will never, read normal Marathon saved games or films because of subtle differences in how the engines operate.
Mod down posts with a "Free Mac Mini/iPod" sig, they're spam!
Basically, keep all your game state in a large array of entities:(BTW, the scheme above has certain distinct advantages over freely new/delete managed objects, via object pooling)
When it comes to persist, simply rewrite all the internal pointers to other entities as indicies and dump the entire list of entities to disk. Loading from a save file is merely the same operation in reverse: translate offsets into pointers.(or merely stuff the base array pointer into the save file and just retranslate on load)
The tradeoff is a quick save/load feature for less flexibility in your game entity scheme (relatively inflexible for oop, but can be done). Also, you'd need an array for every base type you wish to mantain in a proper graph.
For those of you who haven't seen this before, check out Effective C++ by Scott Myers (or Meyers; I don't have it in front of me)
Depending on your language of choice. Container-based singleton management is an excellent design pattern. In Java, spring and picocontainer manage setter / constructor injection of singletons; possibly even hiding the fact that some objects are session-scope, request-scope or even non-singletons.
The code is managed by an XML file (or some external configuration); you get the effect of singletons, but the extensibility to swap out which implementation of the interface/base-class you use in which environment. And as you expressed concern, you don't have to worry about running multiple applications with isolated singletons. The component-manager is an instance variable (not a singleton), so you can have multiple isolated environments within the same application.
Makes testing a LOT easier too. If you use abstract / interface classes everywhere, you can easily swap out mock-instances for testing purposes. (having a testing-environment component-manager configuration file).
I'm speaking from the Java side, but the basic concept should be applicable to C++ if there aren't already implementations.
-Michael
Next, create a utility class that contains functions that can read a file up to a marker and return what was read, and another that can identify a marker and jump to the appropriate class. When you do the save, tell the game to save by storing simple objects in order and then passing the save call to the complex objects in order. Those objects will do the same thing, delimiting their objects by bracketing them. Then when restoring, read to each tag and jump to the appropriate class constructor. In effect, you're crawling the tree of objects that are being used for the game.
The advantage of this method is that you store exactly what you need to store, adding game objects is a breeze and almost append themselves to the game tree, and if you expand your game you can tell the reader to ignore tabs it can't identify and work around them, meaning that your older save-files won't break the newer version.
One other thing to keep in mind is that make sure that no constructor actions take place until everything is loaded, so that you don't have say player objects trying to render on a board that hasn't been constructed yet. Make sure you have both a constructor and an initializer funciton for all of your game objects.
First of all, the guy is talking C++, not Java.
Second of all, serialization in Java is not as simple as just saying 'implements Serializable'... In some cases it can be this simple, but if you are doing anything with controlled object construction, such as singletons or the typesafe enum pattern, or if your objects hold onto things that don't make sense to be persisted (such as network connections, open files, etc), then you are going to have to do some special things.
Take a look at the ReadResolve and WriteReplace methods, the Externalizable interface, and for Gods sake, Read the relevant parts of the book 'Effective Java' by Josh Bloch before doing anything with serialization.
First, I'd like to thank everyone for all the well-written responses I've received to my question, but...
I think that in some instances the crux of my problem may have been overlooked. The title of this post may have been somewhat misleading (my fault), but I was really more concerned with the avoiding code bloat aspect of my question than the "how to save a game state" part, all while maintaining object-oriented methodology. Some people seem to consider this a silly question, as the object-oriented methodology supposedly allows any technique you want. I beg to differ. When I started out programming many years ago (writing BASIC on an Apple II), I instinctively wrote top-down, procedural, and somewhat structured code that had very few GOTO statements (maybe some when I had mismanaged line numbers). The methodologies I just mentioned do allow for any technique you choose to employ, and can rival the efficiency of any of the spaghetti code that came before. I have never found the same thing to be true for object-oriented methodologies.
Some of my concerns about the techniques mentioned:
The singleton technique seems to be a politically correct label for a global data structure. I don't think my ex-instructor would approve (but seriously, it does seem to be somewhat of an anti-oop construct).
The object serialization technique is basically a sophisticated term for the MyObject.savemyself() that I mentioned at the top of the post. Of course the game data would need to be serialized, rather than letting objects write randomly to the save game file with a brief header describing who had done the writing. My overriding concern with this technique is that it would involve so many function calls for a game that included say, 40,000 objects. My secondary concern would be that depending on the class hierarchy, many different versions of a save game state function would have to be employed. While one post claimed that a universal base class would be able to write the game state out for every derivation, I cannot believe it. Derived classes will always have extra features that affect the game state which they will need to handle themselves (their parents and children can't do it for them).
After spending 15 years trying to clearly understand the object-oriented philosophy (even taking a class at my age), it occurs to me that the object-oriented hype seems to be an attempt at scaling down the operating system philosophy to fit single applications. Objects are "mini-programs" that are supposedly decoupled from the surrounding super-program and protected from dangerous and misbehaving code in other modules (of the same super-program). Funny that after 20 years of procedural programming, I have seldom beheld any of the claimed symptoms of not using object-orientation.
I think the industry went in totally the wrong direction. We should have been fixing our operating systems to more closely mimic the single application philosophy...
But I would still be interested in hearing about a truly efficient, simple, elegant, and nearly code-less object-oriented solution to the save game state problem.
email: aofi7@hotpop.com