Posted: Tue Nov 18, 2003 12:14 am Post subject: VCL event dispatching
I noticed that NeoOffice is using a really simple implementation in SalInstance::Yield(): wait a few milliseconds, check for a pending event, dispatch event, check timer, and run timer.
NeoJ used this same approach until the latest 0.7 patch. However, I found that this approach can cause some crashes in some very weird places and it still uses a lot of CPU (Jim Laurent reported 10% CPU usage of an old iMac).
After walking through the X11 code over and over (this was not very fun), I discovered that the X11 VCL event dispatching uses the steps in each pass through SalInstace::Yield() that can profoundly affect the CPU usage and behavior of OOo:
1. If there is a non-native event pending (i.e. an event posted by VCL via SalFrame::PostEvent(), dispatch it, release all of the SalInstance locks, yield to other threads, reacquire all of the SalInstance locks, and return. Don't run the timer!
2. If there are no non-native events pending, release all of the SalInstance locks, yield to other threads, reacquire all of the SalInstance locks, check the timer, and if the timer has expired, run the timer callback.
3. If the bWait parameter passed to SalInstance::Yield() is TRUE, calculate the time until the timer expires again and wait that long for the next native event (i.e. an event from the native CFRunLoop). This waiting is where you can really reduce CPU usage. If there is a native event, dispatch it. Continue dispatching any pending native events in a loop but only block waiting for the first one. When there are no more pending native events, return.
This probably sounds confusing so you may want to copy and edit the NeoJ SalInstance::Yield() method in the neojava/vcl/java/source/app/salinst.cxx file. Just note that "AWT events" means "CFRunLoop" events and "non-AWT" events mean events posted via SalFrame::PostEvent().
In essence, VCL tends to dispatch events by type, not by their order in the queue. In other words, it dispatches all pending non-native events first and then it dispatches all pending native events.
It is too bad that this took me nearly a year to decipher.
Since the X11 SalInstance::Yield() code did not change between 1.0.3 and 1.1, the NeoJ code should still be useful.
I just got done checking in my Cocoa run loop code. The event implementation has changed Can you take a look at it again and see if you think of anything?
I think your use of native timers that post "timer expired" events to the event queue is good. However, your code still mimics the old NeoOffice and NeoJ code in that it dispatches events in the order that they are posted.
In other words, you are intermixing dispatching of native events and VCL generated events. Although this would seem to be OK, I have found OOo to be extremely sensitive to the dispatch order of native, VCL generated, and timer events. Furthermore, the ordering requirement is different depending on whether the "bWait" parameter passed to SalInstance::Yield() is TRUE or FALSE.
VCL really expects SalInstance::Yield() to do the dispatching and many VCL generated and timer events will actually recursively invoke SalInstance::Yield(). Hence, the ordering requirement.
Here's some thoughts on how to normalize the NeoOffice event implementation, right before I go upstairs and code it
I decided that, in a Cocoa model, OpenOffice.org has no business distributing events. That's the job of Cocoa's event loop (CoreFoundation really), and that's why we'd use [NSApp run] in the first place, since it does it better and in a more integrated manner than OOo can ever do. Talking to Apple engineers at WWDC, there's a lot of stuff the Cocoa event loop does that we'd be hard-pressed to duplicate.
Therefore, since OOo code should never do event distribution, we have to plug those holes. There was one place that NeoOffice did do a lot of event related stuff, and that was the Application:: methods Application::Execute, Application::Yield, and SalInstance::Yield. Looking at the code flow, Execute() is called when an application simply wants to execute until a certain condition. Namely, until the mbAppQuit or whatever it is is set. That works great for the four major applications, but others don't use Execute(). A lot use Application::Yield to do event processing, but in the same way as Application::Execute(), just with a different condition to break out. Now, this code might be a simple, short loop where events need to be distributed, or the program might be actually attempting to duplicate the functionality of Application::Execute.
So, we are left with 3 cases where event dispatching happens:
1) Application::Execute has been called, does event processing
2) Application::Yield is called repeatedly in a while() loop, to do event processing, but no Execute has been called
3) Application::Execute was called, but somehow we end up _back_ in Application::Yield, but not directly from Execute.
Solutions:
1) Since we call [NSApp run], that takes care of all event dispatching in [VCLApplication sendEvent], and we are fine
2) We are not in the main loop as specified in Execute, but need to process events. So, since we haven't already called [NSApp run], we'll do it again here, but only if !mainLoop. However, to break out we'll need to have some sort of callback to find out when the conditions are true that the routine that called us uses to break out of the while(blah) Applicatin::Yield(). Then we can call [NSApp stop] where appropriate to break the event loop.
3) The black sheep case... Since we don't want to re-enter [NSApp run], and there seems to be no way of simply allowing [run] to just run (remember, we are down the call stack from run, since this routine was called as a result of an event run dispatched). However, here we can start a modal event processing session, which I believe uses [VCLApplication sendEvent] to do its processing. Since I consider it an error that a program wants to do event processing outside of and in addition to what is supposed to be its main loop in Execute (Win32 does not allow this on a separate thread, but dispatches events back to the main thread), we'll just call [runModalForWindow] with the front window and run a modal event loop, but with a callback as with #2.
I agree with your assertion of the order of events, and I'll add a section to dispatch all VCL events before any native events run, at the top of [VCLApplication sendEvent]. Once all VCL events are dispatched, it will dispatch the one native event that triggered the sendEvent anyway. We may have to implement some timer to ensure that, when no input occurs, VCL events do get dispatched anyway (send an NSApplicationDefined periodically or something just to trigger sendEvent), but that's trivial compared to tracing the code flow of OOo
It sounds like you have approached this from a different angle than I did. I never did figure out the mapping between Application::Execute() and Application::Yield(). I only discovered (by chance) the differences in behavior depending on whether the bWait parameter to SalInstance::Yield() is TRUE or FALSE.
I think your approach to dispatch all SALEVENT_USER events, then timer events before dispatching a the current native event makes sense. I assume that you will have SalFrame::PostEvent() and the timer events post to a private queue instead of the native queue or is there a way to specify priorities for events in Cocoa?
The tricky thing that you would still need to deal with is this: when SalInstance::Yield() is inoked with FALSE, you are not supposed to dispatch any native events. Instead, you dispatch the SALEVENT_USER events, then timer events, and that's it.
This behavior took me forever to find. If you are dispatching native events in this case, OOo has a tendency to crash.
Another tricky thing is that when you do dispatch a native event, you need to dispatch all pending native events as well before you dispatch any SALEVENT_USER events or run any expired timers.
Application::Execute() is called from places like desktop/source/app/app.cxx (?) for example, right after the splash screen is done. That is the entry point to start all normal applications. The setup program has a slightly different path.
Application::Execute() calls Application::Yield() directly, in a while loop until the global quit variable (a member of maAppData) is set to TRUE I believe.
Other places, like Dialog::Execute() call Application::Yield() with different sets of conditions to break out of the while loop calling Yield(). There may be cases where Yield() is called by itself, when Application::Execute() has never been called (setup program is one example I think). Hence my 3 cases from my post above.
I'll try to take the native/VCL event dispatching into account. One thing I notice is that I may have to have a private queue of non-native VCL events lying around to dispatch at the top of [VCLApplication sendEvent]. I can't anywhere right now where that queue is kept globally, and I believe the X11 code uses its own private queue to do it anyway.
With the bWait, that is set as such by Application::Execute() -
Code:
while ( !pSVData->maAppData.mbAppQuit )
Application::Yield();
so when we are not quitting, bWait is TRUE, but once a Quit has been signalled we set it to FALSE. I assume this is to dispatch all pending VCL events before control drops back to the native code to clean up and quit the application. When we are running the loop and not quitting, bWait would be TRUE and we would proceed as normal, dispatching both VCL and native events. I believe that I can handle this correctly in [VCLApplication sendEvent].
so when we are not quitting, bWait is TRUE, but once a Quit has been signalled we set it to FALSE.
This statement is true most of the time. However, there are some circumstances (during a print job is one that I know of) where FALSE is invoked. I assume SalInstance::Yield(FALSE) is being called from some other place (maybe within dispatching of a SALEVENT_USER event?). It is this situation that caused weird behavior for me.
Here's what I've done. I've implemented a queue-type object in the SalData structure:
Code:
CFMutableArrayRef mpVCLEventQueue;
This is simply a FIFO queue of events, which get pushed onto the back of the queue in SalFrame::PostEvent(), which also posts an NSEvent of type NSApplicationDefined to make sure that [VCLApplication sendEvent] is called at some point. Then, in [VCLApplication sendEvent] we process all events off the queue before processing native events. I'll have to create some mechanism to duplication the bWait functionality, but that's not hard. Probably should also lock the dispatching from the mpVCLEventQueue in sendEvent, like theX11 stuff does for its event queue. Later
One other thing that I'll probably have to change is the VCLWindow object posting events directly to the SalFrame callback proc (ImplWindowCallback() or whatever in source/window/winproc.cxx) for paint events and such. Not sure how to handle these yet, because we need the window to paint when we want it to paint, but that might break stuff in the VCL when it expects to be receiving an event for SAL_PAINT from the normal mechanism and not short-circuited.
Also did a fairly invasive cleanup of #includes to try to cut down on redundancy.
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum You cannot attach files in this forum You cannot download files in this forum