Carbon • 51:33
Learn how to take full advantage of Carbon events to improve your application performance. This session covers Carbon events-based alternatives to common Mac OS 9 programming practices that will deliver improved performance on Mac OS X. Developers will also learn how to replace their pooling and tracking code with Carbon events to maximize performance.
Speakers: David McLeod, Curt Rothert
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Ladies and gentlemen, please welcome Mac OS X evangelist, Xavier Legros. Hi everybody. Welcome back. Welcome to session 207, Improving Performance with Carbon Events. So, great. You're all on Mac OS X now. You have a great Carbon application. Now, what's the next step? We showed you this morning about where the toolbox is going, and we really hope that you guys are going to take advantage of these new features. But there is something that is very important. In order to be a good and a great Mac OS X citizen, performance is very important.
Carbon event is a way to achieve a great level of performance. In this session, we're going to talk about some of the pitfalls and some of the concepts that Carbon developers have been using on Mac OS 9 that don't really work well in this new environment. And for that... I'd like to introduce David McLeod, who's not exactly the apparent slave, but works in the toolbox team, and will take you through some of the main steps to make your application a great Mac OS X citizen. Hey, David.
Thank you. Thanks for sticking around after those other sessions. Ed and Guy gave great sessions and showed all the new features that we have, but all those features aren't very good unless your application is performing. So what I'm going to try to show you today is a few tips to use Carbon events to get some great performance out of your applications.
So just a quick overview of what I'm going to talk about. First, I'm going to cover timers and how to use them in your application, as opposed to doing pooling. Then I'm going to talk about mouse tracking and mouse tracking regions. This is a new feature in Jaguar.
Next, I'm going to talk about Carbon events as notifications. It's kind of an overlooked feature of Carbon events. After that, I'm going to talk about tracking loops. What I mean by that is when you watch the mouse for mouse down, mouse up,
[Transcript missing]
And also right at the end, I have a few sort of unrelated to Carbon event tips sort of compiled based on some feedback we got on mailing lists. So let's get right into it.
What is a timer anyway? I'd just like to read off the slide here, because I wrote that one and it's a really good, really good definition. A Carbon event timer is a way to request delayed execution of code. Now, Carbon event timers are a little bit different than the timers you might have been used to in OS 9. They're not VBL tasks or anything odd like that.
They're controlled by the event loop. This is really important because those other ways of doing timers, you couldn't really call into the toolbox because you were accessing at interrupt time. So because they're controlled by the event loop, you can call into any of the HI toolbox APIs safely.
They're time-based, which is important because you can say, I would like something to happen X seconds from now, or I would like something to happen on a frequency of every half second, and not worry about ticks or anything like that. It's all nice human-based time. And a nice side effect is that the code stays with the implementation. You register an event timer with a UPP that calls back into your code. So your timer activities can stay with the rest of your implementation. It keeps everything simple and easy to code.
Primers are important with respect to performance because The event loop is going to do everything for you. So from your point of view, there's no continuous checks to see if it's time for you to do something, do an animation or do something for the user. There's no wasted time watching for and processing null events. It eliminates all of that time in the overhead entirely, so you don't have to worry about it.
There are three kinds of timers. There's a one-shot timer. You just set it and later it occurs and your callback happens once. There's a periodic timer. You set it. After a while, you get a callback. And then periodically afterwards, with a regular frequency, your timer callback will get called. and there's a new kind of timer in Jaguar called an idle timer. What an idle timer is, I'll go into in detail in a second.
So like I said, a one-shot timer, it fires once. You set up a duration when you first create it, say, you know, I want to be called in two seconds or so. After it fires, it doesn't get destroyed or removed from the timer loop, from the event loop. Its duration is just set to forever, so the next time it's going to happen is forever from now. What you can do is reset it to refire at a later time.
And, say you set a duration and you change your mind before... That callback is called. You can chicken out or delay it so that it happens at a later time and cancel it entirely. A good example of that would be a dialogue. Say it comes up and no user interaction happens for a while. You want to say, I want this to go away after two minutes. You can just automatically make it go away using a timer.
Periodic timer is very similar to one-shot timer. It happens, you set a duration and your callback gets called once after the duration you specify. But then after that, it gets called back again at specific intervals. And you can set the interval to any time you want. Like the one-shot timer, it can be delayed or cancelled before you, before it actually gets executed. You have complete control over that.
And a good example of this, and maybe even a little known fact, is that we use timers everywhere in the toolbox where there's animation. So when you see a pulsing button, not throbbing, you see the chasing arrows, or, you know, the There's another one. Progress Bar. I had to eliminate that one because I say Progress Bar and nobody knows what I'm talking about. And lastly, the new kind of timer in Jaguar called an idle timer.
These you set to fire a certain duration after the user becomes inactive. And what that means is when the user stops moving the mouse around, or stops clicking the mouse, or stops touching the keyboard. Like the other two, it can be delayed or canceled before it gets called back.
A good example of this would be type ahead in a search field or something like that. You might not want to process all the events the second the user types. You might want to wait. You might have a huge database. It might be a spell checker lookup or something like that.
You might want to make sure they're good and settled on the letters they've typed and they've pressed backspace enough times like I would. Another good example would be input validation. You want to make sure what they have in a text edit field, for example, is what they're going to type before you validate whether it's good or bad instead of doing it over and over. So let's just have a look at what the APIs look like. Making a one-shot timer is fairly straightforward. You call the API, install event loop timer.
The second parameter here, 10 times k event duration second, specifies the first time that it's going to fire. So 10 seconds from this point, my timer callback UPP is going to be called. The third parameter, zero, just fire once, is what makes this a one-shot timer. So it's gonna fire once, and then it doesn't have a refiring frequency. That's what's used to make a periodic timer. It's the exact same API.
In this case, I used the same first fire time, 10 times k event duration second. But you'll see the third highlighted parameter, 2 times k event duration second. That's the frequency with which it will be called back after the first time it's called. and Idle Timer has its own API.
We couldn't recycle those other ones because it's a special kind. It's called Install Idle Timer. But all of the parameters are the same. You'll see the second parameter is the first fire time again. And there's even a third parameter, and the Idle Timer can be called with a frequency after it first starts.
Just a little bit more on event loop timers. There's a couple more APIs that are important. Like I said with the one-shot timer, after it fires, it does not go away. And you can reuse it afterwards. So what you can do is set event loop timer next fire time.
What happens with that is the time gets set and it's just like the first time you installed it. This is also the way that you can delay any of the timer callbacks. Lastly, if you want to remove an event loop timer entirely, that's the API. We always have our creative names, like Guy says. So I'd like to ask Curt Rothert, another engineer on the High Level Toolbox team, to come up and give us a demo.
Curt? Thank you, David. So it's great that you can move your application up to Mac OS X, but it's more important that you be a good citizen on the system. It's really important in terms of performance. One thing that I typically would do when I'd write a wait-next-event application is I would be lazy or not want to necessarily calculate the sleep time necessary when I need to be called back.
And so if you saw the session 203 on migrating to Carbon events, I was really trying to push home to get off of wait-next-event for your null event idle processing. So what can you do instead? Well, timers are an excellent way of doing it. I just want to demonstrate. I don't always believe everything that I hear until I actually see it in action. So I just wanted to demonstrate what's going on here.
So, I have this application and I'm using WaitNext Event with a sleep timeout of zero. Because I may not know when I want to be called back or I need to do some calculations. There's some overhead associated with that. Like I get the current tick counts and I say, "Ooh, I want to be called back in a third of a second or so." And so I add the appropriate ticks to that. And then I wait and check with WaitNext Event and see if it's the appropriate time to update my animation. So first, since this is a performance session, I want to bring up CPU Monitor. So you can see how the CPU is. I'm sitting idle right now.
Now I'm just sitting waiting next event with a timeout of zero, which is spinning on the processor. It's consuming every resource that the computer has. I mean, if you put this in perspective, your laptop would be burning your legs right now. Which is just not a very good user experience.
In addition, I've heard the argument that I want my application to be as responsive as possible for the user. Well, that's fine, but your computer is not responding, and in subtle ways you're becoming a less, you're making a less happy user because they're downloading the background or maybe they're ripping a CD and it's going to be going slower.
So the solution is to use event loop timers. I mean, that's pretty dramatic. All I need to do is update this image one third-- every third of a second. And you can see that the CPU is hardly utilized at that point. Now let's see what's going on in the code.
So in the case where I'm using Wait Next Event, I'm just defining the tick delay to be one-third of a second. Or since there's 60 ticks per second, I'm just dividing that by three.
[Transcript missing]
and when I idle, I just call this one function and all it does is it updates the image that you saw.
Now, this is what I'm doing when I'm idling with WaitNextEvent. Now, this was a mildly contrived example. If you're just doing one simple animation, you're not necessarily going to sit on WaitNextEvent with a zero timeout. But it's important to demonstrate because, as I said, I have heard the argument that I want my app to be as responsive as possible. So you can see I'm sitting on WaitNextEvent spinning, and then I'm doing a calculation to see if this is the appropriate time to update my animation, and then I idle, and then I recalculate.
Well, instead of doing that, It's very simple to do in order to idle with event loop timers. You just create your event loop timer. In this case, I'm not waiting because I want it to call back immediately. My delay is 1/3 of a second, like you saw defined above.
and then I can still continue to sit on wait next event. But this time, I've made the timeout much longer. The events are coming in. The idle is happening at the same frequency using a different method. And you can see from my example that the CPU is hardly being utilized at that point.
So next, I would like to show you a simple demonstration about idle timers. Now what I'm going to do is I'm going to bring up an application, and as long as you're responding or you're interacting with the application, the application will stay awake. But we're going to install an idle timer, so when the user becomes quiet and doesn't do anything, something interesting is going to happen.
and many of you have probably seen Guy. And the whole point of this exercise is to keep him awake because he's pretty tired after preparing for WWDC. So we can poke him, you know, startle him. Oh, he's pretty scared. Or we can even do the stare down. It's important we can discuss his pastimes. He's really into hockey and Canada. I don't know why. Let's have a conversation with him.
But the interesting part is when we don't do anything, he's really tired and he goes to sleep. So let's see that again. As long as we're active with them, things are fine. We can poke them, we can scare them, prod them.
[Transcript missing]
This is where we install our idle event timer. And as David mentioned, the parameters, you're installing it on the current event loop with our timeout. And then after that, we don't want to call back, so the duration is forever. and when our callback is called, after that five second duration, we make Guy go to sleep. It's quite simple and the performance improvements using event loop timers are considerable since you're not sitting on one entry point polling the hardware for no actions essentially. David? Thanks, Curt.
So that's a good example of how you can gain performance by not polling continuously for null events. And if you're not going to do this, I encourage you to buy one of those leather laptop protectors so you don't burn your legs. That's a good point, Curt. Next I'd like to talk about mouse tracking regions. This is another area where you can gain some performance.
What do I mean by mouse tracking regions? What I mean is generally people want to track the mouse around in a window or such in an application. and it will behave differently depending on where it's pointing in a window. You might want to change the cursor or display status for different pieces or even change the behavior of the application entirely depending on where the mouse is pointing.
The way you could do this with OS X when we shipped it was to install a K event mouse moved handler. So you'd watch where the mouse went every time and you'd get an event every single time wherever the mouse was. You might not have cared where the mouse was unless it was pretty close to the important place for you. So there's too many mouse moved events, and it's kind of expensive to process the ones that you don't really care about.
A new feature on Jaguar are mouse tracking regions, and I encourage you to use those instead. What you can do is register different hot regions in your window. Then, you get callbacks that track entry and exit of the regions. What that means is you get a Carbon event that says, you get one Carbon event that says, the mouse just entered your region. You get one Carbon event that means the mouse just exited your region.
So now you can ignore the mouse mood events, and by extension, if nobody else is worried about mouse mood events, the toolbox underneath you can stop worrying about mouse mood events. This has got a nice side effect in that we don't have to call back to the Windows server, and there's some additional underlying performance gain for you as well. Another good side effect is this can work on inactive windows in the background. We have a demo. Maybe Curt will show that in the demo.
So there's a huge performance gain because you're not processing so many mouse mood events. When could you use them? Like I said, you might want to change the cursor as you move it over different regions. You know, where you have a pencil when you're on a doodle area or something like that.
When the mouse leaves the window, maybe with some widget rollover to change an image or a title. You might notice in the document window as you move the mouse over the control buttons up in the corner, the close box, the collapse box, and the zoom box, that they change image.
We use mouse tracking regions for that. It was a hidden API that we were testing before Jaguar, and now we're exposing it for you to use. And again, status tracking. A good example of that would be in Internet Explorer when you hold the mouse over a link. It changes the status on the bottom, tells you where you're about to click to.
So how do you use mouse tracking regions? It's very simple. At the top, I declare a mouse tracking region ID, and I put in my own signature and my own region ID number. You want to use those because it uniquely identifies your region. You don't want to do things or respond to other regions.
Afterwards, you use the Create Mouse Tracking Region API, passing in that region ID that you created above. Now, there's some extra information like which window it was in, what the region you're worried about is. You can have complex regions rather than just rectangles. What the clip is and some options.
But the important one is your region ID because you're going to use that later to make sure you're tracking the right region. Afterwards, you just install a Carbon event handler and you register for some events. And you continue on and your target will be notified so you'll get the Carbon events. The events you get are K event mouse entered and K event mouse exited. One event on each action. So you're not getting a whole bunch of events, you're just getting them one at a time.
Some other important mouse tracking APIs. Release Mouse Tracking Region. If you're not tracking an area anymore, you can disable it, but it's better to deallocate it entirely and free up resources. You want to be able to get mouse tracking region ID so you can check to see that the region you're acting on is the one that's important to you.
There's a couple of APIs to change and move the mouse tracking region. And an important one is to set mouse tracking region enabled with a true or false Boolean to indicate whether or not you actually want to track. So you can temporarily turn them off or temporarily turn them on. It's very useful.
So I mentioned the region ID and being important to make sure you're acting on the right region. When you get the mouse entered or mouse exited events, As a parameter, you get the mouse tracking region reference to indicate which mouse tracking region is being acted upon. And what you can do is extract the region ID from that track graph and compare it to your signatures to make sure, your signature and your ID, to make sure that's the one that you care about.
Now you really might want to do KMouseMoved event handling. Say you're moving the mouse around and you want to make a graphic underneath and know every point. But you don't need it for the whole window, you just need it for one area. What I would recommend in that case is to use mouse tracking regions and then when the mouse enters your region, install the mouse moved handlers and start paying attention to the mouse moved events. And if the mouse moves out of your tracking region, then uninstall those handlers. And only track the mouse moved events for that one small area that you care about and not for the whole window.
So I'd like to ask Curt to come back up again and give another demo, this time on mouse tracking regions. Curt? Thanks, David. So this is just to demonstrate--well, traditionally, you would use WaitNext Event, like the mouse region, to figure out where the mouse is--if the mouse has exited a region. But the limitation with using WaitNext Event is that you can only have one region per application, and then there's some overhead on your part to determine, okay, once I've exited, I need to reset that region.
Well, using Carbon Events, you could have installed a mouse move handler, and David just went through all the performance implications of that. And I'd just like to demonstrate that as well. So here again we have Guy, who's our demo guy. And you can see that as I move the mouse into the window, we're getting mouse moved events. In fact, it might be interesting to look at the CPU monitor while we do this.
[Transcript missing]
"Even when we're in this region, we continue to get events. Exit, we can say, 'Hey, how you doing?'" You can see we're just accumulating events. We're not even interested in them. We're only interested in it when the mouse is over a particular region. Using Mass Tracking Regions, that's what we get. You can see I'm moving in the window, nothing's happening. We're not getting any events, there's nothing to process.
The only time we get an event is when we have entered a region or exited a region. You can see, two as opposed to 1100 is a pretty big savings. Again, let's go look at the code and see how that was done. Did you note that 6, just a 6? That's a pretty low number. That's a pretty low number.
So again, we just have a region ID, which we populated with the tracking box signature. And in this particular case, we've associated that with the control, so we're just going to use the same control ID that we've used. Then we go ahead and we create the mouse tracking region.
Specifying the window that that region's gonna be bound to. The Control Region that we're interested in. We can even clip that complex region if we want to, but we don't want to in this particular case. The Options Standard, that means that it's going to be a local region to that window. The Region ID, some of our own window data, so when we get the event, we can extract some information. Initially, in this window, we've disabled them. Well then later on we go ahead and enable them when I selected that item from the menu.
We go ahead and we install for these events. This is the K event class mouse, K event mouse entered, and K event mouse exited events. And when we get those events, we just tickle the event counter, which you saw on the bottom, and then we go ahead and update the highlight of that image.
And of course, when we're done, we release the mouse tracking region. So you can see that it was very simple to install a region, and we get some considerable performance gains by not listening to each mouse moved event. That really is just a lot when you're not interested in anything except for this. David? Thanks, Curt. Thank you.
So hopefully the choice between processing hundreds and hundreds of events versus six is an easy one for you to make. Mousetracking regions are also very convenient and easy to use. Next, I'd like to talk about Carbon events, but from a different point of view, viewing them as notifications.
It's sort of an overlooked feature of Carbon events. Toolbox actually sends many, many of these. If you look through carbonevents.h, there's a really good source of information for determining what kind of Carbon events are available to you. They're just Carbon events that you can easily watch for these notifications.
And there's not really a big how-to session I can give on how to use them. Just register for the Carbon event, and you'll just get the one event that is interesting to you as a notification. Some examples are when your window becomes hidden, when your application becomes hidden, Warning that your menu is about to populate. These are really good notifications and you can use them to your benefit to increase your performance. And, instead of checking continuously to see if something has happened, getting a notification just once is a lot better for you. Here's a quick example.
I've declared an event list of a couple of events that I'm interested in. In this case, the volume mounted and the volume unmounted. You don't have to--in this case, we want to not care about the volume list and check this continuously to see if it's changed. We just want to install an application event handler to receive the K event volume mounted or the K event volume unmounted events, which will notify us when the volume has been mounted or unmounted.
It's a lot better than polling, wouldn't you say? That's really all I have to say about the notifications, but you should really check carbonevents.h. Next, tracking loops. What I mean by tracking loops is keeping track of the mouse while you do things, whether the mouse button is down or the key modifiers have changed.
Traditionally, the way to do this was to call still down or button in tight loops. It's very, very expensive on OS X. They hoard the CPU entirely. Check out that CPU meter over there. It's pegged. What you want to do instead is use a tracking loop. You want to call track mouse location or track mouse region.
You're not calling it a tight loop. It blocks entirely and will only return when something important has happened. Nice part about that is you won't get a rainbow cursor. Now, a lot of people view the rainbow cursor as a bad thing, but it's actually a feature of the OS that indicates to the user that an application has become unresponsive.
And the way... Getting a rainbow cursor and working around it is not really the solution. What you want to do is solve the problem that is alerting you too. Nice side effect, when you call track mouse location and it blocks entirely, it's calling the event loop and timers fire. But the best part is that it frees the CPU entirely. Notice it's a lot different here with the mouse button down.
How do you use Track Mouse Location? It's very simple. You just call the API with a couple of parameters, one being the tracking result. You can call it in a loop because it's not going to come back to you many, many times. It'll only come back to you when interesting things happen, like whether the mouse is down or whether the mouse is up. That's all you have to do. and Curt's going to come up and give one more demo showing the difference in performance using trackmost location.
Thanks, David. You're welcome. So in this particular case, what I've done is I've written a new drawing application. And so it's very important for me to get the location of the mouse and actually track that mouse and see what's going on with the user. So initially, I went ahead and I implemented that using while still down git mouse, which I found was extremely inefficient. And I just wanted to show you the difference in performance.
This is my groovy new drawing application. It's very, very cool. So let's use-- I'm going to use-- WaitNextEvent in this particular case, and I'm just going to-- The demonstration is definitely just to show you while still down GitMouse. I'm going to click the mouse button, and we're going to see that the CPU is completely pegged. I'm not doing anything. Here, I go ahead and I draw.
While doing this, the CPU is pegged. Now let's say I implement some groovy new feature in here. Where this application can actually look at what I'm drawing and do some image processing while I'm going on. So I'm going to want to show the user that something's happening, so his head's going to rotate, similar to my first demo. So see, I'm going to do something and he's going to start moving around. Well, the second I click the mouse down, he stops. We're in this really tight loop, and we're not letting any other events happen in this particular case. Pegging the CPU.
Okay, let's go ahead and use Track Mouse Location. I'm gonna click the mouse down, and we see that nothing's happening. I go ahead and I can draw. Nothing is happening with this CPU. We're not consuming resources. It's taken away from other applications that are currently running on our system.
And as David was mentioning, it's pretty nice because timers will continue to fire. You can still see that the animation is continuing even while I draw. So that provides a better user experience to the user. It's allowing it to get into the event loop, and it's blocked when I'm not doing anything extra. So let's go take a look at the code for that as well.
So initially, you can see that I was just calling this simple loop while still down, getMouse. And I do a few calculations to see if the mouse has moved, and then I actually draw a line if that's happened. And this does not do anything other than pull the mouse and see what's going on.
And as I mentioned in session 203, what you're doing here while sitting in this tight loop, it consumes resources initially, yes, but when we call getMouse, that requires interprocess communication with the Windows Server too, which can be expensive. It's just not necessary to sit in a tight loop if the user's not doing anything.
In contrast, we can still sit in a loop in this particular case by calling-- while sitting in a while loop, but instead use track mouse location. In that particular call, it will block in the CPU. There's nothing going on unless the mouse is moved. If the mouse state has changed because it's moved or the mouse button has been released.
And then we'll go ahead and we'll do the same thing until-- as the mouse is dragged and we'll exit when the mouse says comma. So you can see that that's really important. It was such a small change that we needed to make, and we were able to dramatically decrease our CPU usage. David? Thanks, Curt.
So if anything from these three demos, you can see that you probably want to keep the CPU meter up and keep an eye on to see how your application is using the resources that are available to it. If it's using all of them and you're not doing something really, really crazily cool, then you probably want to go visit a few pieces of your code. And you can see that it's very simple to replace it with a timer or a track mouse region or track mouse location, or mouse tracking regions or track mouse location.
Next, I'd like to convince you guys to stop calling find window and tight loops. Historically, people have called find window and find control and those associated calls. When you got an event to try and figure out where events occurred, you had to go back to the start button and Unfortunately, FindWindow has to call the Windows server. There's a bit of a performance hit with that because there's inter-process communication.
And really, all the information you need is in your Carbon event. So you really want to avoid calling FindWinnow because it's not really a necessary call. For example, the mouse down Carbon event. That's probably a pretty common one that most of us would register and want to see.
Once we receive that event, we're probably concerned which window this was occurring in. And it's right there in the kEventParamDirect object. Or where the mouse location is. You can see it there. Or even what the keyboard modifiers are. And if there was a control in the window that was being clicked, that'll show up as a control ref in the parameter list. So you don't need to call anything other than the extractors for the Carbon event parameters.
Here's another example. This is a Carbon event that you might use if you're writing a custom control of some sort. When you're writing that, you're probably interested in what happens when the mouse gets clicked and when you're going to track. You're interested in which control ref, what the control ref is. You're again interested in the mouse location, the keyboard modifiers. You might even be interested in which control part was clicked.
And it's all right there in the Carbon event. Event avail is another call that can be inefficient when you call it in tight loops. Historically, developers have used it to figure out what the state
[Transcript missing]
An Event Avail runs the event loop and fires the timers. It can also flush the windows. and Fire the Timers. So you really want to avoid calling EventAvail if you're just trying to figure out a few different little parameters. And I'll show you some other ways that you can grab that information.
You can check the loop with some APIs that we've introduced, get GlobalMouse, and get current key modifiers. Those have been there for a while. They don't check the event queue. However, they do call the Windows server. So this is a little bit better solution. Much better than using EventAvail by any means. Even better than that, and new in Jaguar, there's two APIs, GetCurrentEventButtonState and GetCurrentEventKeyboardModifiers.
What happens here is, as we pop an event off the queue, we keep track of that information so that you can access it quickly. And we just restore that information. and we can send it back to you without pulling the Windows server so there's no performance costs. Or very low, anyway.
Another time when you might be checking the event queue in that fashion is to determine if the user's canceled. You're looking for a command period or a click on a cancel button. We have a conveniently named API, Check Event Queue for User Cancel. Very descriptive, imaginative, like Guy says. That's the kind of APIs you need. It doesn't call the event loop, and the toolbox can check this faster than using those other calls. And if that's all you're interested in, then Check Event Queue for User Counsel is the API for you.
The ultimate thing to do though, like I mentioned earlier, is to check the Carbon event parameter list for the Carbon event that you have. Because the information might already be there. The Carbon event often includes modifiers. It often includes the mouse location. Back to my control track Carbon event parameter list. You can see that they're right there. And that's probably the information you're going to be looking for.
So those are really the key areas I wanted to cover as far as Carbon event performance. And you've seen over the course of the day many new Carbon events have been mentioned. So there's a whole bunch of other new Carbon events. and in case anybody's frantically scribbling these down, you might want to grab those ones too. I could keep doing this a few more times because there's been a lot of new events.
This is the way we're going forward. We're always going to be adding to the Carbon events and adding to the Carbon event parameters that are available. What you want to do is keep in tune with Carbonevents.h. It's definitely the best documented area in all of the high-level toolbox.
Whenever we add an event, we document it fully. Whenever we add a parameter, we document it fully. And if you do find that particular documentation lacking, be sure to give us that feedback because this area is very important to us. So I'd like to go on to some additional performance tips, as I mentioned in the introduction.
These are things that have come up based on questions from the Carbon Dev mailing list and other sources where there's some niggling performance problems where people are having trouble. So we've just compiled a few tips that we think are helpful, sort of the big bang for a small effort. I have some in the Control Manager, just a couple, some in the Menu Manager, and I have three quick drawing tips that might be helpful to you.
First, the control manager. The controls we draw are fairly complex. Drawing Aqua is not always easy, so redrawing can be a little slow. We make them perform as well as we can, but when you modify them, It can force a redraw. Really a good example of this is if you add many items to a pop-up button's menu. You do that over and over. It does some calculations based on the size of the menu items that you're adding, and it might force a redraw of that button.
Redrawing that button over and over can definitely be a performance hit. What you want to do under these types of circumstances is use the API set control visibility. What you can do with this API is temporarily hide a control before you start changing it. You can change it many times. And then once you're done, use set control visibility again and show it afterwards. So it will only redraw once at most.
So here's an example of that. How to make control changes a little more speedy. You can see the first call at the top, set control visibility. I tell the control not to draw. In this particular example, it's back to that pop-up control again. I'm adding a bunch of menu items.
So I have a loop of, say, I don't know, too many menu items going into a pop-up control. I do that. When I'm finished, I call set control visibility again. with True to tell it to draw again. And at most, I'll only redraw that control once. It's a lot better than drawing it menu items times.
That's what I have for the Control Manager. Next, the Menu Manager. One of the key areas with the Menu Manager where you can gain some performance is try to avoid invalidating the menu key cache. Because it forces a rebuild of the whole thing, and that can be expensive.
What is the menu key cache? The menu key cache is an internal set of data that we keep so that we can determine how to map keyboard shortcuts onto menu items. We have to keep that up to date, so if any keyboard input comes in, we know to route it to you through the menus and perhaps as a command ID.
So what are the things that invalidate the menu key cache? Will you change a menu item's modifiers or the shortcut key? If you insert or delete menus, if you add new menus that have command keys on them, definitely will do that. If you clear the menu bar or set a whole new one, it has to go through the whole menu bar.
Or if you change any of the submenus, it invalidates it because it has to become aware of those new submenus. So how can you avoid this? Don't invalidate it. And that's easy to say, but how do you catch that? How do you figure out what's invalidating your menu key cache? Here's what I would do. I'd run my application using GDB and attach to it, or run it from the command line. and David Levy will be here to talk about the new features of Carbon Events.
Now, if you're doing this before Jaguar, there's no underscore before invalidate menu key cache, something we added in Jaguar. But that is the API you want to break on. Now, I know there's a couple of Code Warrior developers here, so I'll go through the GDB session a little bit.
Like I said, you attach or you run from the command line with GDB and you break on underscore and validate menu key cache. And you continue on. And you use your application until you hit that break point. Once you hit that break point, that's our internal notification API and David L. L. L. L. Just who the culprit was. In this case, someone's changing the item command of a menu item.
So you can use GDB to try and eliminate those. Find points and maybe find out why you were doing that and see if you can avoid doing it at that point. Next with the menu manager, using the K event update command status event, When you receive that, you want to use enable items wisely. When you get this event, it doesn't necessarily mean, oh, geez, the whole menu's gone bad and you need to update everything and tell us what the state of everything is.
There's parameters that come along with that event to indicate what the requested items are. It's only really concerned with a finite set of items, maybe not the whole thing. And if you were to do the whole thing every time, that's a performance hit for you. So you want to listen to the K event menu context parameter. See what it says. It's well documented in carbon-events.h. And you really want to only update the items that it cares about.
Lastly with the Menu Manager, use set menu item data instead of making multiple calls to other set menu foo APIs. The cost here is that when you call any of the menu setter APIs, we have to map the menu ref and the menu item index onto some internal data structures within the menu manager, and that can be expensive. It's something we need to do, but we shouldn't do it more often than we need to.
So here's an example of a little bit slower way of doing it. I'm calling a set item foo API, or set menu item foo API, three times. And you'll notice each of these APIs has a menu and an item parameter. So every time there's a menu and an item parameter, we have to map them, this is a menu ref and a menu item index, onto our internal data structures. So we're doing it three times there.
[Transcript missing]
We set the command key field, the key modifiers field, and the command ID fields All at once in the record. And then you see at the bottom, set menu item data. We just call it API once. We're only going to map the menu ref and the menu item index onto the internal data structures once.
So aside from initializing the record, which is pretty fast, this code's going to be three times as fast as the other one. And who knows how many times you're calling those other -- you might be setting every possible thing you can for the menu item. You'd have to map it every time. This way, you do it all in one go. It's the best way to do it.
On to Quick Draw. Tip 1: Quick Draw Flush Port Buffer. Some people here might have familiarized themselves with this. This API flushes the Quick Draw Port Buffer and people might use it to try and get their
[Transcript missing]
And you definitely don't want to call it more often than the screen's actually going to update. You'll be updating the buffers faster than the system is updating the buffers, and this is all a waste of time.
and you definitely don't want to call it unnecessarily. In fact, you might not have to call it at all because it might get flushed automatically and that type of flushing might be good enough for you. Quick Draw Tip #2: Complex Quick Draw Drawing. You might be drawing something with Quick Draw and drawing thousands and thousands of lines. I think I saw someone with an example like that on the Carbon Development List recently.
What you want to do here, and what the performance problem is, is that every time Quick Draw draws, it's calculating its dirty region. So when you say you're drawing a bunch of lines, every time it's checking to see if the region of that line is intersecting with the dirty region. What you can do is just dirty the region as a big rectangle of where you're going to draw before you start to draw. By using the API qd add_rect to dirty_region.
What this does, when you actually do your drawing, say you're drawing a line, the region calculation to see if that line intersects the rectangle that you've already set as the dirty region is a very simple calculation and can happen very fast, rather than comparing two complex regions. Make sure it covers your drawing area, and then the dirty region calculations become simple, and this will definitely improve the speed of your code. One caveat, though, is that you don't want to ignore the existing dirty region. Make sure you use this API, qdadrect to dirty region. There's another one that ends in set dirty region that ignores the existing dirty region, so anything that was dirty won't get flushed properly.
And my last quick draw tip. Again, before you do a large number of drawing operations, consider using locked port bits on your port, but only if your drawing time is very short, less than a second or so. If you leave that port lock too long, the behaviors become a little bit unpredictable and you might have some redraw issues. And definitely if you use that call, don't forget to unlock the port bits when you're finished.
Because your drawing will definitely have problems if you don't do that. I'm far from being a QuickDraw expert. I'm not on the QuickDraw team. So I would recommend if you're interested in QuickDraw performance to go to session 516 on Friday, QuickDraw Performance. There's a QuickDraw engineer there that will give an extended set of performance tips for QuickDraw.
As far as performance documentation goes, I would really recommend that you read the OS X performance documentation. It has some great tips in there for how to isolate and identify which areas of your applications are causing you performance problems. I'd also recommend rereading the Handling Carbon Events and Carbon Events Manager Reference documentation.
We've said many things today in reference to Carbon events. Some of it might seem new to you, but some of it has been in there for quite a while. And you definitely want to keep fresh on there and understand what's in there. I would even recommend reading carbonevents.h every time there's a release, because it's definitely going to become fuller and have more events, better documentation in the future.
So in summary, use timers instead of continuous pooling tracking. Use mouse tracking regions instead of mouse-moved event handlers. Pay attention to the notifications that are available to you as Carbon events. And use tracking loops with track mouse location or track mouse region instead of still down or button down tight loops. And please stop calling find window and event avail in tight loops.
A Roadmap. Unfortunately, or fortunately, maybe you've been to all of these, there's been a bunch of great sessions at WWDC. There's Ed's sessions this morning, the Architectural Overview and Introducing HIV, Guy's session, and there was also Curt's session yesterday, Migrating to Carbon Events. I recommend that you watch all of those on the DVDs. That's it for me. We'll have Zavia come up.