Saving/Loading a Static Anonymous Inner Class

I have an anonymous inner class of a factory for each of my block types in my puzzle game. What I am trying to do is, on the save of a level, save these static final instances into a j3o. This works.
However when I try to load them I get an error because the importer tries to instantiate a new object of these classes.

For example, here is the actual factory interface:

package Engine.Blocks;

import Engine.Main;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.Savable;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;

public abstract class BlockFactory<T extends BlockSuper> implements Savable {
    
    public abstract T create(Main main, Vector3f pos, Node level, boolean creating);
    
    public void read(JmeImporter ex){
        
    }
    
    public void write(JmeExporter ex){
        
    }
}

As well as one of the inner classes:

    public static final BlockFactory<Block_Wall> wallBlockFactory = new BlockFactory() {
        public Block_Wall create(Main main, Vector3f pos, Node level, boolean creating) {
            return new Block_Wall(main, (int) pos.x, (int) pos.y, (int) pos.z, level, creating);
        }
    };

So I try to load them up and it, naturally, tries to create a new instance of the wallBlockFactory, which you will see that I put as the default in the following code (From Level, which implements Savable) :

    public void write(JmeExporter ex) throws IOException {
        BlockFactory[] xDef = {wallBlockFactory};
        BlockFactory[] yDef = {wallBlockFactory};
        BlockFactory[] zDef = {wallBlockFactory};
        BlockFactory[] xBlocks = new BlockFactory[(int) levelSize.x];
        BlockFactory[] yBlocks = new BlockFactory[(int) levelSize.x];
        BlockFactory[] zBlocks = new BlockFactory[(int) levelSize.x];

        for (int i = 0; i < blocks.length; i++) {
            xBlocks[i] = blocks[i][0][0].getFactory();
            yBlocks[i] = blocks[0][i][0].getFactory();
            zBlocks[i] = blocks[0][0][i].getFactory();
        }

        OutputCapsule capsule = ex.getCapsule(this);
        capsule.write(this, "level", new BatchNode("blocks"));
        capsule.write(xBlocks, "xBlocks", xDef);
        capsule.write(yBlocks, "yBlocks", yDef);
        capsule.write(zBlocks, "zBlocks", zDef);
        capsule.write(levelSize, "levelSize", new Vector3f(100, 100, 100));
    }

    public void read(JmeImporter im) throws IOException {
        InputCapsule capsule = im.getCapsule(this);
        levelSize = (Vector3f) capsule.readSavable("levelSize", new Vector3f(100, 100, 100));
        BlockFactory[] xDef = {wallBlockFactory};
        BlockFactory[] yDef = {wallBlockFactory};
        BlockFactory[] zDef = {wallBlockFactory};
        BlockFactory[] xBlocks = new BlockFactory[(int) levelSize.x];
        BlockFactory[] yBlocks = new BlockFactory[(int) levelSize.x];
        BlockFactory[] zBlocks = new BlockFactory[(int) levelSize.x];

        xBlocks = (BlockFactory[]) capsule.readSavableArray("xBlocks", xDef);
        yBlocks = (BlockFactory[]) capsule.readSavableArray("yBlocks", yDef);
        zBlocks = (BlockFactory[]) capsule.readSavableArray("zBlocks", zDef);


        BlockFactory[] copyBlocks = new BlockFactory[xBlocks.length + yBlocks.length + zBlocks.length];
        System.arraycopy(xBlocks, 0, copyBlocks, 0, xBlocks.length);
        System.arraycopy(yBlocks, 0, copyBlocks, xBlocks.length, yBlocks.length);
        System.arraycopy(zBlocks, 0, copyBlocks, xBlocks.length + yBlocks.length, zBlocks.length);

        BlockFactory[][][] toLoad = new BlockFactory[(int) levelSize.x][(int) levelSize.y][(int) levelSize.z];

        int index = 0;
        for (int x = 0; x < toLoad.length; x++) {
            for (int y = 0; y < toLoad[0].length; y++) {
                for (int z = 0; z < toLoad[0][0].length; z++) {
                    toLoad[x][y][z] = copyBlocks[index++];
                }
            }
        }
    }

What I am trying to achieve is the ability to just be able to create factories and blocks types and have them load in dynamically. Since there is no data that can’t just be regenerated with a new instance of a Block class I am trying to just save the factories.

