[Solved]GUI Update

Hi, i have a question about where/when to update Gui data?



Im not using nifty xml/etc. i use my extended class using BitmapText class and Picture class.



and when i try to update BitmapText position (to have it under object when its moving) In simpleUpdate i have this:


2011-09-26 11:39:45 com.jme3.app.Application handleError
SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.IllegalStateException: Scene graph is not properly updated for rendering.
Make sure scene graph state was not changed after
rootNode.updateGeometricState() call.
Problem spatial name: Gui Node
at com.jme3.scene.Spatial.checkCulling(Spatial.java:229)
at com.jme3.renderer.RenderManager.renderScene(RenderManager.java:508)
at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:712)
at com.jme3.renderer.RenderManager.render(RenderManager.java:745)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:249)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:158)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:203)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:221)
at java.lang.Thread.run(Thread.java:619)


it appear in random time.

doing everything other like opening panels / loading windows / etc Work Fine. Just This sticky object text which is update every frame get this exception.

or just tell me how to easy do sticky object text (like nickname over player) and animated text like (moving up Damage number over player)

During the update() loop the “rootNode” is updated (obviously). If you modify its content after it’s been updated but before the next loop you’ll get the error above.



So to remove that error, you have to have all your modifications done before it’s updated. There is also another way to fix this and it’s sometimes not possible to do it otherwise (depending on how your game is set up) and it’s to call theNode.updateGeometricState(); That call will update the node and its geometry but this is a double-edge sword. It can be helpful in certain situation but you can’t use this as a crutch. That means you have to try as much as possible to set everything in the node before it’s update. You could also flag it to be updated on the following frame instead of the one currently running.



Hopefully that makes sense to you.

Usually, this is caused by trying to modify the scene graph from another thread. Is that what you are doing here?



It’s the “random” part that makes me think so.



You can’t modify the scene graph from any thread except the render thread.

nope, i’m using simpleUpdate as i said.



@madjack: how to have modifications done before it’s updated? simpleUpdate is not for this? I have there every geometry/etc modifications

fragment of simpleUpdate



