Mouse Move throttling?


#1

What is this code good for:

[code]// (Throttling the incoming queue of mouse-events seems to still be required in XP…)
static uint32 lastMouseTime = 0;
const uint32 now = Time::getMillisecondCounter();
const int maxMouseMovesPerSecond = 60;

if (now > lastMouseTime + 1000 / maxMouseMovesPerSecond)
{
lastMouseTime = now;
doMouseEvent (position);
}[/code]

A user “felt” that JUCE’s mouse responsiveness was bad. So I found this code and commented the if clause and then he was happy. Could you explain why this code has been added to reduce the number of mouse move messages? Is that really a good idea? It’s true that when one removes it, one can feel that JUCE windows move around in a much smoother fashion etc… It’s something that always felt wrong in JUCE.


#2

That’s some very very very old code!

It was definitely an important fix back when I wrote it - I seem to remember that there were circumstances where events would arrive faster than the app could cope with them, the whole thing ground to a halt in some sort of horrible feedback loop. I’d forgotten all about it though, and am pretty sure it can be removed now for Vista and Win7.

But… It throttles it to 60fps! A TFT display can’t physically refresh any faster than that, and it’s at the limit of what the human eye can perceive, so I think there might be a bit of a placebo effect involved with your user!


#3

There’s definitely no placebo involved. It makes a HUGE difference in overall responsiveness. Let me put it this way: I always “felt” that other Windows apps would react more quickly to the mouse than JUCE apps, now that I removed this code it’s all gone (Thanks go to the person who pointed this out!). I suppose there’s no extra throttling code on iOS, because there I can feel a very bad sluggishness too in JUCE apps, much worse than on XP?!


#4

But… It throttles it to 60fps! A TFT display can’t physically refresh any faster than that, and it’s at the limit of what the human eye can perceive, so I think there might be a bit of a placebo effect involved with your user!

Now, now, that signifies a lack of imagination! :stuck_out_tongue:

Right away, I’d think of an aliasing problem - that some arithmetic or phase issue meant that “60fps” hard limit has undesirable perceptual characteristics like jerking or submarining (where it flickers as it moves).

Actually, the frame limit in the code sample is 62.5fps if you read the code carefully - so if there were in fact a 60Hz clock going, you might experience a “beat frequency” at the difference, 2.5Hz, which is quite within the perceptual range.

You could easily test if it was that by adding some dithering to the process - in other words, changing that code a little so that the interval between samples varied randomly - each time you get a mouse motion, you set a new “next time that mouse motion is acceptable”. You probably would want to do that anyway, having anything at a hard 60Hz is just a Bad Idea (even if it is really 62.5Hz).

There’s a completely unrelated issue, actually, and that’s that you will often lose your last mouse event with the “if”, which might leave the results several pixels off if you were dragging fairly fast. In other words, it’s often the case that (now > lastMouseTime + 1000 / maxMouseMovesPerSecond) is false, but you never get another mouse event.

I’ve run into this issue on multiple systems and it needs a name - what about “throttle drip”? The last drip of your mouse action gets caught in the throttle?

So “aliasing,” “beat frequencies,” (which are a form of aliasing) and “throttle drip” are possible causes of issues, and that’s accepting the 60Hz refresh limit on your monitor (but who’s to say that a pixel that has had two different values set during a refresh period is necessarily displayed the same as a pixel which had only one value set?)


#5

I’d like to add that my experience with Juce (limited to one app, but a lot of that app) has that its GUIs have been fast, even zippy, on a variety of systems - which absolutely doesn’t mean that you’re not experiencing issues while doing the right thing, but it does mean that I’d be really interested in finding out what’s going on with your program and how to avoid it in mine.

Another possibility for poor behavior overall might be some sort of threading/locking issue in your program basically unrelated to the segment of code presented. When your program spins, background threads might proceed more quickly, but might get quickly locked when the program waits.


#6

Final note… :smiley:

I now realize that you need to sample your mouse position at at least 120Hz in order to 100% correctly display it on a monitor with 60Hz refresh rate - and yes, it’s a Nyquist thing.

Imagine an ideal case - that your mouse were moving uniformly with infinite precision, from left to right, and every 1/60th of a second a lightbulb went off revealing its position for the monitor to represent.

Suppose the mouse is moving at exactly 60 pixels a second. The mouse on the monitor will also move uniformly at 60 pixels a second - but with a delay from 0 to 1/60th of a second depending on the phase difference between the mouse’s pixel transitions and the update frequency of the monitor.

Now suppose that the mouse is moving at 59 pixels a second. What happens now?

Well, the first camera flash goes off at 0s, and the mouse is on pixel 0 . But the second camera flash goes off at 1/60s and the mouse is still on pixel 0 - because it doesn’t hit that second pixel until 1/59s, which is a tiny bit later than 1/60s (1/3540s to be precise). Then the third flash goes off and the mouse is now on pixel 1 and now it continues forward until the next second starts and it loses another pixel.

So the system represents “59 pixels a second” as “60 pixels a second for 59 pixels and then wait”, which rather different.

Now you’re saying, what’s a pixel between friends? Who would notice? And you’ll notice that the “throttle drip” at this speed is only at most one pixel.

But in fact, this is at quite slow mouse speeds. My screen is 2000 pixels wide and I regularly go over half of that in half a second. At 2000 pixels per second, these errors will be 30 times as likely. Your maximum throttle drip is 30 pixels! This means that you could quickly drag something across the screen and end up 30 pixels off!

Now, you don’t quite see that because you quickly correct - but you see the “wild cursor” for 1/60th of a second and it registers as jerky.

Let me tell you that I never use this sort of throttling after a lot of bad experiences. I intercept each and every event, and make quick tests to see if anything has really changed and I can ignore the message (“on change” strategy, I call this). Most of the time you can quickly detect that your actual objects will stay on the same pixels with a small amount of arithmetic and return from the mouse update.

You know, come to think of it I had a similar issue with Juce and a time counter, which I was sampling at 40Hz initially and even moving to 100Hz had… weirdnesses (I think threading issues). Going to an “on-change” mechanism worked really well, all the counters are updated right from my sample player and move extremely smoothly.

And the issue with “throttle drip” was significant there - because there’s a millisecond time display so if you “stop at a point” you might not see exactly the right time.


#7

Yes, very good point about it causing jitter.

In the latest check-in I’ve disabled throttling when running Vista and above. I’m going to leave it in for XP, because although it was many years ago when I wrote it, and I’ve forgotten the exact problem that it solved, it was definitely vital for some reason!


#8

A small remark here, Time::getMillisecondCounter() use timeGetTime() on Windows which returns a 10ms precision on average (since you can’t be sure another application hasn’t called timeEndPeriod(), or was simply closed).
So, while the code below seems correct, you are probably reacting to fast/slow depending of the + or - 10ms shift on the test (which leads to mouse event allowed whenever they are in the [+6ms; + 26ms] range after the previous one, that’s well above the 33ms required for human “jitter” perception).

I still have a WinXP box, I’m using everyday, so if you need to test another code, feel free to ask me.