I realize that I could create enums or string id’s and have a bunch of if statements in the read method, however as I said I would prefer for it to be dynamic.

Thanks for any help guys.

Using anonymous inner classes for this will be dangerous since adding a new block type could potentially change all subsequent names. You’d have to be really careful to keep them in order, always… and hope Java never changes it’s anonymous class name generation policy.

Or you could just make them real static inner classes. JME should have no problem with that.

Real static inner classes as in?

I have my definitions all messed up from a lifetime of not learning the real names of things in Java. I just call most things… things… that do… things. It makes sense in my head anyways.

[java]
public class MyClass {

public static final Whatever SOME_CONSTANT = new MyInnerClass();

public static class MyInnerClass implements Whatever {
}

}
[/java]

1 Like

See how it’s public and static and inner to the other class? Maybe that helps you remember that it’s called a public static inner class. (no sarcasm intended)

Awesome it works spectacularly now, thanks!

@Slyth said: Awesome it works spectacularly now, thanks!

Glad it worked.

Note: the public static final constant thing was just to get it close to what you were already trying to do. It isn’t a necessary part of using a static inner class. Just thought I’d mention that.

I thought I’d ask here instead of creating a whole nother thread because this is on the same thing.
I’ve been struggling for days now trying to get this thing working.

It appears to save the game as null. When I print out

(Level) main.getAssetManager().loadModel(".slythGame/saveGame.j3o")

it prints out null. I haven’t a clue why. I’ve disabled loading the j3o and just loaded a normal level that I coded by hand and saved that. Then I try and load it up and still prints null.

My save code is very simple:

        String userHome = System.getProperty("user.home");
        BinaryExporter exporter = BinaryExporter.getInstance();
        File file = new File(userHome + "/.slythGame/" + "saveGame.j3o");
        try {            
            exporter.save(editorState.getMap().getLevel(), file); //Level class implements Savable
            Util.log("Saved game: " + editorState.getMap().getLevel());
        } catch (IOException e) {
            e.printStackTrace();
        }

And inside my Level class I save all the factories and stuff.

To explain my hierarchy, I have the superclass Level and a few subclasses which override the createLevel() method to make the level by code. Here’s an example of a short and simple level in code:

public class EditorLevel extends Level {

    public EditorLevel() {
        super();
    }

    public void setUpLevel() {
        setLevelSize(90, 90, 90);
        makeFloor(10, 10, wallBlockFactory);
        makeBlock(2, 1, 2, wallBlockFactory);
        makeWall(new Vector3f(0, 1, 0), new Vector3f(5, 4, 0), wallBlockFactory);
        addLight(1, 3, 1, ColorRGBA.White, 10);
        addPhysics();
    }
}

So it’s pretty simple. However to code large levels by hand is a pain which is why I’m trying to create a way to make them in the editor (which I have working) and then actually be able to save and load them.

I saved that EditorLevel up there and then tried to load its j3o and it returns null (EditorLevel).

I’d really appreciate any help, it seems like I’m so close and it’s rather frustrating.

Edit:
Just in case you need to see them here are the write and read methods:

    public void write(JmeExporter ex) throws IOException {
        OutputCapsule capsule = ex.getCapsule(this);

        BlockFactory[] facs = new BlockFactory[blocks.length * blocks[0].length * blocks[0][0].length];
        
        for (int x = 0; x < blocks.length; x++) {
            for (int y = 0; y < blocks[0].length; y++) {
                for (int z = 0; z < blocks[0][0].length; z++) {
                    facs[x + y + z] = blocks[y][y][z].getFactory();
                }
            }
        }

        capsule.write(level, "level", new BatchNode());
        capsule.write(levelSize, "levelSize", new Vector3f(100, 100, 100));
        capsule.write(facs, "facs", bDefault);
    }

    public void read(JmeImporter im) throws IOException {
        InputCapsule capsule = im.getCapsule(this);
        level = (BatchNode) capsule.readSavable("level", new BatchNode());
        levelSize = (Vector3f) capsule.readSavable("levelSize", new Vector3f(100, 100, 100));

        Savable[] facs = capsule.readSavableArray("facs", bDefault);
        BlockFactory[][][] copyArrays = new BlockFactory[(int) levelSize.x][(int) levelSize.y][(int) levelSize.z];

        Util.log(facs.length);

        int index = 0;
        for (int x = 0; x < copyArrays.length; x++) {
            for (int y = 0; y < copyArrays[0].length; y++) {
                for (int z = 0; z < copyArrays[0][0].length; z++) {
                    copyArrays[x][y][z] = (BlockFactory) facs[index++];
                }
            }
        }

        for (int x = 0; x < copyArrays.length; x++) {
            for (int y = 0; y < copyArrays[0].length; y++) {
                for (int z = 0; z < copyArrays[0][0].length; z++) {
                    if (copyArrays[x][y][z] != null) {
                        copyArrays[x][y][z].create(Util.getMain(), new Vector3f(x, y, z), level, true);
                    }
                }
            }
        }
        initiated = true;
    }

