Structure of the XP Event Loop Code
Last Modified: 9-2-99
This page is devoted to providing information about how the XP Event Loop is implemented. This does not cover the design of the XP Event Loop, but rather the actually layout of the C++ used to implement the design. If you haven't already done so and wish to learn more about the design, read the collective documents about the background, types of loops and sets of interfaces found on the main XP Event Loop page. Hopefully this will serve as an aid to anyone needing to port the event loop, but it should also be informative for anyone curious in how it is setup.
The layout of the code was governed by a few key principles:
- Performance - The first and foremost principle was to make the event loop as efficient as possible while retaining the ability to be cross platform. Clearly the best performance would come from writing all native code, however this is difficult to maintain and thus a slight (very, very, very, slight performance hit is ok for the sake of reusable maintainable code). Beyond this, however all effort was made to make the code as efficient as possible while staying XP.
- High Percentage of XP code - Though much of the implementation and actual APIs to implement the event loop are native, there is a large percentage of code that is redundant across the platforms. In these cases all effort was made to move common logic and program flow into XP locations in order to minimize the amount of non-xp code.
- Minimize Cut and Paste - Not only are there many different platforms that may share similar code, but there are also three types of event loops. The basic nature of these is very similar in some core respects, yet differ in others. This often leads to cutting and pasting the code across the variations and then many problems later as bugs are found in each implementation. Since the actual implementation lives in platform code, the number of places to keep in sync would have been 3 x number of platforms. So one of the main goals was to consolidate code that would have been cut and paste and instead share it in some way. Some of this is obviously the sharing that occurs by getting a high percentage of XP code as the previous goal stated. The other part aims to get sharing on a single platform across the three variations of loops.
- Minimize dependencies - It was also important to minimize the number of linking dependencies. Since the interaction with the event loop on many platforms varies depending on the UI toolkit chosen, in order to work with all toolkits we would have to link with each one. We really needed a way to for any given module to link with only one toolkit at a time. Yet in doing this we did not want to give up all the other goals expressed above. Especially the cut and paste goals.
- Portable - Last but not least, this code needed to be easily ported to other platforms. This means two things. First the fact that the code is XP should not force a given platform to give up access to a loop it would normally have. Plainly said, an app using the XP Event Loop should have the same control over the native event loop that they normally would when not using the native platform API calls. Secondly, the actual effort to port the code should be straight forward and without many unknown gotchas requiring large amounts of work to track down. This documentation as well as porting docs is hopefully a step to make this process easier, but the code layout was also designed with this purpose in mind.
The ComponentsEach type of event loop can be found implemented in a component. As there are three types of event loops, there are three different components that implement one loop type each. By putting each one in a component it enables us to then put each component into it's own module. (In our case all three components live in one module). But we can then have multiple implementations of the same component. This allows us to make a runtime decision about which event loop implementation we will used based on which UI Toolkit is chosen.
The Class HierarchyIn order to get maximum re-use of code and get a large percentage of the code to be XP, I have broken up the implementation into 4 levels of a class hierarchy. The code is actually structured in such a way where two of those levels live in the xp code side and two live in the platform side. The platform code is actually named in such a way where it is a build time issue as far as which implementation of a given platform file gets pulled in. This allows us to get away from having any ifdefs in the code.
The levels can actually be grouped into two groups, the base event loop
group (nsCBaseLoop and nsCPlatformBaseLoop) which provides the basic implementation
needed for implementing an event loop component. There is one xp and one
platform class for this level. Then there is the class group level
that handles implementing the specific types of event loops (nsCBaseAppLoop
and nsCAppLoop). In this level there again is one platform class
and one xp class. Below is the list of classes that make up the 4
levels of the class hierarchy. (All platform links point to windows
- XPCOM Tasks - This level handles all the logic needed to make an event loop and XPCOM component.
- Interface Error Checking - Provides all error checking on interface boundaries for nsIEventLoop. This level handles all the interface boundaries. Those that it can not implement the method in place because the implementation is either different per platform or per event loop, passes off the call to a pure virtual function that must be implemented by one of the sub-classes. These sub-classes therefore have the luxury and the responsibility to not do error checking. This is important to keep performance as near to a native event loop as possible.
- Implement Run() - The Run() implementation is setup to be very stream-lined based on the listeners being requested. This level handles creating the basic event structures needed and handing off the running to pure virtual functions that are implemented later by loops of a specific type.
- Event and EventFilter Management - This level handles creating and cloning events. It also handles extracting the platform data from these objects when it is requested.
- Avoid Error Checking - All virtual overrides here should not do error checking. Any over-rides on the real interface boundaries however should. No one should be over-riding the interface boundaries though.
- Implement Shared Platform Methods - Any method that can be implemented the same for all types of event loops for the given platform should be implemented here to avoid copy and pasting after the loop type branch in classes.
- Implement the various RunWith*() functions - nsCBaseLoop calls on a number of RunWith*() functions to implement the run in different ways based on which listeners are requested. This is done to avoid repetitive checks that would slow performance. Beyond this, these Run loops vary depending on event loop type so they must live at this level instead of in nsCBaseLoop. These are implemented by calling on a number of platform virtuals so this code is able to be XP as the virtual functions are overridden by the platform subclass.
- Implement remaining pure virtual functions - Any methods that could not be implemented at a higher level must be implemented at this level. One can also override implementations off higher classes if only one event loop type varies in a slight way.
- Implement the Class Factory "Create" function - The code uses the Generic Class factory to handle creation of the event loop components. Since this is the level that knows the class name for creation, this level handles the creation.