[java] public void simpleUpdate(float tpf) {

///////////////////////////////

objectStickText.updateAll();

////////////////////////////// [/java]



here is class for StickText over object



[java] class ObjectStickText {



public ArrayList<StickText> stickTextList = new ArrayList<StickText>();

public final int WIDTH = 100;

public final int HEIGHT = 20;

public final int MAX_ANIM_STEP = 20;



public class StickText {



String value;

String obj_name;

String gui_name;

Vector3f pushVector;

String animation;

int animationStep = 0;

long lastUpdate = System.currentTimeMillis();



StickText(String new_value, String new_obj_name, String new_gui_name, Vector3f new_pushVector, String new_animation, float new_fontSize) {

value = new_value;

obj_name = new_obj_name;

gui_name = new_gui_name;

pushVector = new_pushVector;

animation = new_animation;

try {

gameGUI.newString(gui_name, new_fontSize, Math.round(WIDTH / 2), Math.round(HEIGHT / 2), WIDTH, HEIGHT, value).attach();

} catch (Exception error) {

Debug.reportInfo(Debug.PRIORITY_ZERO, "StickText: Constructor error: " + error);

}

}



public void update() {

try {

if (animationStep > MAX_ANIM_STEP) {

//removeStickText(gui_name);

} else {

Vector3f loc = World.getObject(obj_name).ObjectList.get(0).node.getLocalTranslation();

Vector3f worldpos = loc.add(pushVector);

Vector3f screenpos = cam.getScreenCoordinates(worldpos);

if (screenpos != null) {

MyGUI.MyString str = gameGUI.getString(gui_name);

str.str = value;

int addX = 0;

int addY = 0;

if (animation.equals("MoveUp")) {

addY = animationStep * 2;

long actUpdate = System.currentTimeMillis();

if (Math.abs(lastUpdate - actUpdate) > 100) {

animationStep++;

lastUpdate = actUpdate;

}

}

str.x = (int) screenpos.x - Math.round(WIDTH / 2);

str.y = (int) screenpos.y - Math.round(HEIGHT / 2) + addY;

str.updateData();

//System.out.println("stickText update2 "+(int)screenpos.x+" "+(int)screenpos.y+"");

}

}

} catch (Exception error) {

Debug.reportInfo(Debug.PRIORITY_ZERO, "StickText: update() error: " + error);

}

}

}



public StickText getStickText(String name) {

for (StickText sticktext : stickTextList) {

if (sticktext.gui_name.equals(name)) {

return sticktext;

}

}

return null;

}



public void removeStickText(String name) {

for (StickText sticktext : stickTextList) {

if (sticktext.gui_name.equals(name)) {

MyGUI.MyString str = gameGUI.getString(name);

str.Dettach();

stickTextList.remove(sticktext);

}

}

}



public void addStickText(String new_value, String new_obj_name, String new_gui_name, Vector3f new_pushVector, String new_animation, float new_fontSize) {

stickTextList.add(new StickText(new_value, new_obj_name, new_gui_name, new_pushVector, new_animation, new_fontSize));

}



public void updateAll() {

System.out.println("stickText updateAll()");

for (StickText sticktext : stickTextList) {

sticktext.update();

}

}



public void removeAll() {

for (StickText sticktext : stickTextList) {

MyGUI.MyString str = gameGUI.getString(sticktext.gui_name);

str.Dettach();

stickTextList.remove(sticktext);

}

}

}

ObjectStickText objectStickText = new ObjectStickText();[/java]





and fragment from my extended gui class:



[java] public void updateData() {

try {

txt.setSize(fontConfig.myFont.getPreferredSize() * fontsize);

txt.setText(str);

txt.setLocalTranslation(x - (txt.getLineWidth() / 2) + (width / 2), y + (txt.getHeight() / 2) + (height / 2), 0);

} catch (Exception error) {

Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI updateData() error: " + error);

}

}[/java]

simpleUpdate() runs right before the “rootNode” updates, so technically you shouldn’t get that error. But my guess is, there’s something going on elsewhere modifying the scene graph after the update. Are you positive it’s coming from there? Is there any other place where you add/remove/rotate/etc geometries/spatials in the rootNode?



From the quick look of that ObjectStickText class, if I were you, I’d make that an AbstractAppState. Much easier to work with. You should read on this in the wiki.

Thanks for AbstractAppState advice. i will read about it :slight_smile:



i get this error in 0-4 seconds always.

When i comment “objectStickText.updateAll();” everything work fine



But still i reallly don’t know why i get this error. I will try to comment “fragment of code” and look where is the problem

re: “and fragment from my extended gui class:” Who calls that updateData()?



Something is modifying the scenegraph outside of simpleUpdate(). You just need to figure out what. You have no other threads running? You aren’t using Swing or something to update your scene graph?

1 Like

now i know what was wrong :slight_smile:



updateData() was ok! dont touch it! ;p Problem was in Attach



@pspeed: yes, it’s true i had problem from another thread but With Adding gui node (not updating).



I had Asynchronous Connection that was calling method to add GuiNode(not in simpleUpdate). Thats why i had problem.



I waited more time ~30 sec and i get same error with comment “objectStickText.updateAll();”. When it was uncomment error appears “earlier” don’t know why.



Nevermind of all i just created now Clear solution for it( for updateData too to be sure :slight_smile: )



MyCallback is just abstract class.



[java]

public void attach() {

stringList.add(this);

rendererUpdateList.add(new MyCallback() {



@Override

public void end() {

guiNode.attachChild(txt);

txt.setSize(fontConfig.myFont.getPreferredSize() * fontsize);

txt.setText(str);

txt.setLocalTranslation(x - (txt.getLineWidth() / 2) + (width / 2), y + (txt.getHeight() / 2) + (height / 2), 0);

}

});

}



public void Dettach() {

stringList.remove(this);

rendererUpdateList.add(new MyCallback() {



@Override

public void end() {

if (txt != null && txt.getParent() != null) {

txt.removeFromParent();

}

}

});

}



public void updateData() {

try {

rendererUpdateList.add(new MyCallback() {



@Override

public void end() {

txt.setSize(fontConfig.myFont.getPreferredSize() * fontsize);

txt.setText(str);

txt.setLocalTranslation(x - (txt.getLineWidth() / 2) + (width / 2), y + (txt.getHeight() / 2) + (height / 2), 0);

}

});

} catch (Exception error) {

Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI updateData() error: " + error);

}

}

[/java]



and



[java] private ArrayList<MyCallback> rendererUpdateList = new ArrayList<MyCallback>();

private ArrayList<MyInput> inputList = new ArrayList<MyInput>();

private ArrayList<MyString> stringList = new ArrayList<MyString>();

private ArrayList<MyImage> imageList = new ArrayList<MyImage>();



public void renderUpdate() {

for (MyCallback renderCallback : rendererUpdateList) {

renderCallback.end();

}

rendererUpdateList.clear();

}[/java]



so now i just call



[java] @Override

public void simpleUpdate(float tpf) {

///////////////////////////////

gameGUI.renderUpdate();[/java]



Thanks to @madjack idea :slight_smile:



and thanks for try help @pspeed :slight_smile:

Cool! I actually had one helpful thread in the last week! :wink:



Grammar nazi in me has his hackles raised. It’s detach, not dettach. :wink: And you should ALWAYS use proper naming convention! Don’t use uppercase on method names! :stuck_out_tongue:

yes i was using replace(to replace some names) so it was wrong, as you see attach is ok ;p



about double “tt” it’s my bad english ;p

Note: it looks to me like your callback stuff is essentially reinventing the Callable queue that is already built into JME. Your version just isn’t thread safe. :stuck_out_tongue:

1 Like

When I started using jME I was a complete idiot (only slightly better now ;)) and to fix that I had to put a println(“modified node in xmethod()”); and I can tell you that for about a week I deeply hated the scene graph. I had node.updateGeometricState() all over the place… Today I laugh at it, but you were good (or lucky). :wink:



Anyway, glad it’s now solved. :slight_smile:

@pspeed: so maybe will you help me make it thread safe? :slight_smile:



it work nice but it appear, you have right, becouse when i try remove gui node in this callback i have this:


SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at mygame.GameApplicationExtend$ObjectStickText.updateAll(GameApplicationExtend.java:1080)
at mygame.Client.simpleUpdate(Client.java:735)


and i cant use it: https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:multithreading?s[]=concurrent

becouse removing use scene graph data

The concurrent mod exception may be unrelated to threading… it’s hard to see because I can only see a mix of old and new code. I tried to see who was adding or removing to/from stickTextList but I wasn’t sure I was seeing the right stuff. But you can get a concurrent mod exception without any threads at all if you try to modify the list while you are iterating over it.



The data structures you use are not thread safe, though and you are treating them as such with your home grown version of JME’s callable queue.



See:

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:multithreading



Scroll down to “The Callable”.



Application already has a place to enqueue Callables that will run at the beginning of the update loop. And it’s already thread safe.

pspeed said:
The concurrent mod exception may be unrelated to threading... it's hard to see because I can only see a mix of old and new code. I tried to see who was adding or removing to/from stickTextList but I wasn't sure I was seeing the right stuff. But you can get a concurrent mod exception without any threads at all if you try to modify the list while you are iterating over it.

The data structures you use are not thread safe, though and you are treating them as such with your home grown version of JME's callable queue.

See:
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:multithreading

Scroll down to "The Callable".

Application already has a place to enqueue Callables that will run at the beginning of the update loop. And it's already thread safe.

Theres a nice snippet for this purpose @pspeed, see the "insert snippet" button ;)
[snippet id="10"]

