AbsoluteMouse stops working on low fps

When using com.jme.input.AbsoluteMouse with a setting setUsingDelta(true), then on low fps mouse looses responsiveness. You can see that something wrong at about 50 fps. At 10 fps, mouse starts moving really slow.



Here is a test to replicate the problem:

Press F to limit fps, press D to toggle mouse.isUsingDelta().

Notice that everything works fine with usingDelta = false on any fps. Notice how fast the mouse speed drops with usingDelta = true as fps becomes lowers.



My system: Ubuntu Linux with Java6.



import com.jme.app.SimpleGame;
import com.jme.input.AbsoluteMouse;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.math.Vector2f;
import com.jme.scene.Text;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;

public class BugTest extends SimpleGame {

   private final String cmdToggleDelta = "toggleDelta";
   private final String cmdToggleFps = "toggleFps";
   
   private AbsoluteMouse mouse;
   private float sensitivity = 1.5f;
   
   private Vector2f tempVector = new Vector2f();
   private Vector2f lastCoordinates = new Vector2f();
   private float lastSensitivity = sensitivity;
   
   private MouseInput mouseInput;
   private KeyBindingManager keyboard =
      KeyBindingManager.getKeyBindingManager();
   
   private Text infoTest;
   private Text infoCoordinates;
   private StringBuffer sb = new StringBuffer();
   
   private int sleep;
   
   @Override
   protected void simpleInitGame() {
      mouseInput = MouseInput.get();
      
      mouse = new AbsoluteMouse("Test Mouse",
            display.getRenderer().getWidth(),
            display.getRenderer().getHeight());
      mouse.setSpeed(sensitivity);
      mouse.setUsingDelta(true);
      mouse.registerWithInputHandler(input);
      TextureState ts = display.getRenderer().createTextureState();
      // replace null with something else if you want an actual cursor
      ts.setTexture(TextureManager.loadTexture(null));
      mouse.setRenderState(ts);
      rootNode.attachChild(mouse);
      
      keyboard.add(cmdToggleDelta, KeyInput.KEY_D);
      keyboard.add(cmdToggleFps, KeyInput.KEY_F);
      
      infoTest = Text.createDefaultTextLabel("infoTest");
      infoTest.setTextureCombineMode(TextureState.REPLACE);
      infoTest.setLocalTranslation(0, display.getHeight() - 40, 0);
        fpsNode.attachChild(infoTest);
      
      infoCoordinates = Text.createDefaultTextLabel("infoCoordinates");
      infoCoordinates.setTextureCombineMode(TextureState.REPLACE);
        infoCoordinates.setLocalTranslation(0, display.getHeight() - 55, 0);
        fpsNode.attachChild(infoCoordinates);
   }
   
   @Override
   protected void simpleUpdate() {
      tempVector.set(
            mouse.getHotSpotPosition().x,
            mouse.getHotSpotPosition().y);
      Vector2f travel = tempVector.subtractLocal(lastCoordinates);
      float softwareTravelDistance = travel.length();
      
      travel.set(mouseInput.getXDelta(), mouseInput.getYDelta());
      float harwareTravelDistance = travel.length();
      
      if (softwareTravelDistance != 0 && harwareTravelDistance != 0) {
         lastSensitivity = softwareTravelDistance/harwareTravelDistance;
      }
      
      lastCoordinates.set(
            mouse.getHotSpotPosition().x,
            mouse.getHotSpotPosition().y);
      
      sb.delete(0, sb.length());
      sb.append("D to toggles delta (current=");
      sb.append(mouse.isUsingDelta());
      sb.append("), F to limit fps (current=");
      sb.append(sleep);
      sb.append(").");
      infoTest.print(sb);
      
      sb.delete(0, sb.length());
      sb.append("x=");
      sb.append(mouse.getHotSpotPosition().x);
      sb.append(", y=");
      sb.append(mouse.getHotSpotPosition().y);
      sb.append(". Mouse speed: set=");
      sb.append(sensitivity);
      sb.append(", calculated=");
      sb.append(lastSensitivity);
      sb.append(".");
      infoCoordinates.print(sb);
      
      if (sleep > 0) {
         try {
            Thread.sleep(sleep);
         } catch (InterruptedException ie) { }
      }
   }
   
