[Solved]calling addMapping(X) after deleteMapping(X) inside AnalogListener.onAnalog() has no effect

SOLVED: must not forget to call addListener(listener, X) after you deleteMapping(X) then addMapping(X) again


That is, while inside the onAnalog() method of an AnalogListener instance,

if I delete the specified mapping “X” then try to add it again(ie. later) the addition has no effect ie. it’s not added or something

but if I don’t delete the mapping and I just add a new one ie. new key for same existing mapping then it works well,

However the add after delete works well when called within simpleInitApp() method

Is this intentional? I’d need this to my own camera class which would prevent mouse rotation inputs from being processed when some boolean is toggled, kind of what flyCam.setEnabled(false) would do except it just tests if !enabled and returns while in onAnalog() method but I figured it would be better to just remove the mappings while disabled, is this really not possible? or is it just a bug or likely I’m missing something?

The code below should demo this, run it then press Space to trigger the mapIncDelay mapping which will delete itself then add itself (while in onAnalog method) [but the add doesn’t work, remains deleted] and also it adds a new key (KEY_L) to a different mapping (which was never deleted) mapDecDelay which works(meaning add works when not preceded by a delete, all while inside onAnalog())

pre type="java"
package org.jme3.tests;

import com.jme3.app.SimpleApplication;

import com.jme3.font.BitmapText;

import com.jme3.input.FlyByCamera;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.AnalogListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Matrix3f;

import com.jme3.math.Quaternion;

import com.jme3.math.Spline;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Mesh;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial.CullHint;

import com.jme3.scene.debug.Arrow;

import com.jme3.scene.debug.Grid;

import com.jme3.scene.shape.Box;

import com.jme3.scene.shape.Curve;

