CustomControl Saving Problem

Hi all. I am having a small problem with CustomControl. Before updating to JME3 SDK3.0 (Stable) everything was working fine with the CustomControl. It was behaving as it should and the attributes are being saved. After re-installing JME to JME3 SDK 3.0 (Stable) the CustomControl behaves as it should but the attributes are not saving. I tried to figure out the problem and see how others did but could not find a solution. I did create a new model and set the CustomControl on it but still the same problem remains. Any help will be appreciated.

Here is the code of one of the CustomControl which is just a basic one:

[java]
package Controls;

import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import java.io.IOException;

public class DamageControl extends AbstractControl implements Savable, Cloneable {

private int damage = 0, critical = 0, energy = 0;

@Override
protected void controlUpdate(float tpf) {
}

@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}

@Override
public Control cloneForSpatial(Spatial spatial) {
    DamageControl control = new DamageControl();
    control.setDamage(damage);
    control.setCritical(critical);
    control.setEnergy(energy);
    control.setSpatial(spatial);
    return control;
}

public void upgradeDamage(int amount){
    damage = damage + amount;
}

public void upgradeCritical(int amount){
    critical = critical + amount;
}

@Override
public void write(JmeExporter ex) throws IOException
{
    super.write(ex);
    OutputCapsule oc = ex.getCapsule(this);
    oc.write(damage, "Damage", 1);
    oc.write(critical, "Critical", 1);
    oc.write(energy, "Energy", 1);
}

@Override
public void read(JmeImporter im) throws IOException
{
    super.read(im);
    InputCapsule ic = im.getCapsule(this);
    damage = ic.readInt("Damage", 1);
    critical = ic.readInt("Critical", 1);
    energy = ic.readInt("Energy", 1);
}

/**
 * @return the damage
 */
public int getDamage() {
    return damage;
}

/**
 * @param damage the damage to set
 */
public void setDamage(int damage) {
    this.damage = damage;
}

/**
 * @return the critical
 */
public int getCritical() {
    return critical;
}

/**
 * @param critical the critical to set
 */
public void setCritical(int critical) {
    this.critical = critical;
}

/**
 * @return the energy
 */
public int getEnergy() {
    return energy;
}

/**
 * @param energy the energy to set
 */
public void setEnergy(int energy) {
    this.energy = energy;
}

}
[/java]

Thank you

When you say “not saving” what exactly do you mean? Are the read()/write() methods ever called? If so, does a file get created? Perhaps the problem is not in DamageControl but in the code which initializes and uses it.

@sgold : I did mention that it is a CustomControl/AbstractControl. The purpose of this is to set behaviors of the objects/models so that I don’t need to call it from my other classes but can if I want to, so I don’t need to initialize in other classes. What I do is create CustomControl set the attributes and save the value of the attributes but the problem is that the attributes are not being saved in the Scene Composer. It used to save the attributes in the Scene Composer but after updating to the new version it does not. Maybe I am missing something.

Ah found the mistake. The attributes in the CustomControl in SceneComposer are being saved but the values are not shown in the properties screen in the SceneComposer. I created a new project to see if older projects had some problem. Then I found this problem. I created a new AbstractControl class to check and found that the values are being saved through read()/write() methods but for some reason are not shown in the properties screen in SceneComposer. It only shows when the values are set but when I re-open the .j3o file the values are gone from the Properties screen but they do exist.

Here is the code for NewClass.java

[java]
package mygame;

import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import java.io.IOException;