for remember, my problem is when i remove gui element (BitmapText or Picture) and i done it in simpleUpdate



i dont know how to do it properly, i try done it like this:



[java] public void simpleUpdate(float tpf) {



Callable<MyCallback> processGUI = new Callable<MyCallback>(){

public MyCallback call() throws Exception {

gameGUI.renderUpdate();

return null;

}

};

application.enqueue(processGUI);

[/java]





or i should done it like this?:



[java] public void simpleUpdate(float tpf) {



Callable<MyCallback> processGUI = new Callable<MyCallback>(){

public MyCallback call() throws Exception {

for (MyCallback renderCallback : gameGUI.rendererUpdateList) {

gameGUI.renderCallback.end();

}

gameGUI.rendererUpdateList.clear();

return null;

}

};

application.enqueue(processGUI);

[/java]



or somethin like this?(becouse i remove BitmapText):



[java] public void simpleUpdate(float tpf) {



Callable<BitmapText> processGUI = new Callable<BitmapText>(){

public BitmapText call() throws Exception {

for (MyCallback renderCallback : gameGUI.rendererUpdateList) {

gameGUI.renderCallback.end();

}

gameGUI.rendererUpdateList.clear();

return null;

}

};

application.enqueue(processGUI);

[/java]



which way is good for me to understand it?



sorry i just dont understand how it work



but it seem dont work for removing GUI stick element


SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at mygame.GameApplicationExtend$ObjectStickText.updateAll(GameApplicationExtend.java:1090)
at mygame.Client.simpleUpdate(Client.java:748)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:241)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:158)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:203)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:221)
at java.lang.Thread.run(Thread.java:619)


