Strange error Savable

Hello

So i was thinking about creating a save feature with Savable.
I already had a pretty big objectgraph so i let all the things implement Savable, and finally at the top an AppState, then i could in theory save and load the entire thing.

The serialization works (in XML form i can see that everything is there).
It loads without exceptions.
But when i attach the newly loaded AppState i get this:

java.lang.NullPointerException
at com.jme3.renderer.opengl.GLRenderer.clearVertexAttribs(GLRenderer.java:2322)
at com.jme3.renderer.opengl.GLRenderer.renderMeshDefault(GLRenderer.java:2674)
at com.jme3.renderer.opengl.GLRenderer.renderMesh(GLRenderer.java:2701)
at com.jme3.material.Material.renderMeshFromGeometry(Material.java:728)
at com.jme3.material.Material.render(Material.java:1217)
at com.jme3.renderer.RenderManager.renderGeometry(RenderManager.java:564)
at com.jme3.renderer.queue.RenderQueue.renderGeometryList(RenderQueue.java:266)
at com.jme3.renderer.queue.RenderQueue.renderQueue(RenderQueue.java:305)
at com.jme3.renderer.RenderManager.renderViewPortQueues(RenderManager.java:814)
at com.jme3.renderer.RenderManager.flushQueue(RenderManager.java:725)
at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:1040)
at com.jme3.renderer.RenderManager.render(RenderManager.java:1088)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:260)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:192)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:232)
at java.lang.Thread.run(Thread.java:745) 

I tried to reproduce this in a little project but i cant get it to not work…lol
But the main project is too large to post here.
The structure goes like this:

AppState
—Node
—ArrayList
—some more primitive fields…

And in the List i have objects that contain more Savable’s as fields.

As soon as i attach the node in the AppState i get the exception.

So now i dont really ask for a concrete solution, just for any general direction where i should look for the solution.
(btw i dont even understand how this error can even happen)

Any tips or advice on how i could go and at least narrow down the problem would be great!

After some hours of debugging i finally got to the root of the problem.
Ive got a custom mesh and i saved it too. (I know i should not save them)
In my read method of that object i recreated the geometry and now it works correctly.

But the thing is that i saved this same mesh elsewhere and there it worked properly.

My point is: there may be an issue in saving custom meshes.

Well, there isn’t really any magic here, though. Your own methods are called to save and load it. When reading, JME creates a new instance, calls your method. So if it’s not reading it correctly then that’s the place to start.

My mesh is just a simple quad (the difference is it is centered at the origin)
It just simply sets up the buffers, and in XML form the buffers are there, so i think they should be read.
Am i right?

The code looks fine from here. So I don’t know.

Like, I’m not sure why you are creating geometry inside a mesh… but I can’t see the code so I’m speculating on a like 100 different things from your description.

Only code is real.

Sorry, i did not post because i did not know where the error came from but now i can show the essential part:

public abstract class GameObject implements Savable {

private static long id = 0L;

protected Node node;
protected Geometry geometry;
protected Vector2f position;
protected long ID;
protected float age;
protected boolean dead;

public GameObject() {
    ID = id++;
    dead = false;
}
...
    @Override
public void write(JmeExporter ex) throws IOException {
    OutputCapsule cap = ex.getCapsule(this);
    cap.write(node, "GONode", null);
    cap.write(geometry, "GOGeometry", null);
    cap.write(position, "GOPosition", null);
    cap.write(ID, "GOID", 0);
    cap.write(age, "GOAge", 0);
    cap.write(dead, "GODead", false);
}

@Override
public void read(JmeImporter im) throws IOException {
    InputCapsule cap = im.getCapsule(this);
    node = (Node) cap.readSavable("GONode", null);
    geometry = (Geometry) cap.readSavable("GOGeometry", null);
    position = (Vector2f) cap.readSavable("GOPosition", null);
    ID = cap.readLong("GOID", 0);
    age = cap.readFloat("GOAge", 0);
    dead = cap.readBoolean("GODead", false);
}

public final class Enemy extends GameObject implements EventHandler {
  @Override
  public void write(JmeExporter ex) throws IOException {
    super.write(ex);
    OutputCapsule cap = ex.getCapsule(this);
    cap.write(material, "EnemyMaterial", null);
    ...

  }

  @Override
  public void read(JmeImporter im) throws IOException {
    super.read(im);
    InputCapsule cap = im.getCapsule(this);
    hpbar_width = cap.readFloat("EnemyHPBarWidth", 0);
    ....

    Mesh m = new MyQuad();
    FloatBuffer tc = BufferUtils.createFloatBuffer(4 * 2);
    float w = (float) material.getParam("FrameWidth").getValue();
    tc.put(0).put(1);
    tc.put(0).put(0);
    tc.put(w).put(0);
    tc.put(w).put(1);
    m.setBuffer(VertexBuffer.Type.TexCoord, 2, tc);
    m.updateBound();
    geometry.setMesh(m);

  }
}

public final class MyQuad extends Mesh{

public MyQuad() {
    FloatBuffer vb = BufferUtils.createFloatBuffer(4 * 3);
    FloatBuffer tc = BufferUtils.createFloatBuffer(4 * 2);
    FloatBuffer nb = BufferUtils.createFloatBuffer(4 * 3);
    ShortBuffer ib = BufferUtils.createShortBuffer(6);
    vb.put(-0.5f).put(0).put(0.5f);
    vb.put(-0.5f).put(0).put(-0.5f);
    vb.put(0.5f).put(0).put(-0.5f);
    vb.put(0.5f).put(0).put(0.5f);
    ib.put((short)1).put((short)0).put((short)2);
    ib.put((short)2).put((short)0).put((short)3);
    tc.put(0).put(1);
    tc.put(0).put(0);
    tc.put(1).put(0);
    tc.put(1).put(1);
    nb.put(0).put(1).put(0);
    nb.put(0).put(1).put(0);
    nb.put(0).put(1).put(0);
    nb.put(0).put(1).put(0);
    vb.rewind();
    ib.rewind();
    tc.rewind();
    nb.rewind();

    setBuffer(VertexBuffer.Type.Position, 3, vb);
    setBuffer(VertexBuffer.Type.Index, 3, ib);
    setBuffer(VertexBuffer.Type.TexCoord, 2, tc);
    setBuffer(VertexBuffer.Type.Normal, 3, nb);

    updateBound();
}
}

These are the essential parts.
On top of this sits an appstate with a list of these:

ArrayList<GameObject> gameObjects = new ArrayList<>(100);

and saves them as so:

cap.writeSavableArrayList(gameObjects, "WorldStateGameObjects", null);

This way it works. Before, i was just letting the geometry come from the gameobject’s read.

So you’re showing me the working code?

Well, since you got it working somehow then I guess it doesn’t matter anyway… but to fix broken code we’d need to see the broken code.