/**
*

  • @author Kamran
    */
    public class NewClass extends AbstractControl implements Savable, Cloneable {

    private float testFloat1 = 1, testFloat2 = 1;
    private int testInt1 = 2;
    private boolean testBool1 = false;

    @Override
    protected void controlUpdate(float tpf) {

    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {

    }

    @Override
    public Control cloneForSpatial(Spatial spatial) {
    NewClass control = new NewClass();
    control.setTestBool1(testBool1);
    control.setTestFloat1(testFloat1);
    control.setTestFloat2(testFloat2);
    control.setTestInt1(testInt1);
    control.setSpatial(spatial);
    return control;
    }

    @Override
    public void write(JmeExporter ex) throws IOException
    {
    super.write(ex);
    OutputCapsule oc = ex.getCapsule(this);
    oc.write(testBool1, “testBool1”, true);
    oc.write(testFloat1, “testFloat1”, 1.0f);
    oc.write(testFloat2, “testFloat2”, 1.0f);
    oc.write(testInt1, “testInt1”, 1);
    System.out.println(“findme Write”);
    }

    @Override
    public void read(JmeImporter im) throws IOException
    {
    super.read(im);
    InputCapsule ic = im.getCapsule(this);
    testBool1 = ic.readBoolean(“testBool1”, true);
    testFloat1 = ic.readFloat(“testFloat1”, 1.0f);
    testFloat2 = ic.readFloat(“testFloat2”, 1.0f);
    testInt1 = ic.readInt(“testInt1”, 1);
    System.out.println(“findme Read”);
    }

    /**

    • @return the testFloat1
      */
      public float getTestFloat1() {
      return testFloat1;
      }

    /**

    • @param testFloat1 the testFloat1 to set
      */
      public void setTestFloat1(float testFloat1) {
      this.testFloat1 = testFloat1;
      }

    /**

    • @return the testFloat2
      */
      public float getTestFloat2() {
      return testFloat2;
      }

    /**

    • @param testFloat2 the testFloat2 to set
      */
      public void setTestFloat2(float testFloat2) {
      this.testFloat2 = testFloat2;
      }

    /**

    • @return the testInt1
      */
      public int getTestInt1() {
      return testInt1;
      }

    /**

    • @param testInt1 the testInt1 to set
      */
      public void setTestInt1(int testInt1) {
      this.testInt1 = testInt1;
      }

    /**

    • @return the testBool1
      */
      public boolean isTestBool1() {
      return testBool1;
      }

    /**

    • @param testBool1 the testBool1 to set
      */
      public void setTestBool1(boolean testBool1) {
      this.testBool1 = testBool1;
      }
      }

[/java]

Here is the code for the Main.java from where I printed the values to check:

[java]

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication {

public static void main(String[] args) {
    Main app = new Main();
    app.start();
}

@Override
public void simpleInitApp() {
       /** A white ambient light source. */ 
    AmbientLight ambient = new AmbientLight();
    ambient.setColor(ColorRGBA.White);
    rootNode.addLight(ambient); 

    DirectionalLight dl = new DirectionalLight();
    dl.setColor(ColorRGBA.White);
    dl.setDirection(new Vector3f(2.8f,-2.8f,-2.8f).normalizeLocal());
    rootNode.addLight(dl);
    
    //This is the model with the AbstractControl/NewClass.java
    Spatial spatial = assetManager.loadModel("Models/Dummy1.mesh.j3o");
    rootNode.attachChild(spatial);
    
    //Printing out the values from NewClass.java in the model
    NewClass control = spatial.getControl(NewClass.class);
    System.out.println(control.getTestFloat1());
    System.out.println(control.getTestFloat2());
    System.out.println(control.getTestInt1());
    System.out.println(control.isTestBool1());
}

@Override
public void simpleUpdate(float tpf) {
    //TODO: add update code
}

@Override
public void simpleRender(RenderManager rm) {
    //TODO: add render code
}

}

[/java]

Here is the picture to clarify the problem:

Is this a bug?

Just a debugging tip. I’m a big fan of “println” debugging and it would have helped you a lot in this case. Whenever there is a question like “Why isn’t this happening?” Step 1 is usually to determine if it’s even running. A System.out.println() will tell you that… and then let you go to the next step of verifying that the values are saved/loaded like you meant in this case.

I only mention it because it would probably have taken only as long as writing your well thought-out original post… and you’d have had your answer right away. :slight_smile:

To the other problem, I don’t know. I’m not sure the SDK automatically inspects custom classes… but I have no knowledge of SDK magic.

@pspeed : Yeah I should have checked it with println before :slight_smile: . I guess I just got off guard from SDK not showing the values in the Properties screen :facepalm: Any way thanks. =D

Having looked at the class com.jme3.gde.core.sceneexplorer.nodes.JmeGenericControl, it seems that the property sheet is not filled simply because

a) PropertyUtils.getPropertyDescriptor(c, field) is being called

This in turn will create a new PropertyDescriptor(field.getName(), c), which expects both a get<PropertyName> and isPropertyName method to be available.

The result is that PropertyUtils.getPropertyDescriptor() will always return null if both or one of these methods are missing.

In my opinion, PropertyUtils should check whether there is a getter or setter and then pass in these method names directly using the other available constructor,
and passing in null for methods which are not available.

I could provide you with a patch that would solve that problem.

@axnsoftware said: Having looked at the class com.jme3.gde.core.sceneexplorer.nodes.JmeGenericControl, it seems that the property sheet is not filled simply because

a) PropertyUtils.getPropertyDescriptor(c, field) is being called

This in turn will create a new PropertyDescriptor(field.getName(), c), which expects both a get<PropertyName> and isPropertyName method to be available.

The result is that PropertyUtils.getPropertyDescriptor() will always return null if both or one of these methods are missing.

In my opinion, PropertyUtils should check whether there is a getter or setter and then pass in these method names directly using the other available constructor,
and passing in null for methods which are not available.

I could provide you with a patch that would solve that problem.

I’m not sure what you are talking about here. Java’s PropertyDescriptor will work with either an isXXX OR a getXXX method. PropertyDescriptor only requires that there be both an accessor and a mutator and it will keep track of both. This seems logical in this case since the SDK will need to display and to edit that value.

PropertyDescriptor would be completely broken otherwise. So maybe there is an edge case you haven’t explained properly.

Excerpt from PropertyDescriptor:

public PropertyDescriptor(String propertyName, Class&lt;?&gt; beanClass)
            throws IntrospectionException {
    this(propertyName, beanClass,
         Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName),
         Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));
}

This is the constructor being called.

It in turn will delegate to the other constructor, which will then

    this.readMethodName = readMethodName;
    if (readMethodName != null &amp;&amp; getReadMethod() == null) {
        throw new IntrospectionException("Method not found: " + readMethodName);
    }
    this.writeMethodName = writeMethodName;
    if (writeMethodName != null &amp;&amp; getWriteMethod() == null) {
        throw new IntrospectionException("Method not found: " + writeMethodName);
    }

causing PropertyUtils to always return null for non boolean, hence the IS_PREFIX, fields.

So, if you have a non boolean field with getter and setter and named them appropriately,
PropertyUtils will always fail as the property descriptor is looking for is<FieldName> instead
of get<FieldName>.

I must admit, though, that I am using Java 7 :smiley: and it seems that the OP does the same.

The same holds true for PropertyUtils#getPropertyDescriptor(Class, Method).

getReadMethod() will check for getXXX or isXXX as needed.

PropertyDescriptor would be 100% broken (ie: most of the Java Beans API would be broken) if what you say is true since every property would require both a getXXX and an isXXX… which just isn’t true.

Yeah, you are right, I did overlook the part in getReadMethod() where it will check for both methods.