so problem is elsewhere or i just not proper write Callable?

it can edit only returned object yes?
if yes, then i dont think if Callable can help in my problem

If you are already in simpleUpdate() then you don’t need to do anything special. The Callable stuff is meant to do from other threads which is what I thought the issue was and why you needed some home-grown Callable thing.



If everything is running from one thread then:

mygame.GameApplicationExtend$ObjectStickText.updateAll(GameApplicationExtend.java:1090)



Is doing something that modifies the list that is being iterated over. So the element updates are changing the outer list.

ok so for know:


  • have an Arraylist of MyCallbacks:



    [java]

    import java.util.ArrayList;

    abstract class MyCallback {

    public ArrayList<String> args = new ArrayList<String>();

    public void end(){};

    }[/java]



    and i have a classes



    [java]

    public class MyGUI extends AbstractAppState {



    @Override

    public void update(float tpf) {

    for (MyCallback renderCallback : rendererUpdateList) {

    renderCallback.end();

    }

    rendererUpdateList.clear();

    }





    MyThread blink = new MyThread() {



    public boolean sign = false;



    @Override

    public void run() {

    try {

    while (true) {

    if (threadDone) {

    break;

    }

    sign = !sign;

    for (int i = 0; i < inputList.size(); i++) {

    if (inputList.get(i).active) {

    if (sign) {

    inputList.get(i).value.txt.setText(inputList.get(i).value.str + "|");

    } else {

    inputList.get(i).value.txt.setText(inputList.get(i).value.str + "");

    }

    } else {

    inputList.get(i).value.txt.setText(inputList.get(i).value.str + "");

    }

    }

    Thread.sleep(500);

    }

    } catch (InterruptedException error) {

    System.out.println("InterruptedException error: " + error);

    }

    }

    };



    MyGUI() {

    blink.work(true);

    }

    public boolean clickToHandle = false;

    public boolean usingMouseClick = false;

    public ArrayList<MyCallback> rendererUpdateList = new ArrayList<MyCallback>();

    private ArrayList<MyInput> inputList = new ArrayList<MyInput>();

    private ArrayList<MyString> stringList = new ArrayList<MyString>();

    private ArrayList<MyImage> imageList = new ArrayList<MyImage>();







    public void renderUpdate() {

    for (MyCallback renderCallback : rendererUpdateList) {

    renderCallback.end();

    }

    rendererUpdateList.clear();

    }



    public void keyEvent(String key) {

    for (int i = 0; i < inputList.size(); i++) {

    inputList.get(i).keaAction(key);

    }

    }



    public void mouseEvent(int mouseX, int mouseY, boolean clicked) {

    usingMouseClick = false;

    for (int i = 0; i < inputList.size(); i++) {

    if (inputList.get(i).mouseAction(mouseX, mouseY, clicked)) {

    usingMouseClick = true;

    }

    }

    for (int i = 0; i < stringList.size(); i++) {

    if (stringList.get(i).mouseAction(mouseX, mouseY, clicked)) {

    usingMouseClick = true;

    }

    }

    for (int i = 0; i < imageList.size(); i++) {

    if (imageList.get(i).mouseAction(mouseX, mouseY, clicked)) {

    usingMouseClick = true;

    }

    }

    }



    public void detachInp(String name) {

    MyGUI.MyInput checkinp = getInput(name);

    if (checkinp != null) {

    checkinp.detach();

    }

    }



    public void detachImg(String name) {

    MyGUI.MyImage checkimg = getImage(name);

    if (checkimg != null) {

    checkimg.detach();

    }

    }



    public void detachStr(String name) {

    MyGUI.MyString checkstr = getString(name);

    if (checkstr != null) {

    checkstr.detach();

    }

    }



    public MyInput getInput(String name) {

    for (int i = 0; i < inputList.size(); i++) {

    try {

    if (inputList.get(i).name.equals(name)) {

    return inputList.get(i);

    }

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI getInput() error: " + error);

    }

    }

    return null;

    }



    public MyImage getImage(String name) {

    for (int i = 0; i < imageList.size(); i++) {

    try {

    if (imageList.get(i).name.equals(name)) {

    return imageList.get(i);

    }

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI getImage() error: " + error);

    }

    }

    return null;

    }



    public MyString getString(String name) {

    for (int i = 0; i < stringList.size(); i++) {

    try {

    if (stringList.get(i).name.equals(name)) {

    return stringList.get(i);

    }

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI getString() error: " + error);

    }

    }

    return null;

    }



    public MyInput newInput(String newName, float newFontsize, int newX, int newY, int newWidth, int newHeight, String newImg, String newImgOver, String newStr) {

    return new MyInput(newName, newFontsize, newX, newY, newWidth, newHeight, newImg, newImgOver, newStr);

    }



    public MyString newString(String newName, float newFontsize, int newX, int newY, int newWidth, int newHeight, String newStr) {

    return new MyString(newName, newFontsize, newX, newY, newWidth, newHeight, newStr);

    }



    public MyImage newImage(String newName, int newX, int newY, int newWidth, int newHeight, String image) {

    return new MyImage(newName, newX, newY, newWidth, newHeight, image);

    }



    public class MyInput {



    boolean blinking = false;

    public String name;

    public boolean active = false;

    public int x;

    public int y;

    public int width;

    public int height;

    public String image;

    public String imageOver;

    MyImage img;

    MyString value;

    MyCallback mouseover = new MyCallback() {



    @Override

    public void end() {

    }

    };

    MyCallback mouseclick = new MyCallback() {



    @Override

    public void end() {

    }

    };



    MyInput(String newName, float newFontsize, int newX, int newY, int newWidth, int newHeight, String newImage, String newImageOver, String newStr) {

    name = newName;

    x = newX;

    y = newY;

    width = newWidth;

    height = newHeight;

    image = newImage;

    imageOver = newImageOver;

    value = new MyString(name + "value", newFontsize, x, y, width, height, "");

    img = new MyImage(name + "value", x, y, width, height, image);

    }



    public void attach() {

    updateData();

    img.attach();

    value.attach();

    inputList.add(this);

    }



    public void detach() {

    img.detach();

    value.detach();

    inputList.remove(this);

    }



    public void updateData() {

    try {

    img.x = x;

    img.y = y;

    img.width = width;

    img.height = height;

    value.x = x;

    value.y = y;

    value.width = width;

    value.height = height;

    img.updateData();

    value.updateData();

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI updateData() error: " + error);

    }

    }



    public boolean mouseAction(int mouseX, int mouseY, boolean clicked) {

    boolean usingClick = false;

    if (clicked) {

    active = false;

    updateData();

    }

    if (img.image.equals(imageOver)) {

    img.image = image;

    img.updateData();

    }

    if (mouseX >= x && mouseX <= (x + width) && mouseY >= y && mouseY <= (y + height)) {

    img.image = imageOver;

    mouseover.end();

    if (clicked) {

    usingClick = true;

    active = true;

    mouseclick.end();

    }

    updateData();

    }

    return usingClick;

    }



    public void keaAction(String key) {

    if (active) {

    if (key.equals("backspace")) {

    try {

    value.str = value.str.substring(0, value.str.length() - 1);

    } catch (Exception err) {

    }

    } else {

    value.str = value.str + key;

    }

    value.updateData();

    }

    }

    }



    public class MyString {



    public String name;

    public float fontsize;

    public int x;

    public int y;

    public int width;

    public int height;

    public String str;

    public BitmapText txt;

    MyCallback mouseover = new MyCallback() {



    @Override

    public void end() {

    }

    };

    MyCallback mouseclick = new MyCallback() {



    @Override

    public void end() {

    }

    };



    MyString(String newName, float newFontsize, int newX, int newY, int newWidth, int newHeight, String newStr) {

    name = newName;

    fontsize = newFontsize;

    x = newX;

    y = newY;

    width = newWidth;

    height = newHeight;

    str = newStr;

    txt = new BitmapText(fontConfig.myFont, false);

    }



    public void attach() {

    try {

    stringList.add(this);

    rendererUpdateList.add(new MyCallback() {



    @Override

    public void end() {

    guiNode.attachChild(txt);

    txt.setSize(fontConfig.myFont.getPreferredSize() * fontsize);

    txt.setText(str);

    txt.setLocalTranslation(x - (txt.getLineWidth() / 2) + (width / 2), y + (txt.getHeight() / 2) + (height / 2), 0);

    }

    });

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI attach() error: " + error);

    }

    }



    public void detach() {

    try {

    stringList.remove(this);

    rendererUpdateList.add(new MyCallback() {



    @Override

    public void end() {

    if (txt != null && txt.getParent() != null) {

    txt.removeFromParent();

    }

    }

    });

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI detach() error: " + error);

    }

    }



    public void updateData() {

    try {

    rendererUpdateList.add(new MyCallback() {



    @Override

    public void end() {

    txt.setSize(fontConfig.myFont.getPreferredSize() * fontsize);

    txt.setText(str);

    txt.setLocalTranslation(x - (txt.getLineWidth() / 2) + (width / 2), y + (txt.getHeight() / 2) + (height / 2), 0);

    }

    });

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI updateData() error: " + error);

    }

    }



    public boolean mouseAction(int mouseX, int mouseY, boolean clicked) {

    boolean usingClick = false;

    if (clicked) {

    updateData();

    }

    if (mouseX >= x && mouseX <= (x + width) && mouseY >= y && mouseY <= (y + height)) {

    mouseover.end();

    if (clicked) {

    usingClick = true;

    mouseclick.end();

    }

    updateData();

    }

    return usingClick;

    }

    }



    public class MyImage {



    public boolean over = false;

    public String name;

    public int x;

    public int y;

    public int width;

    public int height;

    public String image;

    public Picture view;

    MyCallback mouseover = new MyCallback() {



    @Override

    public void end() {

    }

    };

    MyCallback mouseout = new MyCallback() {



    @Override

    public void end() {

    }

    };

    MyCallback mouseclick = new MyCallback() {



    @Override

    public void end() {

    }

    };



    MyImage(String newName, int newX, int newY, int newWidth, int newHeight, String newImage) {

    name = newName;

    x = newX;

    y = newY;

    width = newWidth;

    height = newHeight;

    image = newImage;

    view = new Picture(name);

    }



    public boolean isMouseOver() {

    if ((int) inputManager.getCursorPosition().x > this.x && (int) inputManager.getCursorPosition().x < this.x + this.width && (int) inputManager.getCursorPosition().y > this.y && (int) inputManager.getCursorPosition().y < this.y + this.height) {

    return true;

    }

    return false;

    }



    public void attach() {

    try {

    imageList.add(this);

    rendererUpdateList.add(new MyCallback() {



    @Override

    public void end() {

    guiNode.attachChild(view);

    view.setWidth(width);

    view.setHeight(height);

    view.setPosition(x, y);

    view.setImage(assetManager, image, true);

    }

    });

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI attach() error: " + error);

    }

    }



    public void detach() {

    try {

    imageList.remove(this);

    rendererUpdateList.add(new MyCallback() {



    @Override

    public void end() {

    if (view != null && view.getParent() != null) {

    view.removeFromParent();

    }

    }

    });

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI detach() error: " + error);

    }

    }



    public void updateData() {

    try {

    if (guiNode.hasChild(view)) {

    rendererUpdateList.add(new MyCallback() {



    @Override

    public void end() {

    view.setWidth(width);

    view.setHeight(height);

    view.setPosition(x, y);

    view.setImage(assetManager, image, true);

    }

    });

    }

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "GUI updateData() error: " + error);

    }

    }



    public boolean mouseAction(int mouseX, int mouseY, boolean clicked) {

    boolean usingClick = false;

    if (clicked) {

    updateData();

    }

    if (mouseX >= x && mouseX <= (x + width) && mouseY >= y && mouseY <= (y + height)) {

    if (!over) {

    over = true;

    mouseover.end();

    }

    if (clicked) {

    usingClick = true;

    mouseclick.end();

    }

    updateData();

    } else {

    if (over) {

    over = false;

    mouseout.end();

    }

    }

    return usingClick;

    }

    }

    }

    public MyGUI gameGUI = new MyGUI();

    /////////////////////////////////////////////////////////////////////



    class ObjectStickText {



    public ArrayList<StickText> stickTextList = new ArrayList<StickText>();

    public final int WIDTH = 100;

    public final int HEIGHT = 20;

    public final int MAX_ANIM_STEP = 20;



    public class StickText {

    String value;

    String obj_name;

    String gui_name;

    Vector3f pushVector;

    String animation;

    int animationStep = 0;

    long lastUpdate = System.currentTimeMillis();



    StickText(String new_value, String new_obj_name, String new_gui_name, Vector3f new_pushVector, String new_animation, float new_fontSize) {

    value = new_value;

    obj_name = new_obj_name;

    gui_name = new_gui_name;

    pushVector = new_pushVector;

    animation = new_animation;

    try {

    gameGUI.newString(gui_name, new_fontSize, Math.round(WIDTH / 2), Math.round(HEIGHT / 2), WIDTH, HEIGHT, value).attach();

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "StickText: Constructor error: " + error);

    }

    }



    public void update() {

    try {

    if (animationStep > MAX_ANIM_STEP) {

    removeStickText(gui_name);

    } else {

    Vector3f loc = World.getObject(obj_name).ObjectList.get(0).node.getLocalTranslation();

    Vector3f worldpos = loc.add(pushVector);

    Vector3f screenpos = cam.getScreenCoordinates(worldpos);

    if (screenpos != null) {

    MyGUI.MyString str = gameGUI.getString(gui_name);

    str.str = value;

    int addX = 0;

    int addY = 0;

    if (animation.equals("MoveUp")) {

    addY = animationStep * 2;

    long actUpdate = System.currentTimeMillis();

    if (Math.abs(lastUpdate - actUpdate) > 100) {

    animationStep++;

    lastUpdate = actUpdate;

    }

    }

    str.x = (int) screenpos.x - Math.round(WIDTH / 2);

    str.y = (int) screenpos.y - Math.round(HEIGHT / 2) + addY;

    str.updateData();

    //System.out.println("stickText update2 "+(int)screenpos.x+" "+(int)screenpos.y+"");

    }

    }

    } catch (Exception error) {

    Debug.reportInfo(Debug.PRIORITY_ZERO, "StickText: update() error: " + error);

    }

    }

    }



    public StickText getStickText(String name) {

    for (StickText sticktext : stickTextList) {

    if (sticktext.gui_name.equals(name)) {

    return sticktext;

    }

    }

    return null;

    }



    public void removeStickText(String name) {

    for (StickText sticktext : stickTextList) {

    if (sticktext.gui_name.equals(name)) {

    MyGUI.MyString str = gameGUI.getString(name);

    str.detach();

    stickTextList.remove(sticktext);

    }

    }

    }



    public void addStickText(String new_value, String new_obj_name, String new_gui_name, Vector3f new_pushVector, String new_animation, float new_fontSize) {

    stickTextList.add(new StickText(new_value, new_obj_name, new_gui_name, new_pushVector, new_animation, new_fontSize));

    }



    public void updateAll() {

    System.out.println("stickText updateAll()");

    for (StickText sticktext : stickTextList) {

    sticktext.update();

    }

    }



    public void removeAll() {

    for (StickText sticktext : stickTextList) {

    MyGUI.MyString str = gameGUI.getString(sticktext.gui_name);

    str.detach();

    stickTextList.remove(sticktext);

    }

    }

    }

    ObjectStickText objectStickText = new ObjectStickText();

    [/java]



    now i use AbstractAppState and update not simpleUpdate, but its the same yes?



    something is wrong here i dont know where but with detach element



    if i will fix My stupid code everyone can use this stupid code :stuck_out_tongue:

[java]

public void removeAll() {

for (StickText sticktext : stickTextList) {

MyGUI.MyString str = gameGUI.getString(sticktext.gui_name);

str.detach();

stickTextList.remove(sticktext);

}

}

[/java]



Just on quick glance you will get a concurrent modification exception just in that loop. You cannot modify an ArrayList that you are iterating over in this way. At best you could convert to use an actual iterator and then call iterator.remove()…



but really… why not just clear the list after the loop instead of removing each element?



And I’m still not sure what all of the callbacks are for.