[Solved] Saving (and loading) a game

How could one go about saving a Zay-ES architectured game? Are there any built-in functionalities I don’t know about? I would imagine that outside of the entities I have only very little additional data that needs to be serialized to a save game file.

Here is how I would have gone about this:

  • Use the jME binary exporter format (this though I guess requires the most amount of manual coding)
  • Pause game loop
  • Write any additional state data needed (like a render thumbnail for the save file and quick header data for information, game time etc)
  • Write all the entity data

How to load the game? This is kinda directed to Zay ES itself. I have EntityIDs stored in entity components as a relation, so the IDs need to be persistent when I serialize it back. How to go on with these?

You can use Zay-ES persistence API to save persistent components to the SQL database.

You need to implement PersistentComponent interface

and replace “EntityData” with “SqlEntityData”.

You also need to add the HSQLDB jar into Gradle dependencies.

1 Like

Thanks. Although this is not exactly what I’m looking for. Looking at the classes it looks like I have to do some manual labor to replicate the PersistentEntityIdGenerator and related classes.

I’m looking specifically to save and load to a disk file (well, to a stream but I’m sure it is a file 100% of the time) on user request.

Can you elaborate on your use case, please?

Why?

Not sure if I am understanding what you mean but HSQLDB will already save it to a file.

To me this functionality looks like it is keeping a persistent state of the game at all times in a database. Maybe that is suitable for some game server that people just return to, play and leave. For me this feels like an overhead.

What I have is a traditional self hosted game with single and multiplayer (RTS of sorts). And my use case is that the player goes to the game menu and saves the game. Multiple save game files can be entered. And loaded. User triggers these. Surely there can be a autosave function but I don’t need to keep the game state up-to-date anywhere else than in memory (the current simulation) until the user decides to save the game.

Surely I could use an embedded database to store this. But I rather not to. I feel it adds some overhead too, managing the connections, transactions, tables and datatypes. Although a file storage has similar constructs too. It feels lighter to serialize to a binary/JSON/whatever. I don’t need keys, views nor indexes. All data is written and read fully at once, not queried ever.

PersistentEntityIdGenerator is kinda what I need. It just uses the HSQLDB. I just feel I don’t want to use a database :slight_smile:

HSQLDB is a pseudo-database in the sense that it is embedded. So your fear of “connections” and stuff is not really accurate. There is some SQL overhead but it’s light and the zay-es persistence layer relies heavily on precompiled statements. It generally keeps the data in RAM and flushes to disk periodically.

One strategy would be to let the running game use a working hsqldb and then “saving” is to flush the caches and then copy (and perhaps zip) the hsqldb to a save file.

That being said, it would not be hard to adapt the approach SqlEntityData uses to a custom implementation that just holds the stuff in RAM and flushes the components to a file on demand. It’s just a bunch of hash maps in the end.

I’d caution against using JME’s save/load stuff, though. Java’s built in serialization is way more powerful. The only single thing JME’s capsule stuff has going for it is being able to target XML or binary…and apparently the XML isn’t fully working anyway. And with JAXB, one could pretty trivially get a Java serialized hierarchy mapped to XML anyway (or use GSON to go to JSON files.)

But by all other measures, JME’s save/load stuff is very crude and overly verbose.

2 Likes

Yeah, I actually somewhat love HSQLDB. It is very convenient. I have been using it at work for years (although never in production).

This would be the preferred approach. Should be easy to follow this as you and Ali suggested.

I’ll try to whip something up. Probably a binary format that has these sections (header, ES data, other game data) in various other formats, whichever is the easiest for a lazy man to get serialized. And quite freely to deserialize without worrying too much about save file versioning.

You just described Java’s serialization almost exactly, by the way.

Add a serial version to the top of your persistent classes now… worry about it later when it’s a problem. (I find a predictable serailization version easier to deal with than the automatically generated one.)

Structure your data into one class as you like it… that implements Serializable. Then send that to an ObjectOutputStream.writeObject() method. Read it back from an ObjectInputStream.readObject() method. Done.

Just make sure your components implement Serializable and your fine.

My one failing in all of this is that PersistentComponent doesn’t already extend Serializable. Or at least that the existing built-in components that implement PersistentComponent don’t also implement Serializable. (It’s possible to work around this with Java serialization, though.)

Any other approach you take is going to have more setup than ObjectInputStream/ObjectOutputStream… almost guaranteed. JME’s save/load support being the most verbose and most work of all options.

1 Like

Thanks, I just always want to make it so that I can read a header of sorts without serializing the whole file yet. For displaying information about the save file, the rest on demand then. Major business requirement :slight_smile:

Yep, I like a similar amount of control.

Ergo:

    public static void store( BlockTypeData data, OutputStream rawOut ) throws IOException {
        try( ObjectOutputStream out = new ObjectOutputStream(rawOut) ) {
            out.writeLong(formatVersion);
            out.writeObject(data.types);
            out.writeInt(data.badTypeIndex);
        }
    }

Add whatever other header info where I’ve stored formatVersion. It could be read without reading the rest.

Edit: and note that the writeLong() is doing nothing special. 64 bits written right to the first bytes of the stream.

1 Like