import com.jme3.system.AppSettings;


  • Sample 2 - How to use nodes as handles to manipulate objects in the scene

  • graph. You can rotate, translate, and scale objects by manipulating their

  • parent nodes. The Root Node is special: Only what is attached to the Root

  • Node appears in the scene.


    public class HelloNode6 extends SimpleApplication {

    private static final float moveSpeed = 1f;

    private static final float rotSpeed = 2f;

    private static final float yawSpeed = 4f;

    private static final float rollSpeed = 7f;

    private static final float pitchSpeed = 11f;

    private Vector3f cornerPos;

    private Geometry geoCurve;

    private Geometry geoBox;

    private final Spline spline = new Spline();

    private Node coord;

    private Box box;

    private final Vector3f centerOfBox = new Vector3f();

    private final Quaternion qRotation = new Quaternion();

    float yaw = 0f;

    float roll = 0f;

    float pitch = 0f;

    private final String mapIncDelay = “IncDelay”;

    private final String mapDecDelay = “DecDelay”;

    long sleep = 0;

    private float fpsNow;

    private float maxSeenFps = 30;

    private static final long sleepIncrement = 100;

    private BitmapText helloText;

    private final String mapRollRight = “rollRight”;

    private final String mapRollLeft = “rollLeft”;

    public static void main(String[] args) {

    HelloNode6 app = new HelloNode6();

    AppSettings aSet = new AppSettings(true);







    • (non-Javadoc)

    • @see com.jme3.app.SimpleApplication#simpleUpdate(float)



      public void simpleUpdate(float tpf) {

      yaw = (yaw + FastMath.DEG_TO_RAD * rotSpeed * yawSpeed * tpf)

      % (FastMath.PI * 2);

      roll = (roll + FastMath.DEG_TO_RAD * rotSpeed * rollSpeed * tpf)

      % (FastMath.PI * 2);

      pitch = (pitch + FastMath.DEG_TO_RAD * rotSpeed * pitchSpeed * tpf)

      % (FastMath.PI * 2);

      qRotation.fromAngles(yaw, roll, pitch);

      // rotating the box to these absolute angles (from its origin pos)


      // move box “forward” too, that is, forward relative to itself ie.

      // spaceship moving forward


      .mult(moveSpeed * tpf));

      Vector3f clonedCornerPos = cornerPos.clone();

      // now applying the same transform (pos/rot/scale) to the corner as the

      // box has

      // clonedCornerPos =

      geoBox.getLocalTransform().transformVector(clonedCornerPos,// in,

      clonedCornerPos// store




      // same orientation as Box


      // move coord system at center of Box


      // we have the corner’s exact pos now, relative to the Node the geoBox &

      // geoCurve are both in

      // we add that pos to the curve

      spline.addControlPoint(clonedCornerPos);// must be cloned!

      // we must create new Curve object because we can’t add/update the

      // spline in existing one (?!)

      fpsNow = timer.getFrameRate();

      if (fpsNow > maxSeenFps) {

      maxSeenFps = fpsNow;


      int subSegments = (int) Math.floor(maxSeenFps / fpsNow);

      geoCurve.setMesh(new Curve(spline, subSegments));

      helloText.setText("MaxSeenFps: " + (int) Math.ceil(maxSeenFps)

  • " / subSegs: " + subSegments);

    try {


    } catch (InterruptedException e) {




    private void initGUI() {

    // guiFont =

    // assetManager.loadFont( “Interface/Fonts/Default.fnt” );

    helloText = new BitmapText(guiFont, false);


    helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);



    private void initKeys() {


    .addMapping(mapIncDelay, new KeyTrigger(KeyInput.KEY_SPACE));


    // XXX:here it works well: del then add


    .addMapping(mapIncDelay, new KeyTrigger(KeyInput.KEY_SPACE));

    inputManager.addMapping(mapDecDelay, new KeyTrigger(


    inputManager.addMapping(mapRollRight, new KeyTrigger(KeyInput.KEY_E));

    inputManager.addMapping(mapRollLeft, new KeyTrigger(KeyInput.KEY_Q));

    inputManager.addListener(analogListener, mapIncDelay, mapDecDelay,

    mapRollRight, mapRollLeft);

    inputManager.deleteMapping(“FLYCAM_Rise”);// delete prev. Q mapping

    // (the lame way)



    • from {@link FlyByCamera#rotateCamera(float, Vector3f)} which is

    • protected, copied it here

    • @param value

    • @param axis


      protected void rotateCamera(float value, Vector3f axis) {

      Matrix3f mat = new Matrix3f();


      // flyCam.rotationSpeed//can’t use it not visible

      1f * value, axis);

      Vector3f up = cam.getUp();

      Vector3f left = cam.getLeft();

      Vector3f dir = cam.getDirection();

      mat.mult(up, up);

      mat.mult(left, left);

      mat.mult(dir, dir);

      Quaternion q = new Quaternion();

      q.fromAxes(left, up, dir);




      private final AnalogListener analogListener = new AnalogListener() {


      public void onAnalog(String name, float value, float tpf) {

      if (mapRollRight == name) {


      rotateCamera(value, cam.getDirection());



      if (mapRollLeft == name) {


      rotateCamera(-value, cam.getDirection());



      if (mapIncDelay == name) {

      System.out.println(“in mapIncDelay analog”);

      sleep += sleepIncrement;


      // XXX:here it deletes it forever, can’t add:

      inputManager.addMapping(mapIncDelay, new KeyTrigger(


      // XXX: the add above has no effect

      // but the following add works for decrement delay below

      inputManager.addMapping(mapDecDelay, new KeyTrigger(



      if (mapDecDelay == name) {

      if (sleep > 0) {

      sleep -= sleepIncrement;


      if (sleep < 0) {

      sleep = 0;



      System.out.println(“Sleep now at:” + sleep);




      public void simpleInitApp() {


      Node subNode = new Node();

      subNode.setLocalTranslation(new Vector3f(2, 0.5f, 1));

      subNode.rotate(1f, -2f, 4f);

      // a box with non 0,0,0 center which means a position of x,y,z relative

      // to it’s parent geoBox(below)

      box = new Box(new Vector3f(2, 3, 4), 0.5f, 0.9f, 1.3f);

      geoBox = new Geometry(“Box”, box);

      geoBox.setLocalTranslation(1f, 2f, 3f);

      geoBox.scale(1.3f, 1.2f, 1.1f);// always ok

      geoBox.rotate(2f, -4f, 0.4f);

      Material mat2 = new Material(assetManager,


      mat2.setColor(“Color”, ColorRGBA.Red);


      Material curveMat = new Material(assetManager,


      curveMat.setColor(“Color”, ColorRGBA.Green);

      geoCurve = new Geometry(“trails”);




      rootNode.setLocalTranslation(-1, -2, -4);

      rootNode.rotate(-1f, 2f, -4f);


      rootNode.scale(1.5f);// this is always ok

      subNode.scale(0.4f);// this is always ok too

      // subNode.scale(1.3f, 0.7f, 0.4f);// XXX: bug when uncommented

      // rootNode.scale(0.4f, 1.4f, 1.1f);// XXX: or/and this

      // doing this here only once

      cornerPos = new Vector3f();

      // calculating position of corner, first getting corner as it were if

      // box were at 0,0,0 and no rotation/scale


      box.getXExtent(), box.getYExtent(), box.getZExtent());

      // now considering box may have a different than 0,0,0 center


      coord = new Node();


      attachCoordinateAxes(Vector3f.ZERO, coord);


      // coord.setLocalTranslation( geoBox.getLocalTranslation() );//

      // box.getCenter() );



      attachGrid(Vector3f.ZERO, 100, ColorRGBA.Yellow);


      private void attachCoordinateAxes(Vector3f pos, Node toNode) {

      Arrow arrow = new Arrow(Vector3f.UNIT_X);

      // make arrow thicker,


      putShape(arrow, ColorRGBA.Red, toNode).setLocalTranslation(pos);

      arrow = new Arrow(Vector3f.UNIT_Y);

      arrow.setLineWidth(1); // make arrow thicker

      putShape(arrow, ColorRGBA.Green, toNode).setLocalTranslation(pos);

      arrow = new Arrow(Vector3f.UNIT_Z);

      arrow.setLineWidth(1); // make arrow thicker

      putShape(arrow, ColorRGBA.Blue, toNode).setLocalTranslation(pos);


      private Geometry putShape(Mesh shape, ColorRGBA color, Node onNode) {

      Geometry g = new Geometry(“coordinate axis”, shape);

      Material mat = new Material(assetManager,



      mat.setColor(“Color”, color);




      return g;


      private Geometry attachGrid(Vector3f pos, int size, ColorRGBA color) {

      Geometry g = new Geometry(“wireframe grid”, new Grid(size, size, 1f));

      Material mat = new Material(assetManager,



      mat.setColor(“Color”, color);




      return g;



pspeed said:
In the mean time, you could try adding a Callable to do it to the JME event queue. I don't know if it fits your exact use-case but it is a convenient way to run things one frame later and not in the same code context.

I'm not yet aware of JME event queue(can I get a link? I did google but fail) but am I safe to assume that Callable there will never interleave with calls to onAnalog/onAction ? that is they are both executed in JME Event Queue anyway thus they are `synchronized` (as opposed to them being called on different threads /async)

Also I assume you mean I would add that Callable to JME Event Queue from within onAnalog() aka when it happens and then on the next frame JME EQ will run that Callable before or after calling onAnalog() for this next frame(well if onAnalog was still in effect for this next frame)... Maybe, is it possible that JME EQ can call that Callable after it processed the onAnalog() that just added that Callable to the queue? that is, in the same frame...?

Having looked at some of this code… unfortunately, some parts of JME don’t handle the “listener modifying the listener list” thing very well. InputManager is one of those cases. I’ll try to fix it sometime when I have time unless someone else looks at it first.

In the mean time, you could try adding a Callable to do it to the JME event queue. I don’t know if it fits your exact use-case but it is a convenient way to run things one frame later and not in the same code context.

1 Like
pspeed said:
Having looked at some of this code... unfortunately, some parts of JME don't handle the "listener modifying the listener list" thing very well. InputManager is one of those cases. I'll try to fix it sometime when I have time unless someone else looks at it first.

In the mean time, you could try adding a Callable to do it to the JME event queue. I don't know if it fits your exact use-case but it is a convenient way to run things one frame later and not in the same code context.

I am fine with the fixes as long as the performance of iteration remains as fast as before

I noticed that the mapping’s listeners are gone the after delete, thus adding it again would require maybe calling inputManager1.addListener(trigger, mapping) for that deleted mapping, else it just has no listeners so it won’t get triggered

as a simple test for this, I added a println line in deleteMapping(mapping) method

in com.jme3.input.InputManager.java so it now looks like this:

pre type="java"
public void deleteMapping(String mappingName) {

Mapping mapping = mappings.remove(mappingName);

if (mapping == null) {

throw new IllegalArgumentException("Cannot find mapping: " + mappingName);



ArrayList triggers = mapping.triggers;

for (int i = triggers.size() - 1; i >= 0; i–) {

int hash = triggers.get(i);

ArrayList maps = bindings.get(hash);




so now to test:

  1. add a mapping X (it has no listener)
  2. call addListener() this adds the listener to the X mapping
  3. delete the mapping (X had that listener)
  4. add it again (it has no listener)
  5. delete it again (still has no listener, due to addListener wasn’t called between 4 and 5)

    and I’ve done this while in simpleInitApp()

    I guess what I’m saying is:

    that adding the mapping isn’t enough to have it trigger? or maybe it does trigger but since it has no listener there’s nobody to call…

    So it was merely my lack of understanding …

    tested it, it works all the time if I don’t forget to addListener(listener, mapping) if I re-add the mapping (after I’ve deleted it once)

    Reading the topic title again, the answer is:

    it’s just that after deleteMapping the listener is lost, so you need to call addListener again for that mapping after you call addMapping