   public void updateInput() {
      super.updateInput();
      
      if (keyboard.isValidCommand(cmdToggleDelta, false)) {
         mouse.setUsingDelta(!mouse.isUsingDelta());
      }
      if (keyboard.isValidCommand(cmdToggleFps, false)) {
         sleep += 33;
         if (sleep > 100) sleep = 0;
      }
   }
   
   public static void main(String[] args) {
      BugTest game = new BugTest();
      game.setDialogBehaviour(
            SimpleGame.ALWAYS_SHOW_PROPS_DIALOG);
      game.start();
   }

}

I don't know if you have noticed, but the speed of delta vs no-delta is different even when you don't limit the frame-rate.

Yeah, that's why it's not the same, because of this bug…



The test calculates speed dynamically based on the actual mouse motion.

And according to the test the speed is nearly the same at super-high fps (around 500 fps). Around 100 fps you can feel its lower when using delta. The problem is the mouse speed drops very noticeably when you are around 50-30 fps. Around 10-15 fps you can barely move the mouse.

On top of that the mouse movement becomes jerky when fps fluctuates, say 50 fps ±10.

The mouse moves have to become jerky, because so has the fps… also, I am not so much interested in your computation of the dynamic speed of the mouse as much of the perceived speed (I assume you are doing it the right way  ;)).



Instead, just try to move the mouse at constant speed with your hand, and toggle the delta on/off while the fps are not being limited and you should sense the difference I talked about.

@duenez: I've run the test and hit D (toggle delta) while moving mouse smoothly across the screen. I don't see or fee any difference when the fps is not limited (at 500fps). Maybe it's the matter of faith :stuck_out_tongue:



As for the mouse being jerky on jumping fps: it's a different kind of jerky alltogether (not beef). What happens is that mouse goes slower then faster when fps jumps hence making it hard to predict where the cursor will end up. Makes for a rather frustrating user experience. As opposed to constant speed mouse motion: even on low fps you know where the mouse is gonna go. I should write another test to demonstrate the effect of jumping fps… here is is:



Now, try moving your mouse around and tell me if you feel in control. Notice, the test doesn't touch the mouse settings, all it changes is fps. Hit D to switch to delta=false and notice how much better it is, even on jumping fps.



import java.util.Random;

import com.jme.app.SimpleGame;
import com.jme.input.AbsoluteMouse;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.math.Vector2f;
import com.jme.scene.Text;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;

public class BugTest extends SimpleGame {

   private final String cmdToggleDelta = "toggleDelta";
   private final String cmdToggleFps = "toggleFps";
   private final String cmdToggleJumpingFps = "toggleJumpingFps";
   
   private AbsoluteMouse mouse;
   private float sensitivity = 1.5f;
   
   private Vector2f tempVector = new Vector2f();
   private Vector2f lastCoordinates = new Vector2f();
   private float lastSensitivity = sensitivity;
   
   private MouseInput mouseInput;
   private KeyBindingManager keyboard =
      KeyBindingManager.getKeyBindingManager();
   
   private Text infoTest;
   private Text infoCoordinates;
   private StringBuffer sb = new StringBuffer();
   
   private int sleep = 15;
   private boolean jumpingFps = true;
   private float jumpingInterval = 0.5f;
   private Random random = new Random();
   private float timer;
   private boolean bonusSleep;
   
   
   @Override
   protected void simpleInitGame() {
      mouseInput = MouseInput.get();
      
      mouse = new AbsoluteMouse("Test Mouse",
            display.getRenderer().getWidth(),
            display.getRenderer().getHeight());
      mouse.setSpeed(sensitivity);
      mouse.setUsingDelta(true);
      mouse.registerWithInputHandler(input);
      TextureState ts = display.getRenderer().createTextureState();
      // replace null with something else if you want an actual cursor
      ts.setTexture(TextureManager.loadTexture(null));
      mouse.setRenderState(ts);
      rootNode.attachChild(mouse);
      
      keyboard.add(cmdToggleDelta, KeyInput.KEY_D);
      keyboard.add(cmdToggleFps, KeyInput.KEY_F);
      keyboard.add(cmdToggleJumpingFps, KeyInput.KEY_J);
      
      infoTest = Text.createDefaultTextLabel("infoTest");
      infoTest.setTextureCombineMode(TextureState.REPLACE);
      infoTest.setLocalTranslation(0, display.getHeight() - 40, 0);
        fpsNode.attachChild(infoTest);
      
      infoCoordinates = Text.createDefaultTextLabel("infoCoordinates");
      infoCoordinates.setTextureCombineMode(TextureState.REPLACE);
        infoCoordinates.setLocalTranslation(0, display.getHeight() - 55, 0);
        fpsNode.attachChild(infoCoordinates);
   }
   
