C H A P T E R  10

Porting the Event Processing Service

The event processing service enables a device's native code to convert native events to a format that the Java platform event (Java event) system can understand. Ideally, an event is delivered to a Java platform object (Java object) by calling a method on it, however, this is not possible because the event originates from native code, and because CLDC HotSpot Implementation has no facility that enables native code to call methods on Java objects. Instead, the native code places the native event into an event queue. A Java thread calls into native code to retrieve the event from the event queue.

This chapter describes the native interfaces to the event queue. This chapter also describes flow-of-control issues that determine when native code can place events into the queue and when Java threads can remove events from the queue.

MIDP must be able to receive events from outside the Java platform. The way that MIDP receives events depends, in part, on how the VM is invoked. See Section 10.4 "Invoking the Virtual Machine" in the CLDC HotSpottrademark Implementation Porting Guide for information about invoking the VM.

The central issue regarding event delivery is control flow, which is highlighted by the following questions:

The event processing service collects events from the native system in an event queue. These native events are converted into Java events, merged with a stream of events that originates from the Java layer, and are placed into a unified event queue. Events are processed one at a time. The processing of one event is always completed before the processing of the next event begins. This satisfies the event serialization requirements of the MIDP LCDUI package. Serial event processing is also useful for managing multi-threaded control flow that occurs in various portions of the implementation.

Multiple Java threads handle control flow by posting events. The event stream from the native event queue and event stream from the Java platform are interleaved into a unified Java event queue. The unified event queue is processed by a single Java thread.

Other subsystems that block threads, such as networking, multimedia, and WMA, cooperate with this service in conserving battery power when the VM is idle. Have the subsystems register for an event instead of waiting until some external state changes. This allows the VM to go to sleep waiting for the event, reducing CPU usage and thus conserving battery power.


Event System

FIGURE 10-1 shows an overview of what happens when Java Wireless Client software processes an event.

FIGURE 10-1 Event Queue Components and Interactions


Diagram shows the relationships between event queue components

The following steps describe the numbers shown in FIGURE 10-1:

1. The native event monitor (NEM) thread waits for a native event.

The NEM thread is a Java thread that monitors the arrival of native events.

2. The native system submits an event.

This is where your porting effort is focused and is described in more detail in Submitting Native Events below.

3. The NEM thread wakes up.

4. The NEM thread converts the native event to a Java object and places the event in the Java event queue.

5. The event dispatch thread processes the Java event.

The event dispatch thread pulls the next event from the event queue and chooses an event listener based on the type of event.

6. The event dispatch thread calls the appropriate event listener for the dispatched event.


procedure icon  Submitting Native Events

The preceding section describes the entire event processing service. You are responsible for implementing Step 2 (submitting native events). The following steps describe the step-by-step functionality that your code must provide:

1. Declare a midpEvent data structure.

2. Use the MIDP_EVENT_INITIALIZE macro to initialize the midpEvent data structure.

3. Complete the fields of the midpEvent data structure.

This is described in more detail in Complete the Event Fields.

4. Call the StoreMIDPEventInVmThread function to store the event in the event queue and to wake up the NEM thread.

The native event queue must be thread safe if multiple native threads have access to it. To ensure that multiple access does not corrupt the queue, a locking system is required. This is described in more detail in Locking.

5. Wait for the next native event.

How you wait is determined by your platform.



Note - The data structures and functions that you need to call are described in the Native API Reference. Refer to the section that describes midpEvents.h.



Complete the Event Fields

The MidpEvent data structure is a C struct with several integer and string fields. There are enough fields of each type to accommodate events that can occur in most systems. Platform-specific event code transforms information received from the platform by setting particular fields of the MidpEvent data structure. The type field of the event must always be set. For the other required fields, the meaning of the data depend upon the particular event type. For example, the code sample shown in CODE EXAMPLE 10-1 shows a pen event being submitted into the native event queue.


CODE EXAMPLE 10-1 Code Completing a MidpEvent Data Structure
void handlePointerEvent(...) {
	MidpEvent event;
 
 	MIDP_EVENT_INITIALIZE(event);
 
 	event.type = MIDP_PEN_EVENT; [defined in midpEvents.h]
 	event.intParam1 = PRESSED; [or DRAGGED or RELEASED]
 	event.intParam2 = the x position of the event ;
 	event.intParam3 = the y position of the event ;
 
 	StoreMIDPEventInVmThread(evt, 0);
}

Locking

Operations on the native event queue can occur on multiple threads, so you must create a facility that protects the event queue from concurrent access. This is accomplished through an event queue locking API defined in the midpServices.h header file. This API defines create, lock, unlock, and destroy operations. The functions defined by this API are called by the event queue code in MIDP and must be implemented by the platform-specific code.

The locking functions are described in the Native API Reference. Refer to the section that describes midpServices.h, located in the "Misc. System Services" category.

You must implement the following functions:

The semantics of these APIs are very similar to their equivalent POSIX threads (pthreads), and these functions can be implemented using the corresponding pthread functions:

Circumstances exist in which a single thread is responsible for both submitting events into the event queue and removing them. The VM is the only part of the system that removes events from the event queue. If the thread that runs the VM is also the only thread that submits events into the event queue, it is not possible to have concurrent access to the event queue. Even in this case, the locking functions are still called and you must provide implementations of the locking functions, but they can be empty.



Note - It is important to understand that it is only possible to guard against concurrent access to the event queue if all of the events are submitted from the same thread. Certain parts of the platform, such as networking or multimedia, might generate events from another thread. If any possibility exists that even a single event might be generated from some other thread, then the locking functions must be implemented to guard against concurrent access.



