Creating the MouseProcessor

The problem with just using the synchronized keyword is that we still cannot control exactly when we are handling events in our game in terms of where our main loop is located, and as a game grows bigger, more and more synchronization problems will arise. The last thing you want to do is throw the synchronized keyword everywhere, as this can affect the speed of execution heavily if handled poorly. Keeping your main loop and event interrupts synchronized in this way can make your code more complex than it needs to be. The most convenient solution is to handle any events that could cause problems with the main loop in the main loop itself. Ideally, our main game loop will be structured like this:

while(mainLoopRunning) {     // Handle input     // Do game logic     // Update display     // Handle Frame Rate }

This will involve storing a list of input events coming in from the listener thread and then emptying the events stored in this list in the main loop thread. We can perform this effectively by creating a class called MouseProcessor and an interface called MouseProcessable. Let's take a look at some source code.

Code Listing 10-2: MouseProcessable

start example
import java.awt.event.*;      public interface MouseProcessable {     public void handleMouseEvent(MouseEvent e); }
end example

This is a simple interface defining the method handleMouseEvent, taking one MouseEvent parameter. An object of a class that implements this interface will then provide an implementation of this method for handling events gathered by the mouse processor.

Code Listing 10-3: MouseProcessor

start example
import java.awt.event.*; import java.util.*;      public class MouseProcessor {     public MouseProcessor(MouseProcessable handler)     {         mouseEventList = new LinkedList();         mouseMotionEventList = new LinkedList();              this.handler = handler;     }          public void addMouseEvent(MouseEvent event)     {         synchronized(mouseEventList)         {             mouseEventList.add(event);         }     }              public void addMouseMotionEvent(MouseEvent event)     {         synchronized(mouseMotionEventList)         {              mouseMotionEventList.add(event);         }     }           public void processMouseEventList()     {         MouseEvent event;              while(mouseEventList.size() > 0)         {             synchronized(mouseEventList)             {                 event = (MouseEvent) mouseEventList.removeFirst();             }                   handler.handleMouseEvent(event);         }     }                   public void processMouseMotionEventList()     {         MouseEvent event;              while(mouseMotionEventList.size() > 0)         {             synchronized(mouseMotionEventList)             {                 event = (MouseEvent) mouseMotionEventList                     .removeFirst();             }                  handler.handleMouseEvent(event);         }     } 
end example

There are two basic implementations in the MouseProcessor class. One is adding an event to one of its LinkedList list objects, be it the mouseEventList or the mouseMotionEventList. The other is handling the contents of these lists with the methods processMouseEventList and processMouseMotionEventList. The functionality of the MouseProcessor revolves around mouse events coming in from the listener thread, with those events being added to their respective lists. Then, in the main loop thread, we can repeatedly make calls to the methods processMouseEventList and processMouseMotionEventList and handle any mouse events that occur, where we can see exactly when these events are being handled completely in synch with our game logic and rendering code. The beauty of the MouseProcessor is using the MouseProcessable interface. This interface defines the one method, handleMouseEvent(MouseEvent e), which acts as a callback method to handle the mouse events in the main loop thread. The constructor of the MouseProcessor requires a MouseProcessable object as a parameter, which will be stored internally and used as the object to handle the mouse events. We will implement this completely in an example shortly.

Adding Events to the MouseProcessor

The methods addMouseEvent and addMouseMotionEvent simply add MouseEvent objects as they are retrieved. Before we add the data to the lists, we need to synchronize adding events to those lists with those lists being processed (emptied) in the processMouseEventList and processMouseMotionEventList methods, respectively, which will be executed in the main loop thread. Here we can use the list object itself for synchronization, and therefore we also need to do this in the process methods.

Processing Events in the MouseProcessor

The methods processMouseEventList and processMouseMotionEventList are designed for invocation in the main loop, which will run through the given list containing recorded mouse events and handle them accordingly. The mouse events are removed from their list first in, first out (FIFO) as a queue, handled each time by calling the handleMouseEvent method of the MouseProcessable object originally passed when the MouseProcessor was constructed.

Note 

If you were merely listening for mouse motion events to track the latest position of the mouse, a mouse motion event list would not be required. Instead, you could just have one MouseEvent reference variable, simply holding a reference to the most recently received mouse motion event instead.

The MouseProcessor in Action

The following example, AdvancedMouse, is a demo applet of a circle object moving about the screen and bouncing off each of the four boundary walls. In this example we use the main loop code that we learned in Chapter 9, but this time we remove and recreate new HotSpot objects at the press of a mouse button, handling this removal/recreation code securely in the main loop using the MouseProcessor. This example uses five source files, two of which are the aforementioned MouseProcessor class and MouseProcessable interface. The other three classes that make up this example are AdvancedMouse (main class), Animator, and HotSpot. The Animator and HotSpot classes were used in the previous chapter as a means of quickly assembling an animation of a circle about the screen, so we could concentrate on the theory. The source code for these two classes is discussed in the previous chapter and must be used in order to compile this example. Before we go any further, let's take a look at the source code for AdvancedMouse.java.

Code Listing 10-4: AdvancedMouse.java

start example
import java.awt.*; import java.awt.image.*; import javax.swing.*; import java.awt.event.*;      public class AdvancedMouse extends JApplet              implements Runnable, MouseProcessable, MouseListener {     public void init()     {         getContentPane().setLayout(null);         setSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);         setIgnoreRepaint(true);           animator = new Animator(new Rectangle(0, 0, DISPLAY_WIDTH,             DISPLAY_HEIGHT));                  backBuffer = new BufferedImage(DISPLAY_WIDTH, DISPLAY_HEIGHT,             BufferedImage.TYPE_INT_RGB);         bbGraphics = (Graphics2D)backBuffer.getGraphics();              addMouseListener(this);         mouseProcessor = new MouseProcessor(this);     }          public void start()     {         loop = new Thread(this);         loop.start();     }          public void stop()     {         loop = null;     }          public void run()     {         long startTime, waitTime, elapsedTime;         // 1000/25 Frames Per Second = 40 millisecond delay         int delayTime = 1000/25;              Thread thisThread = Thread.currentThread();         while(loop==thisThread)         {             startTime = System.currentTimeMillis();                // handle mouse events in main loop             mouseProcessor.processMouseEventList();             mouseProcessor.processMouseMotionEventList(); // not used                   // handle logic             animator.animate();                   // render to back buffer             render(bbGraphics);                  // render to screen             Graphics g = getGraphics();             g.drawImage(backBuffer, 0, 0, null);             g.dispose();                   //  handle frame rate             elapsedTime = System.currentTimeMillis() - startTime;             waitTime = Math.max(delayTime - elapsedTime, 5);                          try             {                  Thread.sleep(waitTime);              }             catch(InterruptedException e) {}         }     }           public void render(Graphics g)     {         animator.render(g);     }           public void mousePressed(MouseEvent e)     {         System.out.println("Mouse Pressed");               mouseProcessor.addMouseEvent(e);     }           // not used     public void mouseReleased(MouseEvent e) {}     public void mouseClicked(MouseEvent e)  {}     public void mouseEntered(MouseEvent e)  {}     public void mouseExited(MouseEvent e)   {}                 public void handleMouseEvent(MouseEvent e)     {         if(e.getID()==MouseEvent.MOUSE_PRESSED)         {             System.out.println("Mouse Press Handled");                  if(animator.hotSpot==null)                 animator.createHotSpot();             else                  animator.hotSpot = null;         }     }           private Animator animator;     private Thread loop;     private BufferedImage backBuffer;     private Graphics2D bbGraphics;     private MouseProcessor mouseProcessor;           private static final int DISPLAY_WIDTH = 400;     private static final int DISPLAY_HEIGHT = 400; } 
end example

The vast majority of the main class AdvancedMouse should already be familiar to you, as we have already looked at creating a similar main game loop in the previous chapter. The main class AdvancedMouse implements both the MouseListener and the MouseProcessable interfaces. This means that this class provides methods for receiving the information on the mouse event from the Event Dispatch Thread, notably through the mousePressed method in this example, and provides the method handleMouseEvent for handling those events in the main loop thread through a call to the method processMouseEventList of the MouseProcessor object. It all begins in the mousePressed method, where we add a mouse press event to the mouse event list in the mouse processor.

public void mousePressed(MouseEvent e) {     System.out.println("Mouse Pressed");          mouseProcessor.addMouseEvent(e); }

Here we simply pass the MouseEvent object to the addMouseEvent method of the mouseProcessor object. The main class AdvancedMouse itself implements the MouseProcessable interface and is used to handle the mouse event through its method handleMouseEvent. This was defined in the init method of AdvancedMouse with the following line of code:

mouseProcessor = new MouseProcessor(this);

Here we tell the mouseProcessor object that we want AdvancedMouse to handle the mouse events.

When the MouseProcessor method processMouseEventList is next invoked in the main loop, any events in the queue will be processed one at a time in the order they were read.

mouseProcessor.processMouseEventList();

A call to this method means that each object stored in the mouse event list of the mouse processor will then be handled and removed from the list until the list is empty. Hence the list is checked and emptied at every cycle of the main loop.

    public void processMouseEventList()     {         MouseEvent event;              while(mouseEventList.size() > 0)         {             synchronized(mouseEventList)             {                 event = (MouseEvent) mouseEventList.removeFirst();             }                   handler.handleMouseEvent(event);         }     }

The handler object, of type MouseProcessable, that we originally passed in the constructor, is then used where its handleMouseEvent method is called. The handler object in this example is the instance of the main class AdvancedMouse.

public void handleMouseEvent(MouseEvent e)     {         if(e.getID()==MouseEvent.MOUSE_PRESSED)         {             System.out.println("Mouse Press Handled");                  if(animator.hotSpot==null)                 animator.createHotSpot();             else                  animator.hotSpot = null;         }     }

In this example, we handle the event if it is a mouse pressed event, which is not exactly necessary because we only add events of this type to the mouseProcessor list anyway, but I have left this in to illustrate differentiating between different types of mouse events being handled.

When you compile and run the AdvancedMouse example, you should get output similar to Figure 10-3. In this screen shot, we have clicked on the applet four times. As you can see in the console window, the text "Mouse Pressed" is followed by "Mouse Press Handled." The first text output is printed when the event is first received. The second text output indicates when the event is handled safely in the main loop thread.

click to expand
Figure 10-3:

A good thing to take a look at in this example is to set the variable delayTime in the run method of AdvancedMouse to a longer period of time than just a few milliseconds—try 1000 milliseconds (1 second) for example. When you do this, run the applet and click on the applet with the mouse quickly a number of times. You should see that your mouse events are accepted first and added to the mouse processor, and then all of these mouse events are processed when the next main loop cycle comes around again, showing the effect of adding and handling the events in the main loop more obviously.

In this example we do not actually listen for any mouse motion events (moving or dragging), but we have left this in to illustrate how you would call this. We now have our mouse events completely synchronized with the main loop thread. When a mouse button is pressed and processed in the main loop, we remove the reference to the current HotSpot object and then create a new HotSpot with which to replace it (with a further mouse button press). Synchronization problems highlighted earlier in this section no longer apply, as the event handling code is executed in synch with the rest of the code in the main loop.

If you want to see an example of our code failing because we have failed to synchronize the mouse event handling with a part of the main loop code, try the following:

  • In the animate method of the Animator class, just after the check to see if the hotspot reference is not equal to null, add a Thread.sleep(1000) call. You will need to try/catch an InterruptedException exception for this.)

  • In the mousePressed method of the AdvancedMouse class, replace all of the code with an immediate call to the handleMouseEvent method, passing the mouse event to it as a parameter.

  • Compile and run the applet and continue to click on the applet. After a few lucky clicks, you should get an unlucky one that throws a NullPointerException exception. The Thread.sleep (1000) call is placed immediately after the (hotSpot!=null) check to increase the likeliness that a mouse event will be handled at this stage, where we still believe that hotSpot!=null. This scenario was explained at the start of this section.



Java 1.4 Game Programming
Java 1.4 Game Programming (Wordware Game and Graphics Library)
ISBN: 1556229631
EAN: 2147483647
Year: 2003
Pages: 237

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net