OS/X: Menu commands fail when application is playing


#1

Getting to the end on this project, but of course I’m getting to the small, difficult bugs.

I have a digital audio application with menus containing submenus. They work fine if I’m not “playing” - the menus come up OK and I can get to the submenus.

But when I’m “playing” (yes, I know this is not a Juce concept but a concept of my app), the menus come up fine - but none of the submenus come up when I click on them, and none of the menu commands work from the menu (they work fine from the keyboard or the GUI).

The key accelerators work fine, all my buttons work fine, my screen updates fine, my CPU is mostly idle (I’m using about 15% of one of my eight cores). I can do all sorts of abusive things to the program like hammering on command keys and it works without a flaw.

So what could it be?

If I were grabbing the MessageManagerLock, surely my GUI would fail to update? If I had lock contention, surely I’d hear it or see it or I’d have some other effect? I thought I might be calling menuItemsChanged() too often but I am not.


#2

I have a solution to this, though I’m again not exactly sure what the underlying issue is.

Basically, I was posting “too many” CallbackMessages to the MessageManagerQueue. Now, I’ve been doing much the same thing all along without issues, and I didn’t think there were that many messages (less than 100 messages a second). Nor was the queue falling behind as far as I could see, because all sorts of other things that use it seemed to keep happening.

Once I figured out which operation it was, I moved it to a separate thread which I notify as needed. Interestingly enough, that thread does take the MessageManagerLock just as frequently, but with no apparent ill effects - indeed, I could speed it up considerably and still have it work well…

I’m a little taken aback that this can happen silently with no other symptoms, but as usual, now I can get it to work I’m less worried about the aesthetics of the thing…


#3

I spoke too soon. :frowning:

That didn’t fix my issue - I just hadn’t worked out what the correct pre-conditions were to cause it - I need to be scrolled in pretty far to my waveform.

I’m continuing to debug.

One of the things I’m also finding is that when I move a tall, thin component on my large canvas, for some reason Juce (not my code) is requesting a repaint of a huge area in the canvas, despite the fact that repaint is only (and correctly) called for the two tiny slivers before and after. I’m overriding and instrumenting internalRepaint() and paint(), and we get (as an example):

paint: clip area: (0, 0, 1159, 350)
repaint: (62, 0, 9, 350)
repaint: (66, 0, 9, 350)
paint: clip area: (62, 0, 1036, 350)

The first call to paint should mark the whole area (1159 x 350) as clean.

The next two repaints are exactly as I’d expect - my tall, thin component moved from x = 62 to x = 66. There don’t seem to be any subsequent repaints - and yet, when paint is called, instead of the clip area being (62, 0, 13, 350), it’s (62, 0, 1036, 350).

This is pretty bad. Drawing my waveform is somewhat expensive and I do it pretty often, because the time cursor is sweeping over it. The fact that the clip area is consistently almost two orders of magnitude greater than it should be means I’m spending almost 100 times as much time redrawing as I should be. This alone might account for the menu issues, but even if it doesn’t, I need to figure this out.

(I’m sort of surprised I don’t see this in my CPU meters, but I never trust those anyway…)

Note that the area is just a Plain Old Component and I’m not using any affine transformations or anything fancy like that.


#4

Update: I get better results, but still not perfect results, by keeping track of my own dirty rectangle and clipping to that.

I’m also limiting now how far I can zoom in :frowning: and that prevents the problem from happening.

However, this isn’t really a good solution - I’d like to zoom in a little more than a five second window, and I have no idea if this fix will work on an old, slow machine. (On the other hand, I’ll get a big boost from running an optimized binary - this is a debug binary).

There seem to be several problems:

  1. Juce seems to get the dirty rectangle calculations wrong.
  2. Somehow, Juce is dropping messages on the floor silently.
  3. Somehow, I’m managing to exhaust some resource or doing something to cause Juce to drop these messages, and yet I see little use of CPU, almost no disk access, a steady RAM footprint that’s just what I expect, and basically no reason to suppose that I’m doing anything wrong.

#5

Further update:

Two discoveries, both bad.

First, it’s not the case that internalRepaint() is the only way that a component learns which parts are dirty. If you for example put a component in front of your component and then use ComponentAnimator::fadeIn or ::fadeOut, your component gets blanked immediately and never seems to get a repaint command of any type.

