Saving, loading—oh no! Semi-tutorial?

We were having a bit of a conversation yesterday, you two people who know who you are (and most certainly will respond to this thread) and I. It’s about time we all have an in-depth conversation—or discussion—about various different techniques as for saving and loading our games, depending on what type of a project it is. It’s also highly recommended that tiny code examples are provided should you choose to answer, for the less intelligent people of the future. ::slight_smile:

I’ll start off by going over a rather simple example, then you (other fine people) go ahead and discuss what can be done better, alternatively worse (or a combination between the two, ultimately tricking people into doing worse). 8)

This is probably one of the easiest ways there are in order to save things:


try
{
	// Load file
	FileOutputStream fos = new FileOutputStream("player_left_leg_save.arm");
	ObjectOutputStream oos = new ObjectOutputStream(fos);
			
	// Write to file
	oos.writeObject(987); // 987 is part of the fibonacci sequence—it’s necessary!
	
	// Close object output stream
	oos.close();
}
catch (IOException e)
{
	e.printStackTrace();
	System.exit(0);
}

Should one wish to load objects rather than save them, they may use the ObjectInputStream, as well as the FileInputStream, whilst also using the readObject method located on the ObjectInputStream.

I personally have slightly different methods when saving and loading certain things—for instance the loading and saving of gigantic three-dimensional worlds. The above example would be good for saving (and loading) of anything that doesn’t require all too much performance. It would even be alright to use it if your world is static and doesn’t require to dynamically load as the player walks around. :point:

But that’s enough from me for now—I’ll fill in with more examples as this thread goes on and the discussion rages on!

Perhaps there’s a way of saving and loading you’d like to share with the community?

I use Java 8 try-with-resources syntax. And Eclipse formatting tags to stop it being autoformatted nastily. And a GZIP, and a buffer.


//@formatter:off
try (
		FileOutputStream fos = new FileOutputStream(file);
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		GZIPOutputStream gzos = new GZIPOutputStream(bos);
		ObjectOutputStream oos = new ObjectOutputStream(gzos);
	)
//@formatter:on
{
	oos.writeObject(theThingy);
}

Heh, forum code formatter uses 3 spaces for tabs - the work of Beelzebub!

Cas :slight_smile:

In my opinion, serialization is a dangerous thing to use for saving games.

Consider the case that you released your game that is using serialization of the game stats object. You have fields such as this.


public class Stats
{
    public static int levelReached;

    public static int numVehiclesDestroyed;
    public static int numCarsImmobilized;
    public static int numCarsImpounded;
}

You are saving this as your game save. Good, but then in a next version, you decided to refactor numCarsImpounded into numCarsCaptured or something for instance. When you release this version, you’ll also introduce a bug, a major issue. All the existing player’s game saves will be lost as serialization will fail.

You might also have save game on exit, and that even destroys all the progress the guy has made. I even faced this issue in a college project, and this was my good lesson.

For this purpose, I’d never consider serialization for save games. Instead, my choice would be to go with JSON or XML. Have predefined JSON structure, and you can also upgrade existing save games to newer JSON structure. If you fear that player may alter the stats, go implement a custom file format, maybe even binary.

@SCH
JSON and XML can both be great ways as for saving, but consider the time you might need to save a game world such as the ones in various major voxel games. All of a sudden your best bet would definitely be to save everything in the form of bytes—only saving the things that are actually necessary. We’re looking to get small files and fast loading performance. :point:

But for most cases, JSON and XML would be very much sufficient! Most applications don’t have way too many things that need saving. :slight_smile:

@princec
I’ve actually missed the try-with-resource statement completely—fine notice! Finally something new to absorb! ::slight_smile:

Robot Farm actually uses both serialization and more traditional saving systems. We use the traditional saving for the world, but the player profile uses a serializer. We had problems at first, but if you learn about how serialization works, and use the tools Java offers for it, there’s a lot of ways to fix problems that you may run into. I personally won’t be using serialization for game saving anymore after Robot Farm, though.

Perhaps someone could go benchmark some attributes of the different methods there are to save and load things in the Java standard library? I don’t think I’ve read a thorough benchmarking of that before. ::slight_smile:

I’ve successfully been using Java serialisation to save and restore games for over 10 years… it does work just perfectly fine within its (very lenient) restrictions. I’ve even actually been able to serialise game state on Windows and restore it on an Android device!