Well, this is kind of a completely different topic and I don’t know if everyone who could answer will bother drilling into a network question to see if there is some non-network question in here.

I didn’t have time to read your whole post but something important to note is that just because you can save things to a directory doesn’t mean that directory is automatically available to the AssetManager. If you haven’t already registered a locator for that location then you won’t be able to load assets from it.

I’m not really sure how to add a locator for the savegame areas and I’m not sure the people who worked on that will drill into a network thread. I guess we’ll see.

Well I can save it locally to the project as well but that doesn’t change anything. It says it saves, and it loads the binary just fine but none of the changes are there. It just rebuilds the level I created it with.
This is global apparently. Even if I create a new instance of a level I coded it still prints out null… this is very strange.

Okay I have the level saving correctly. However it doesn’t seem to load correctly, of course. Why would it ever want to do that?
What I think is happening is it’s just overwriting what I put in through the save when the loading inside jme loads the Level the j3o saved. Because when I save the level originally as one of the hand coded subclasses of Level, like LevelTest, and put a println in the constructor, it prints something out on the load of the j3o. So is there any way to stop it from instantiating a new object of that subclass and just make a new Level object?

When I’m just loading in the j3o I’m loading it into a Level object, no mention whatsoever of the subclass. I don’t see why it would do this.

Your terms may not be 100% accurate but it sounds like you might be describing an issue with caching. Once you load something with AssetManager it is cached. You’d have to remove it from the cache (or more brutally: clear the cache) in order to load a new version of that asset.

Does just restarting the game not do that?

@Slyth said: Does just restarting the game not do that?

As in closing the application and opening it again? Yeah, that will reset everything. If you are not seeing your changes after an application restart then they are not getting written or are being written to a different place.

I suppose they are getting written to a different place. Because I actually got it to save edits, but physics and everything else didn’t work so it was useless. Also when I restarted again all the changes were erased and it loaded absolutely nothing. Honestly I think I’m just going to do it the hard way, save the names in an array and have a bunch of if statements for each block. Terrible design, but what works works…
Of course that’s most likely not going to work either.

@Slyth said: I suppose they are getting written to a different place. Because I actually got it to save edits, but physics and everything else didn't work so it was useless. Also when I restarted again all the changes were erased and it loaded absolutely nothing. Honestly I think I'm just going to do it the hard way, save the names in an array and have a bunch of if statements for each block. Terrible design, but what works works... Of course that's most likely not going to work either.

Yeah. It’s hard for us to help further anyway because we can’t really see any of the relevant code here.

If I run into any problems I can post the Level class and the BlockSuper class to pastebin. However it is most likely that I will Just find some other way to load levels.
I appreciate your patience pspeed, I know it’s tough to deal with people like me :wink:

@Slyth said: If I run into any problems I can post the Level class and the BlockSuper class to pastebin. However it is most likely that I will Just find some other way to load levels. I appreciate your patience pspeed, I know it's tough to deal with people like me :wink:

lol. Nope. :slight_smile: Not at all.

Honestly I think it would be so much easier to do this if it was possible to save 3 dimensional arrays. Any chance of this being a feature?

well cant you just make a class with a 3dimensional array and implement it Saveable?

Anyhow you should probably debug why it is not saving your stuff, as this looks like another hiden problem, the binary exporter never gave me any problems in several years, that were not my fault in the end.

1 Like