   @Override
   protected void simpleUpdate() {
      tempVector.set(
            mouse.getHotSpotPosition().x,
            mouse.getHotSpotPosition().y);
      Vector2f travel = tempVector.subtractLocal(lastCoordinates);
      float softwareTravelDistance = travel.length();
      
      travel.set(mouseInput.getXDelta(), mouseInput.getYDelta());
      float harwareTravelDistance = travel.length();
      
      if (softwareTravelDistance != 0 && harwareTravelDistance != 0) {
         lastSensitivity = softwareTravelDistance/harwareTravelDistance;
      }
      
      lastCoordinates.set(
            mouse.getHotSpotPosition().x,
            mouse.getHotSpotPosition().y);
      
      sb.delete(0, sb.length());
      sb.append("J for jumping fps, D to toggles delta (now=");
      sb.append(mouse.isUsingDelta());
      sb.append("), F to limit fps (sleep=");
      sb.append(sleep);
      sb.append("ms).");
      infoTest.print(sb);
      
      sb.delete(0, sb.length());
      sb.append("x=");
      sb.append(mouse.getHotSpotPosition().x);
      sb.append(", y=");
      sb.append(mouse.getHotSpotPosition().y);
      sb.append(". Mouse speed: set=");
      sb.append(sensitivity);
      sb.append(", calculated=");
      sb.append(lastSensitivity);
      sb.append(".");
      infoCoordinates.print(sb);
      
      if (jumpingFps) {
         timer += tpf;
         if (timer > jumpingInterval) {
            bonusSleep = !bonusSleep;
            timer = 0;
            // random between 0.3 and 0.63
            jumpingInterval = 0.3f + random.nextFloat()*0.33f;
         }
      } else {
         timer = 0;
         bonusSleep = false;
      }
      
      int totalSleep = sleep + (bonusSleep ? 30 : 0);
      if (totalSleep > 0) {
         try {
            Thread.sleep(totalSleep);
         } catch (InterruptedException ie) { }
      }
   }
   
   public void updateInput() {
      super.updateInput();
      
      if (keyboard.isValidCommand(cmdToggleDelta, false)) {
         mouse.setUsingDelta(!mouse.isUsingDelta());
      }
      if (keyboard.isValidCommand(cmdToggleFps, false)) {
         sleep += 15;
         if (sleep > 30) sleep = 0;
      }
      if (keyboard.isValidCommand(cmdToggleJumpingFps, false)) {
         jumpingFps = !jumpingFps;
      }
   }
   
   public static void main(String[] args) {
      BugTest game = new BugTest();
      game.setDialogBehaviour(
            SimpleGame.ALWAYS_SHOW_PROPS_DIALOG);
      game.start();
   }

}

My guess would be that some deltas are missed on low fps. But with your tests I cannot reproduce the behavior you described. It feels alright for any fps (even jumpy) - no difference between with and without using delta. Even the calculated mouse speed it always ~1.5 :expressionless:

you can try to apply this patch locally to see if my guess is right:

--- jME/src/com/jme/input/mouse/MouseInputHandlerDevice.java   (revision 1.6)
+++ jME/src/com/jme/input/mouse/MouseInputHandlerDevice.java   Fri Aug 10 11:28:16 CEST 2007
@@ -177,6 +177,7 @@
             super.putTriggerInfo( event, invocationIndex );
             event.setTriggerIndex( axis );
             event.setTriggerDelta( delta );
+            delta = 0;
             event.setTriggerPosition( position );
         }
 