What’s the point of a nice method to override like internalRepaint if there is some hidden magic in the component with “additional information”? I ended up having to start a thread that goes off right after the component in front has faded away to tell the main component to switch back to the optimized repaint. Lame!

And second, that if I rigorously test, even that 5 second window is a little too small - I had to raise it to 8 second. Also lame. :frowning:

I can’t for the life of me see why juce::AudioThumbnail would take longer to paint the same screen area if it represented less time rather than more - or perhaps it’s that the cursor is moving faster and thus invalidating more screen real estate? But I seem to have plenty of memory bandwidth and plenty of CPU so I don’t see it as a problem either way.

Paradoxically, this has improved my trust in my own code, because I’ve tried all sorts of tricks to try and get past this problem, and the codebase has responded predictably in each case.

I have this in a state where it’s at least stable for now, even if the user can’t zoom in all the way. Once I’ve released I’ll attempt to track down exactly what’s going on.


#6

[quote]Basically, I was posting “too many” CallbackMessages to the MessageManagerQueue. Now, I’ve been doing much the same thing all along without issues, and I didn’t think there were that many messages (less than 100 messages a second). Nor was the queue falling behind as far as I could see, because all sorts of other things that use it seemed to keep happening.

Once I figured out which operation it was, I moved it to a separate thread which I notify as needed. Interestingly enough, that thread does take the MessageManagerLock just as frequently, but with no apparent ill effects - indeed, I could speed it up considerably and still have it work well…[/quote]

You’d have to send a really enormous number of messages to overload the queue, but I guess it’d be possible if you just keep on sending them faster than they can arrive. Probably the reason that the MessageManagerLock works is that when it sends a message, it’ll block until the message has arrived before it sends another one.

Sounds like the dreaded CoreGraphics repaint bug… If you use the software renderer, the dirty region should be done optimally, but CoreGraphics doesn’t provide any way to accurately detect whether a rectangle intersects its clip region, so any components that lie within the entire bounding box of the dirty region have to be rendered - nothing I can do about that, unfortunately! (I think there are some other threads about this problem)

Don’t understand that… Whenever a component gets moved or made visible/invisible, the normal repaint process happens (?)


#7

You’d have to send a really enormous number of messages to overload the queue, but I guess it’d be possible if you just keep on sending them faster than they can arrive.

I wasn’t sending that many - 86 a second (44100/512). Now I’m not sending them at all but I still get the same issue.

Core Graphics

But I’m not using Core Graphics, as far as I know? It’s just plain old components…?

Would this explain why I get a different “dirty rectangle” from yours if I accumulate mine using only calls to internalRepaint? I’d expect that if I accumulated a bounding rectangle from calls to internalRepaint, I’d get a clipping region that’s at least as big as the correct one, but the clip rectangle I in fact get from Juce is much bigger than the (apparently correct) one I am getting from my own calculations.

Don’t understand that… Whenever a component gets moved or made visible/invisible, the normal repaint process happens (?)

Would that mean that internalRepaint got called? Because it really doesn’t seem to be getting called after the fade process starts…

This is all good information and will help me at least make a clear bug report - if there is a bug to report, that is!


#8

[quote]But I’m not using Core Graphics, as far as I know? It’s just plain old components…?

Would this explain why I get a different “dirty rectangle” from yours if I accumulate mine using only calls to internalRepaint? I’d expect that if I accumulated a bounding rectangle from calls to internalRepaint, I’d get a clipping region that’s at least as big as the correct one, but the clip rectangle I in fact get from Juce is much bigger than the (apparently correct) one I am getting from my own calculations.[/quote]

If you’re running in OSX then by default it’ll be using CoreGraphics for all the dirty region handling. That generally works fine, but doesn’t provide access to the actual dirty region - see this thread:
http://www.rawmaterialsoftware.com/viewtopic.php?f=4&t=6701

Not sure why your own region would differ in size though.


#9

Hmm, interesting (and a little scary, frankly - I hate when I can’t explain things).

I have to put this down for a while, because it’s low priority and I’m out of time. I’ll be investigating it again in February or so and will have time to present a systematic test case…