Setting the MAX_EVENTS Constant

The MAX_EVENTS constant limits the maximum number of events that can be placed in the native event queue at one time. You can change the value of MAX_EVENTS to work with the speed and memory requirements of your device. See Constants for the location of the MAX_EVENTS constant and instructions on modifying a constant's value.

Under most circumstances, events come in at a moderate rate and are processed quickly by the VM. However, if events occur at a high rate, and if the VM is not run frequently enough or if its time slice is not long enough, events can start to build up in the native event queue. In most cases, the cluster of events lasts briefly. For example, if the user drags the pointer across the screen very quickly. The VM eventually catches up and drains the events from the queue. Set the value of MAX_EVENTS to accommodate the largest expected flurry of events.


Types of Event Systems

This section discusses the flow of control between the VM and the rest of the native system, and how it interacts with event processing. Please refer to Chapter 10 of the CLDC HotSpottrademark Implementation Porting Guide, "Implementing J2ME Profiles," for information on the different modes in which the VM can be invoked, the APIs used for processing events, and code samples that illustrate use of these APIs.

Normal Mode

In normal mode, the main processing loop resides within the VM.



Note - Normal mode is often referred to as master mode because it is complementary to slave mode described later.



The general flow of control in normal mode is as follows:

1. Platform-specific code initializes.

2. Platform-specific code calls JVM_Start(), which does not return until the VM exits.

3. The VM main loop time slices between Java threads and periodically calls JVMSPI_CheckEvents().

4. JVM_Start() returns when all Java threads exit or when VM termination is requested.

This flow of control is illustrated in FIGURE 10-2.

FIGURE 10-2 CLDC HotSpot Implementation Running in Normal Mode


Diagram shows CLDC HotSpot implementation running in normal mode interacting with Java platform threads and native code

JVMSPI_CheckEvents() has several responsibilities. It must check for the occurrence of any event that can possibly occur in the system. Some events, such as user interface events, must be submitted into the event queue as described in Event System. Other events, such as network traffic, are destined for a particular Java thread. The CheckEvents code must awaken the appropriate thread so that the network traffic can be processed. Finally, if no events are available, the code must sleep for as long as the VM requests (possibly indefinitely).

For normal mode operation, MIDP provides a platform-independent implementation of JVMSPI_CheckEvents(). In turn, this implementation relies on platform-specific code to gather event information for it. This platform-specific code resides in the checkForSystemSignal() function that you must implement as part of your porting effort. Its responsibilities are similar to those of JVMSPI_CheckEvents() but its parameters and return values are different. It must convert the event into a MidpEvent structure as previously described. In addition, it must return information such as a file descriptor to the platform-independent layer so that any Java threads waiting for this event are awakened. This information is returned in a MidpReentryData structure.

The checkForSystemSignal() function and the data structures it uses are described in the midp_checkSysSignals.h header file.

Slave Mode

In slave mode, the main processing loop resides outside the VM. The general flow of control in slave mode looks like this:

1. Platform-specific code initializes.

2. Platform-specific code calls JVM_Start(), which returns immediately.

3. Platform-specific code runs a main loop that calls JVM_TimeSlice() to run Java threads for one time slice and checks for any events that occurred in the system, awakening Java threads as necessary.

4. The VM terminates when all Java threads exit, or when VM termination is requested.

5. The platform-specific main loop exits.

The control flow in slave mode is inverted in relation to normal mode. In normal mode, the VM time-slices Java threads and calls platform-specific code periodically to check for events. In contrast, in slave mode, the platform-specific code calls the VM periodically and checks for events in between calls to the VM. The event-checking step for slave mode (Step 3) has the same responsibilities that JVMSPI_CheckEvents() has for normal mode.

Slave mode is necessary in cases where the main processing loop cannot reside in the VM. An example of this occurs with user interface toolkits such as Qt. In a Qt environment, the main loop resides within the member function QPEApplication::enter_loop(). This function calls Qt callbacks to deliver events, and it blocks until an event is available.

JVM_TimeSlice() must be called repeatedly for Java threads to make progress. In a Qt-based system, this can be accomplished by ensuring that an event is always available for processing. Events are generated by setting up a timer event for a short time (or zero time) in the future. When the the timer event fires, the Qt calls the appropriate callback, which calls JVM_TimeSlice() and then reschedules a timer event. Handling of events, (for example, for the user interface and the network) are handled through other Qt callbacks.

This flow of control is illustrated in FIGURE 10-3.

FIGURE 10-3 CLDC HotSpot Implementation Running in Slave Mode


Diagram shows CLDC HotSpot implementation running in slave mode interacting with Java platform threads and other objects

Choosing Normal or Slave Mode

The preceding sections illustrate some of the issues to consider when choosing between normal and slave modes.

If your system has a call that reads events from an event queue, it might make sense to run the VM in normal mode. Place a call to the function that reads events in the implementation of the checkForSystemSignal() function.

If your system controls the main loop and issues callbacks to deliver events, it might make sense to run the VM in slave mode. Write callbacks to submit events into the MIDP event queue. In addition, you must employ a technique (such as a timer event) to ensure that JVM_TimeSlice() is called repeatedly.

Both of these approaches run the entire system in a single native thread. If the system can support multiple native threads, it might be possible to run the VM in normal mode in its own native thread. This technique adds complexity because the coupling between the system's event processing and the submission of events to the VM using checkForSystemSignal() must be done in a thread-safe manner. However, running the VM in normal mode can provide increased throughput and responsiveness if the OS can switch threads more quickly than time slices can be scheduled in the slave mode technique.