@@ -186,7 +187,7 @@
 
         public void checkActivation( char character, int axisIndex, float position, float delta, boolean pressed, Object data ) {
             if ( axisIndex == this.axis ) {
-                this.delta = delta;
+                this.delta += delta;
                 this.position = position;
                 if ( !allowRepeats ) {
                     activate();


attention: hotfix! not to remain in code afterwards!

That does the trick! It steel doesn't feel as good as when not using delta (by now its probably a personal impression rather than an observed fact), but infinitely better.

This behavior must only manifest on my system… :confused: maybe it affects more Linux machines?

I've measured the total distance traveled for absolute mouse and mouse input. The absolute mouse starts at the edge of the screen, and doesnt report changes, so initially they start off differently. Also i keep moving mouse far away from the edge as any delta over the edge will not translate into mouse motion and will not be added to the distance traveled for the absolute mouse. The test prints the total distance traveled every 5 seconds.



Here is what I get after your patch (on jumping fps simulation):

AbsoluteMouse=902.35657 MouseInput=751.7663 diff=150.59027

AbsoluteMouse=8388.615 MouseInput=8237.762 diff=150.85352

AbsoluteMouse=13988.238 MouseInput=13837.385 diff=150.85352

AbsoluteMouse=17480.14 MouseInput=17329.26 diff=150.88086

AbsoluteMouse=21724.402 MouseInput=21576.246 diff=148.15625

AbsoluteMouse=27166.701 MouseInput=27018.545 diff=148.15625

AbsoluteMouse=28324.477 MouseInput=28179.229 diff=145.24805

AbsoluteMouse=31899.312 MouseInput=31754.445 diff=144.86719

AbsoluteMouse=35527.094 MouseInput=35381.684 diff=145.41016

AbsoluteMouse=39762.95 MouseInput=39616.273 diff=146.67578

AbsoluteMouse=44452.33 MouseInput=44303.363 diff=148.96484



Here is what I get without the patch:

AbsoluteMouse=879.0363 MouseInput=2718.8435 diff=-1839.8071

AbsoluteMouse=2823.0408 MouseInput=8900.307 diff=-6077.2656

AbsoluteMouse=4874.697 MouseInput=15276.512 diff=-10401.814

AbsoluteMouse=6686.7876 MouseInput=21094.863 diff=-14408.076

AbsoluteMouse=8808.735 MouseInput=27774.545 diff=-18965.809

AbsoluteMouse=10605.04 MouseInput=33501.855 diff=-22896.816

AbsoluteMouse=11879.96 MouseInput=37278.72 diff=-25398.758

AbsoluteMouse=13240.423 MouseInput=41494.33 diff=-28253.906

AbsoluteMouse=14700.684 MouseInput=45378.324 diff=-30677.64





Just in case: the code for this test is the little piece inserted into simpleUpdate() method of the last test posted above:


...
if (softwareTravelDistance != 0 && harwareTravelDistance != 0) {
         lastSensitivity = softwareTravelDistance/harwareTravelDistance;
      }

// new code
travelAbsoluteMouse += softwareTravelDistance;
      travelMouseInput += harwareTravelDistance*sensitivity;
      printTimer += tpf;
      if (printTimer > 5) {
         System.out.println("AbsoluteMouse=" + travelAbsoluteMouse + " MouseInput=" + travelMouseInput + " diff=" +(travelAbsoluteMouse-travelMouseInput));
         printTimer = 0;
      }

@Irrisor: so what is your consensus about this problem:

does it need more testing on other machines to prove it affects enough audience to constitute a bug?

is it a minor thing and should just be ignored?

Well if that patch hurts nobody and it's important for you I can commit it. But as you suggested we should do more testing before that, as I cannot reproduce the problem over here (neither windows nor linux) - maybe it's the mouse hardware?

I can do more testing on windows to exclude the hardware possibility and narrow down the range or system this may affect. However my windows is essentially blank, so I need to know whether the testing with the latest jme release will suffice or I absolutely must test the cvs version.

Please take a cvs snapshot, you can zip your linux jme folder and copy it to your windows box.

Irrisor, if this can help you: I'm using SuSE Linux 10.3 alpha 4 with java 6 and I've got exactly the same problem.

Ah, ok. And does the patch help you, too?

I didn't yet tried the patch.

I have used current jme from CVS folder (without your patch, that was triple-checked) and retested for this bug on Windows using the same machine. AbsoluteMouse worked fine.

So this bug is not hardware related and occurs on some (or all) Linux systems.