How to provide Savables with context?

I am trying to setup a saveGame system. The information I need to save is in several classes that I made savable. Some of these classes are children of other classes.

To rebuild the instances, I need to make them aware of their parent class. How do I pass information on the just newly build parent instance to a child instance?

Example: my brain class could have a read-method like this.

    public void read(JmeImporter im) throws IOException {
        InputCapsule capsule = im.getCapsule(this);
        
        for (int i=0; i<nofNeurons;i++){
            neurons[i]=(Neuron)capsule.readSavable("neuron_"+i, new Neuron());
        }
    }

And the neuron class has this read method:

    public void read(JmeImporter im) throws IOException {
        InputCapsule capsule = im.getCapsule(this);
        
       //---------------------
       // READING OTHERSTUFF
       //--------------------
       for (int i=0;i<GLOB.CONNECTIONS_PER_NEURON;i++){
            float weight=capsule.readFloat("c_"+i+"_w",0f);
            int sourceIDX=capsule.readInt("c_"+i+"_IDX",-1);
            if (sourceIDX<-1){
                   
            }
            //HERE I WOULD NEED TO KNOW ABOUT THE PARENT
            Neuron source=brain.neurons[sourceIDX];

            addConnection(source,weight);
        }

    }

Unless you need to save JME spatials (and even then), you will experience nothing but pain with JMEā€™s savable system.

You are much better off using regular Java serialization evenā€¦ which has solutions for all of these problems and more.

You might also consider using GSON to save things as JSON so that they are in an editable formatā€¦ but for many types of things this will be very verbose and may require lots of custom serializers to make work nicely. Just depends on your data.

In any case, if you are only saving custom classes, JMEā€™s savable stuff is just awful. Only worth dealing with at all if you are saving lots of already Savable data and have no other choice. (Though in some of my serialization I use JMEā€™s BinaryExporter to turn it into bytes so I can serialize it with Java serialization just so the rest of my tree can still use regular Java serialization.)

Edit but specifically regarding your question:

ā€¦wouldnā€™t the ā€˜child classā€™ already have access to the already read parent state by nature of being the actual object that those things were set on? Or do you mean parent/child containers?

For parent/child containers you will have to have some sort of ā€œfixupā€ stage where you allow everything to find each other again. JME had to do this a lot with many of its classes.

Edit2: which note, you generally would not have to do with Java serialization because the whole graph is saved and reconstituted.

2 Likes

I also went down this path and found serialisation a double edged sword (i used kryo rather than javaā€™s serialisation but i think itā€™s the same deal). A problem i found was that upgrading a saved game file to a new version was a big problem. If a class that was serialised needed to be updated to include new members the save games would become incompatible.

In that game I created generic state holders that never changed, but that meant giving up a lot of the convinience of serialisation. In my new project Iā€™m going for all json. Too early for me to comment on if that was a good decision

No, there is a way to solve this problem and itā€™s essentially the same as for JME.

The new fields get default values for their typeā€¦ then you override a method to initialize them to something. If you canā€™t figure out something backward compatible to initialize them to then a serialization mechanism was not your problemā€¦ no serialization will be able to manufacture values that you as a developer donā€™t know what they should be.

Iā€™ve used Java serialization for things for 20+ years and never had a problem adding/removing fields from classes. Iā€™ve even been able to move classes from one package to another and kept compatibility. You just have to know the serialization classes well.

Itā€™s quite powerful.

That approach seems to require keeping all the old classes hanging about (marked as deprecated and not renamed) and a bunch of migrators.

Not saying it canā€™t be done, just that its a pain. Perhaps Iā€™m unnecessarily allergic to having ShipData, ShipDataV2, ShipDataV3, ShipDataV4

Just to add some new fields? No, definitely not. Itā€™s nearly automatic.

To move them from one package to another? Not really necessary but itā€™s definitely the easiest way. But itā€™s also super duper rare.

Whatever the case, if you use Java serialization then you really need to specify your own serial version ID for all of your classes or you create future problems for yourself. Though it can be done retroactively if you forget.

Edit: and note that JMEā€™s serialization will also be really unhappy if you switch packages and there is essentially nothing at all that you can do about it. Youā€™d have to deal with conversion externally.

Ok, the solution I ended up with is my own file format that stores information about the data structure with the data without getting very verbose.

In large my steps to save a game is this:

  • define the data structures that will be used (I only use int, float, String and Enum for now)
  • Open a binary file stream
  • Write the structure
  • Write an ordinal-mapping for the enums used
  • Write the data by filling records of the defined structure and send them to the file.
  • Every block in the file - structure defs, enum-maps, the different data groups - starts with a block-indicator. The last indicator is [END].

To read it all back, I loop through all the blocks in the file.

  • The structures - or record definitions - come first.
  • Then the Enum-mappings
  • Then every data block is red per record
  • When we hit [END] we are done.

This allows me to add and remove fields from the data. It took me a day to get this working, but it seems pretty self-repairing when something unexpected is encountered. I now use it to store trained brains, and on a functional level, the brains that are loaded behave exactly the same as the ones that were saved. Even if I change the number of neurons or connections, or add fields.

1 Like

Note: if youā€™ve never looked at GSON before then you might want to give it a glance. If you are already willing to shuffle data around then potentially you could just move your data into special ā€˜configā€™ classes that get serialized to JSON and then do the reverse on load. Iā€™ve done this many times.

The benefit is that it can support things like enum, etc. automatically along with all of the other types while also being human readable/editable if you just need to tweak something in a saved file.

Edit: unnecessary followup:

Java serialization also trivially allows this.

1 Like

The human readability could be a plus indeed. But JSON is very verbose. If I need to store a set of 200 brains, whith 200 neurons each, with all of them using 10 connections, that would create gigantic files.

My current solution is a nice middle ground. When I open the file in a text editor, I can read what structure the file is - i.e. if it is missing fields or blocks, or something like that. The loader code can spit out detailed info on the data it reads.

So I think it is fine for now.

1 Like