The thing about serialisation is: it works, it’s fast enough, it’s extremely reliable, it’s comprehensive, it requires almost no effort, and often the things you can break in serialisation are the things you have to manually deal with in any other format anyway. Don’t waste time now fixing problems you think you might have one day in the future - it may never happen, kid! Just use serialisation and move on, and when you’re famous and rich enough that 10,000 people complain on the Steam forums that you broke their savegames in a patch, then you can think about replacing it with something else.

Cas :slight_smile:

Spent my working life with database applications. So I am using the integrated JavaDB (a “real” SQL database) together with MyBatis (a lean ORM mapper like it should be).

This has to be solved by testing your load/save code and proper implementation of readObject() or readResolve(). Like Cas said - it would be the same, just in a different place, if you load and set values from xml/json files.

If you want to have some readable format, you can also go with the likes of xstream instead of plain java serialization.

Just make sure, you implement some kind of self contained model for your savegame, so that you don’t accidently safe the whole memory image of your game :wink:

[quote=cylab]Just make sure, you implement some kind of self contained model for your savegame, so that you don’t accidently safe the whole memory image of your game :wink:
[/quote]
No—let’s implement the Serializable interface in every single class of the game and load it! That’s definitely the easiest way to do it! :point:

Sarcasm put aside, however. The problem I probably run into the most when saving entire objects is the files becoming extremely large—even if the files contain its various different fields in the form of only bytes and booleans.

Perhaps anyone has some recommendations on as to how one might minimize the size of the files, using Java’s standard library’s Serializable. Would it maybe be possible to somehow only save the important bits of a class and leave out the other bits? Or contain… I don’t know what I’m talking about… stuff inside some mysterious way that just works? ::slight_smile:

As I said, use a datamodel. For derived data, use transient and recompute the field in readObject() or readResolve().

Btw. the danger of serializing a memory image is real when you use libs like xstream, that don’t require classes to implement Serializable.

Use the aforementioned GZIPOutputStream for starters.

Secondly, how large is large?

Thirdly, it is probably the case you’re serialising stuff that’s not actually part of your game state but you’re accidentally referencing it in non-transient fields. The big culprit is referencing “static” game resources in your game state, eg. the world map. This is where you need to get clever with your use of transient, readObject/writeObject/readReplace/writeReplace. But getting this wrong probably will also crash your game on a restore anyway.

You will find that you need to use a compression scheme and deal with what is actually being serialised whatever method you use to serialise object graphs if you want the data to be smallish and work properly - Java serialisation just does all the tedious boilerplate for you in a (IMHO) brilliantly simply manner and lets you move on to tackling more interesting problems elsewhere. I hear a lot of my peers whining incessantly about how hard/tedious/error prone their save/load game function is in any other language and it’s always made me chuckle when I’ve solved it forever in Java with 5 lines of code either way.

Cas :slight_smile:

I’m saving a whole lot of world data—the files are at about 100kB in size, each as for a 15 x 15 x 89 chunk of tiles. Saving one single of those isn’t a problem, rather, the problem emerges when constantly saving hundreds of them upon walking around in the world. :persecutioncomplex:

To be fair, I probably just need to go over accidental references that somehow ended up inside the files, as you said @princec. GZIPOutputStream definitely is an alternative—the only thing that would worry me about using that is the performance when files need to be compressed prior to being saved. I’ve got quite a lot of things going on inside this game, performance is thus quite a concern. :slight_smile:

Performance is only a concern when it’s concerning.

-Xprof

Cas :slight_smile:

Oh @princec, performance already is an issue. :point:

There are a lot of things that are concerning when it comes to this game. :slight_smile:

What I’m getting at, is measure it before you go trying to make it fast.

Cas :slight_smile:

When I think about implementing saving and loading, I am also including things like figuring out how to set and update a default file folder. Is it so obvious how to do this? I would include this aspect in a tutorial. The short answer is that this can be provided via util.Properties, but I probably overthought it and got a little complicated with coding the default to be updated whenever a new file folder is used. Also, tutorial would include how to set up a MenuBar correctly with a save/load and import/export. Making space for a MenuBar on a game screen (if you use one) can be annoying. Also, little things like tracking high scores or other user properties for different users might also require management and all this is separate from storing the state of a game.

As for storing game state, for the simple stuff I am doing, XML is working. I went through a bit of a quandary before deciding what forms to use for incoming and outgoing. I got scared off of using Serialization, in part because of a nightmare story from a friend working on a contract on a Java project with faulty Serialization that was crashing the program, and not wanting to maintain or upgrade “older version” save files. (At least with XML, you can always inspect the contents easily, even if you can’t load the files.) But from what princec says, a lot of the fears about Serialization are overstated, and it actually works fine.