Controls loaded via asset manager share instances of lists!

I ran into a rather annoying problem which after much investigation I have boiled down to this:
Controls which are loaded via the asset manager will share List instances when they should not!
here is the easiest way to reproduce:

public class BuggedControl extends AbstractControl{
    private List<String> buggedList;
    
    public BuggedControl(){
        buggedList = new ArrayList<>();
    }
    
    public void addStringToList(String s){
        buggedList.add(s);
    }
    
    public List<String> getList(){
        return buggedList;
    }

    @Override
    protected void controlUpdate(float tpf) {}

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {}
}
public class BugTest extends SimpleApplication{

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        BugTest app = new BugTest();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        //create 3 spatials via code, add to their lists
        Spatial[] codedArray = new Spatial[3];
        int coded = 0;
        while(coded < 3){
            String name = "Test "+coded++;
            Node s = new Node(name);
            BuggedControl bug = new BuggedControl();
            s.addControl(bug);
            bug.addStringToList(name);
            codedArray[coded-1] = s;
        }
        //load 3 identical spatials, add to their lists
        Spatial[] loadedArray = new Spatial[3];
        int loaded = 0;
        while(loaded < 3){
            String name = "Test "+loaded++;
            Spatial s = assetManager.loadModel("Test.j3o");
            s.setName(name);
            BuggedControl bug = s.getControl(BuggedControl.class);
            bug.addStringToList(name);
            loadedArray[loaded-1] = s;
        }
        //print the lists
        System.out.println("Coded...");
        for(int x=0; x<3; x++){
            BuggedControl bug = codedArray[x].getControl(BuggedControl.class);
            System.out.println(Arrays.toString(bug.getList().toArray()));
        }
        System.out.println("Loaded...");
        for(int x=0; x<3; x++){
            BuggedControl bug = loadedArray[x].getControl(BuggedControl.class);
            System.out.println(Arrays.toString(bug.getList().toArray()));
        }
    }
}

in either the sdk or programmically, create and save a node with the BuggedControl attached, and name it “Test.j3o”
https://drive.google.com/file/d/1SJ2jF7aS9yCJL4iJ6tFyjG8eHmjcLCUw/preview
the above test produces the following console log:

Coded...
[Test 0]
[Test 1]
[Test 2]
Loaded...
[Test 0, Test 1, Test 2]
[Test 0, Test 1, Test 2]
[Test 0, Test 1, Test 2]

I have tested with the lists as final, and not final. The fix I have found is to generate the list when it is first accessed via the update loop.
This bug still presents itself if you create the list in the read() method as well!

1 Like

Models loaded from the asset manager are cloned. If you don’t override cloneFields() to clone your fields then they will be the same instance.

Edit: look at examples of existing controls to see how to properly implement cloning in your custom controls.

1 Like

Thank you, In the documentation it says to over ride cloneForSpatial which I had tried, I’ll try using the cloneFields and see if that does better

1 Like

…was the old crappy way that required tons of extra work.

The JmeCloner way is much simpler.

Here is one example of implementing cloneFields():

2 Likes

Erf… We should update the doc ™️

2 Likes

Even looks like it might make sense to categorize parts that should and should not be shared somehow?

That’s up the implementation. That’s what cloneFields() is for. It lets the class clone or leave whatever it wants to.

2 Likes