|
|
Structure of the XP Event Loop Code
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 Components
Each 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 Hierarchy
In 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
implementation).
This class is the base of all the event loops. It's jobs include:
-
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.
This class subclasses from nsCBaseLoop. It is found in the parent
hierarchy of all event loops. This class allows platform specific
implementation to interject itself before the the classes branch into the
various event loop types. Native code that is common across all types
of events for a given platform should go here. It's jobs include:
-
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.
This class subclasses from nsCPlatformBaseLoop. It is found in the
parent hierarchy of all event loops. This class provides the code
that is different for each or event loop type, but is the same for all
platforms. It's jobs include:
-
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.
This class subclasses from the appropriate nsCBase*Loop (nsCBaseAppLoop
for nsCAppLoop etc). This is the final class in the class hierarchy
and is what is instantiated as the implementor of the event loop component
for the requested type. This code is nearly all platform specific.
Note that the name of the class does not have a platform in it. This
is done intentionally to put the burden on which class is instantiated
on the build system rather than ifdefs in the code. It's jobs include:
-
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.
Other Components
There are a couple of other components that are used to make up the complete
XP Event Loop. These components are actually instantiated and created
by the event loop code. Both are completely implemented in platform
code, but provide XP interfaces.
This class implements the nsIEvent interface. The object that is
instantiated and passed around is the basic event for the platform.
It is passed around as platform independent entity via it's nsIEvent interface.
A given platform however can quickly get to it's platform data by calling
the appropriate Get or SetNativeData() functions. In fact the core
job of nsCEvent is to wrap up this even data and provide access to it via
the accessor functions. The data passed through these functions are
a void* which means both sides have to know what the structure of the data
is. Some platforms may be able to re-use structures defined by their
OS, or they may need to define another known structure which will be the
native data.
This class implements the nsCEventFilter interface. This object serves
as the wrapper for the platform event retrieval filter data. Much
like nsCEvent this class simply provides Get and SetNativeData() functions
to allow code to retrieve the internal filter data. Also like nsCEvent,
the data passed through is void*. This means both sides have to know
what the structure of the data is. Some platforms may be able to
re-use structures defined by their OS, or they may need to define another
known structure which will be the native data.
|