Message queue problem with repaint throttling

We’re also currently facing big problems with this behaviour in plugins running on macOS, didn’t discover until recently that it was actually related to the message thread event handling and repaint throttling.

I will also do some tests with the above code snippet. Seems like a reasonable change.

bump, this is an acute problem, and should be resolved as soon as possible.

2 Likes

I remember we tried this with a timer before, but there were some race conditions where the last repaint message would sometimes get lost. I’ll check if I can still wrap my head around why we didn’t go for the Timer solution which we originally intended.

Okay thanks for replying to this, the current solution is definitely a big problem. If you have a plugin which redraws faster than the 30fps, other plugins may receive no messages anymore until the redraw is lower than 30fps.

But this happens not all the time, but often enough. It seems that macOs (is this the new spelling) somehow prioritize the message queue differently ( no FIFO behavior )

This must be fixed, also on master, because all vendors who now build plugins with this current behavior will create potential issues on other plugins.

1 Like

I can’t see any race-condition in my solution where the last repaint message might get lost. If you find some please let me know. Hopefully this will fixed soon (also on master, because of the non-cooperative handling regarding the message thread )

Just for reference - this is why we put in that commit in the first place:


I need to make sure that your fix does not break this again. The problem is, I think this only happened on El Capitan. I can’t reproduce it on Sierra even when i completely remove the async updater thing (and redraw directly).

Yes, i know, it looks like redraws which will be posted often will somehow prioritized so that other messages will be slowed down, but in the same way you reduce redraws, by posting new messages, these are again become prioritized, which again slows down other message handling. So the key to success is, not posting more messages (or posting permanent messages as a pseudo timer), instead use a real timer thread.

PS: I wouldn’t remove repaint throttling at all, because i think 98% of all audio people still use El Captian), and we have to be sure that the initial issue is really removed with Sierra

Thanks chkn. I’ve pushed your fix to develop. Please test!

2 Likes

Thanks!

Yes, i will do that

I just wondering, if its better to measure the time with Time::getMillisecondCounter(), because its the same how the Timer-Class it does, to make it more exact?

Here is the diff

diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm
index 8281315..04eee0d 100644
--- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm
+++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm
@@ -83,6 +83,7 @@ static NSRect flippedScreenRect (NSRect r) noexcept
           textWasInserted (false),
           isStretchingTop (false), isStretchingLeft (false),
           isStretchingBottom (false), isStretchingRight (false),
+          lastRepaintTime (Time::getMillisecondCounter()),^M
           notificationCenter (nil)
     {
         appFocusChangeCallback = appFocusChanged;
@@ -907,8 +908,11 @@ void repaint (const Rectangle<int>& area) override
 
         // already a timer running -> stop
         if (isTimerRunning()) return;
-
-        int64 msSinceLastRepaint = Time::getCurrentTime().toMilliseconds() - lastRepaintTime.toMilliseconds();
+        ^M
+        const uint32 now = Time::getMillisecondCounter();^M
+        const int msSinceLastRepaint = (int) (now >= lastRepaintTime ? (now - lastRepaintTime)^M
+                                   : (std::numeric_limits<uint32>::max() - (lastRepaintTime - now)));^M
+        ^M
         static int minimumRepaintInterval = 1000 / 30; // 30fps
 
         // When windows are being resized, artificially throttling high-frequency repaints helps
@@ -917,9 +921,9 @@ void repaint (const Rectangle<int>& area) override
         if (shouldThrottle
             && msSinceLastRepaint < minimumRepaintInterval)
         {
-            startTimer (static_cast<int> (static_cast<int64> (minimumRepaintInterval) - msSinceLastRepaint));
+            startTimer (minimumRepaintInterval - msSinceLastRepaint);^M
             return;
-        }
+        };^M
 
         setNeedsDisplayRectangles();
     }
@@ -935,7 +939,8 @@ void setNeedsDisplayRectangles()
         for (const Rectangle<float>* i = deferredRepaints.begin(), *e = deferredRepaints.end(); i != e; ++i)
             [view setNeedsDisplayInRect: makeNSRect (*i)];
 
-        lastRepaintTime = Time::getCurrentTime();
+        lastRepaintTime = Time::getMillisecondCounter();^M
+       ^M
         deferredRepaints.clear();
     };
 
@@ -1364,7 +1369,7 @@ void textInputRequired (Point<int>, TextInputTarget&) override {}
     NSNotificationCenter* notificationCenter;
 
     RectangleList<float> deferredRepaints;
-    Time lastRepaintTime;
+    uint32  lastRepaintTime;^M
 
     static ModifierKeys currentModifiers;
     static ComponentPeer* currentlyFocusedPeer;
1 Like

hi chkn,
Today I had issues with your first mod (not the last patch). repaint() calls from my component stopped working. I do not know why, since it was working perfectly.
For a moment I thought it was something I changed in my project, then I have returned to an old commit where everything worked perfect, but the problem persisted. It has been solved by returning to the original version of NSViewComponentPeer.

It is very weird, since all these days it was working perfectly.

EDIT: Now I’ve applied your last patch and it works again.

@chkn: OK I’ve changed the workaround to use Time::getMillisecondCounter.

@xeneize: Does this also happen on the latest develop branch? I hope not!

Thanks fabian!

My first version, still uses the AsyncUpdater (which was always triggered by any repaint), while the patch in the develop-branch (and the latest version) directly posts the rectangles when needed (setNeedsDisplayInRect) or uses the timer, maybe thats the difference, why the current version works better.

I recommend to use the latest patch from the develop branch, and if still something going on, to debug.

BTW: The reason why i preferred Time::getMillisecondCounter was, I also measure repaints which are still faster than 30fps, i guess this is something how the Timer works (i double-checked the whole timer code, maybe Time::getMillisecondCounter isn’t just precise enough), but otherwise it effectively reduces the amount of repaints.

@fabian
is this right? in your patch

(std::numeric_limits<uint32>::max() - lastRepaintTime) + now);

should it be

(std::numeric_limits<uint32>::max() - (lastRepaintTime - now)));

I think that’s the same right? I always like to write it the first way: A + B where A is the amount of elements until we wrap around and B is anything left over.

Ah yes, you are right! Thats more clear!

Hi fabian,
not, is the 4.3 version, with the modified NSViewComponentPeer. I will test the “test” development branch.

I also think you should fix the master asap. I’m not 100% sure that i have the same issue, but it looks like other plugins do not receive any messages when i use a timer that refreshes fast. (10ms) Also on the latest OSX.

You are absolutely right! Should be fixed in the master asap

1 Like

Hi guys,
What is the status of this? Has it been solved on JUCE 5?