I noticed that I disabled the AWT event coalescing (most notably, this
makes all mouse move events beeing dispatched, resulting in heavy
performance regressions as noted in PR 29162). I did this because I had
some problems with it (lockup due to data-structure f***up). I fixed the
issue and re-enabled the coalescing, bringing back some performance in
mouse move and AWT paint event processing.

This brings me to finally check in some work that lies around here for
almost 1 week or so. While playing with EventQueue, I noticed that the
RI does re-order some events in the EventQueue. I found that PaintEvents
are always dispatched after all other events. So posting the following
events:
MouseEvent 1
PaintEvent 1
MouseEvent 2
PaintEvent 2

would result in the following events beeing dispatched in this order:
MouseEvent 1
MouseEvent 2
PaintEvent 1
PaintEvent 2

You can easily check that with a small program yourself. I think this is
an implementation detail that is not to be tested in Mauve. However, I
find that this is useful (despite the fact that this is somewhat against
the spec: << .. if AWTEvent A is enqueued to the EventQueue before
AWTEvent B then event B will not be dispatched before event A .. >>). I
enables for better optimizations of paint regions by making sure that
the paint events are dispatched _after_ all input has been processed.

I implemented something similar by having two internal queues, one for
normal events and one for 'low priority events', which are either
PaintEvents or internal event classes marked by the LowPriorityEvent
interface. I immediately make use of this for the RepaintManager worker
event, as this is more or less equivalent to the AWT PaintEvent in
purpose. It makes sure that the RepaintManager is triggered _after_ all
available input is processed, allowing for maximum optimization of
revalidation and paint regions.

I tested all this stuff for some time now and for me everything works
fine. Should you find a problem (like lockup or performance issues in
GUI apps, etc), please ping me and I'll fix ASAP.

2006-09-27  Roman Kennke  <[EMAIL PROTECTED]>

        * java/awt/EventQueue.java
        (Queue): New inner class. Implements the actual queue.
        (LOW_PRIORITY): New constant field.
        (NORM_PRIORITY): New constant field.
        (queueHead): Removed. Moved into Queue.
        (queueTail): Removed. Moved into Queue.
        (queues): New field.
        (EventQueue): Initialize two internal queues, one for
        normal events, one for low priority events.
        (getNextEventImpl): New helper method, fetches the next event.
        (getNextEvent): Use getNextEventImpl() for fetching the event.
        (peekEvent): Use getNextEventImpl() for fetching the event.
        (peekEvent(int)): Search for event in all queues.
        (postEventImpl(AWTEvent)): Moved actual posting into
        postEventImpl(AWTEvent,int). Prioritize events here.
        (postEventImpl(AWTEvent,int)): Take priority parameter and
insert
        event into correct queue. Re-enable event coalescing.
        * gnu/java/awt/LowPriorityEvent.java: New marker interface.
        * javax/swing/RepaintManager.java
        (RepaintWorkerEvent): New internal class. This is a low priority
        event for the repaint worker.
        (addDirtyRegion): Use new internal invokeLater() for sending
        a low priority event.
        (addInvalidComponent): Use new internal invokeLater() for
sending
        a low priority event.
        (commitBuffer): Added some null checks.
        (invokeLater): New helper method. Sends a low priority
        repaint worker event on the event queue.

/Roman

Index: gnu/java/awt/LowPriorityEvent.java
===================================================================
RCS file: gnu/java/awt/LowPriorityEvent.java
diff -N gnu/java/awt/LowPriorityEvent.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnu/java/awt/LowPriorityEvent.java	27 Sep 2006 20:16:34 -0000
@@ -0,0 +1,48 @@
+/* LowPriorityEvent.java -- Marks events with low priority
+   Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt;
+
+/**
+ * A marker interface that marks events with low priority. LowPriority events
+ * are dispatched _after_ other (normal priority) events by the EventQueue.
+ */
+public interface LowPriorityEvent
+{
+  // Empty marker interface.
+}
Index: java/awt/EventQueue.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/awt/EventQueue.java,v
retrieving revision 1.29
diff -u -1 -5 -r1.29 EventQueue.java
--- java/awt/EventQueue.java	18 Sep 2006 12:32:31 -0000	1.29
+++ java/awt/EventQueue.java	27 Sep 2006 20:16:34 -0000
@@ -26,65 +26,99 @@
 As a special exception, the copyright holders of this library give you
 permission to link this library with independent modules to produce an
 executable, regardless of the license terms of these independent
 modules, and to copy and distribute the resulting executable under
 terms of your choice, provided that you also meet, for each linked
 independent module, the terms and conditions of the license of that
 module.  An independent module is a module which is not derived from
 or based on this library.  If you modify this library, you may extend
 this exception to your version of the library, but you are not
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version. */
 
 
 package java.awt;
 
+import gnu.java.awt.LowPriorityEvent;
+
 import java.awt.event.ActionEvent;
 import java.awt.event.InputEvent;
 import java.awt.event.InputMethodEvent;
 import java.awt.event.InvocationEvent;
 import java.awt.event.PaintEvent;
 import java.awt.peer.ComponentPeer;
 import java.awt.peer.LightweightPeer;
 import java.lang.reflect.InvocationTargetException;
 import java.util.EmptyStackException;
 
 /* Written using on-line Java 2 Platform Standard Edition v1.3 API 
  * Specification, as well as "The Java Class Libraries", 2nd edition 
  * (Addison-Wesley, 1998).
  * Status:  Believed complete, but untested.
  */
 
 /**
  * This class manages a queue of <code>AWTEvent</code> objects that
  * are posted to it.  The AWT system uses only one event queue for all
  * events.
  *
  * @author Bryce McKinlay
  * @author Aaron M. Renn ([EMAIL PROTECTED])
  */
 public class EventQueue
 {
   /**
-   * The first item in the queue. This is where events are popped from.
+   * Indicates events that are processed with normal priority. This is normally
+   * all events except PaintEvents.
    */
-  private AWTEvent queueHead;
+  private static final int NORM_PRIORITY = 0;
 
   /**
-   * The last item. This is where events are posted to.
+   * Indicates events that are processed with lowes priority. This is normally
+   * all PaintEvents and LowPriorityEvents.
    */
-  private AWTEvent queueTail;
+  private static final int LOW_PRIORITY = 1;
+
+  /**
+   * Implements the actual queue. EventQueue has 2 internal queues for
+   * different priorities:
+   * 1 PaintEvents are always dispatched with low priority.
+   * 2. All other events are dispatched with normal priority.
+   *
+   * This makes sure that the actual painting (output) is performed _after_ all
+   * available input has been processed and that the paint regions are
+   * coalesced as much as possible.
+   */
+  private class Queue
+  {
+    /**
+     * The first item in the queue. This is where events are popped from.
+     */
+    AWTEvent queueHead;
+
+    /**
+     * The last item. This is where events are posted to.
+     */
+    AWTEvent queueTail;
+  }
+
+  /**
+   * The three internal event queues.
+   *
+   * @see Queue
+   */
+  private Queue[] queues;
 
   private EventQueue next;
   private EventQueue prev;
   private AWTEvent currentEvent;
   private long lastWhen = System.currentTimeMillis();
 
   private EventDispatchThread dispatchThread = new EventDispatchThread(this);
   private boolean shutdown = false;
 
   synchronized private void setShutdown (boolean b) 
   {
     shutdown = b;
   }
 
   synchronized boolean isShutdown ()
@@ -100,208 +134,265 @@
       {
         Frame[] frames = Frame.getFrames();
         for (int i = 0; i < frames.length; ++i)
           if (frames[i].isDisplayable())
             return false;
         return true;
       }
     return false;
   }
 
   /**
    * Initializes a new instance of <code>EventQueue</code>.
    */
   public EventQueue()
   {
-    // Nothing to do here.
+    queues = new Queue[2];
+    queues[NORM_PRIORITY] = new Queue();
+    queues[LOW_PRIORITY] = new Queue();
   }
 
   /**
    * Returns the next event in the queue.  This method will block until
    * an event is available or until the thread is interrupted.
    *
    * @return The next event in the queue.
    *
    * @exception InterruptedException If this thread is interrupted while
    * waiting for an event to be posted to the queue.
    */
   public synchronized AWTEvent getNextEvent()
     throws InterruptedException
   {
     if (next != null)
       return next.getNextEvent();
 
-    AWTEvent res = queueHead;
+    AWTEvent res = getNextEventImpl(true);
     while (res == null)
       {
         // We are not allowed to return null from this method, yet it
         // is possible that we actually have run out of native events
         // in the enclosing while() loop, and none of the native events
         // happened to cause AWT events. We therefore ought to check
         // the isShutdown() condition here, before risking a "native
         // wait". If we check it before entering this function we may
         // wait forever for events after the shutdown condition has
         // arisen.
 
         if (isShutdown())
           throw new InterruptedException();
 
         wait();
-        res = queueHead;
+        res = getNextEventImpl(true);
       }
 
-    // Unlink event from the queue.
-    queueHead = res.queueNext;
-    if (queueHead == null)
-      queueTail = null;
-    res.queueNext = null;
     return res;
   }
 
   /**
+   * Fetches and possibly removes the next event from the internal queues.
+   * This method returns immediately. When all queues are empty, this returns
+   * <code>null</code>:
+   *
+   * @param remove <true> when the event should be removed from the queue,
+   *        <code>false</code> otherwise
+   *
+   * @return the next event or <code>null</code> when all internal queues
+   *         are empty
+   */
+  private AWTEvent getNextEventImpl(boolean remove)
+  {
+    AWTEvent next = null;
+    for (int i = 0; i < queues.length && next == null; i++)
+      {
+        Queue q = queues[i];
+        if (q.queueHead != null)
+          {
+            // Got an event, remove it.
+            next = q.queueHead;
+            if (remove)
+              {
+                // Unlink event from the queue.
+                q.queueHead = next.queueNext;
+                if (q.queueHead == null)
+                  q.queueTail = null;
+                next.queueNext = null;
+              }
+          }
+      }
+    return next;
+  }
+
+  /**
    * Returns the next event in the queue without removing it from the queue.
    * This method will block until an event is available or until the thread
    * is interrupted.
    *
    * @return The next event in the queue.
    * @specnote Does not block. Returns null if there are no events on the 
    *            queue. 
    */ 
   public synchronized AWTEvent peekEvent()
   {
     if (next != null)
       return next.peekEvent();
 
-    return queueHead;
+    return getNextEventImpl(false);
   }
 
   /**
    * Returns the next event in the queue that has the specified id
    * without removing it from the queue.
    * This method will block until an event is available or until the thread
    * is interrupted.
    *
    * @param id The event id to return.
    *
    * @return The next event in the queue.
    *
    * @specnote Does not block. Returns null if there are no matching events 
    *            on the queue. 
    */ 
   public synchronized AWTEvent peekEvent(int id)
   {
     if (next != null)
       return next.peekEvent(id);
 
-    AWTEvent evt = queueHead;
-    while (evt != null && evt.id != id)
+    AWTEvent evt = null;
+    for (int i = 0; i < queues.length && evt == null; i++)
       {
-        evt = evt.queueNext;
+        Queue q = queues[i];
+        evt = q.queueHead;
+        while (evt != null && evt.id != id)
+          evt = evt.queueNext;
+        // At this point we either have found an event (evt != null -> exit
+        // for loop), or we have found no event (evt == null -> search next
+        // internal queue).
       }
     return evt;
   }
 
   /**
    * Posts a new event to the queue.
    *
    * @param evt The event to post to the queue.
    *
    * @exception NullPointerException If event is null.
    */
   public void postEvent(AWTEvent evt)
   {
     postEventImpl(evt);
   }
 
   /**
+   * Sorts events to their priority and calls
+   * [EMAIL PROTECTED] #postEventImpl(AWTEvent, int)}.
+   *
+   * @param evt the event to post
+   */
+  private synchronized final void postEventImpl(AWTEvent evt)
+  {
+    int priority = NORM_PRIORITY;
+    if (evt instanceof PaintEvent || evt instanceof LowPriorityEvent)
+      priority = LOW_PRIORITY;
+    // TODO: Maybe let Swing RepaintManager events also be processed with
+    // low priority.
+    postEventImpl(evt, priority);
+  }
+
+  /**
    * Actually performs the event posting. This is needed because the
    * RI doesn't use the public postEvent() method when transferring events
    * between event queues in push() and pop().
    * 
    * @param evt the event to post
+   * @param priority the priority of the event
    */
-  private synchronized final void postEventImpl(AWTEvent evt)
+  private final void postEventImpl(AWTEvent evt, int priority)
   {
     if (evt == null)
       throw new NullPointerException();
 
     if (next != null)
       {
         next.postEvent(evt);
         return;
       }
 
     Object source = evt.getSource();
 
+    Queue q = queues[priority];
     if (source instanceof Component)
       {
         // For PaintEvents, ask the ComponentPeer to coalesce the event
         // when the component is heavyweight.
         Component comp = (Component) source;
         ComponentPeer peer = comp.peer;
         if (peer != null && evt instanceof PaintEvent
             && ! (peer instanceof LightweightPeer))
           peer.coalescePaintEvent((PaintEvent) evt);
 
         // Check for any events already on the queue with the same source
         // and ID.
         AWTEvent previous = null;
-        for (AWTEvent qevt = queueHead; qevt != null; qevt = qevt.queueNext)
+        for (AWTEvent qevt = q.queueHead; qevt != null; qevt = qevt.queueNext)
           {
             Object src = qevt.getSource();
             if (qevt.id == evt.id && src == comp)
               {
                 // If there are, call coalesceEvents on the source component 
                 // to see if they can be combined.
                 Component srccmp = (Component) src;
                 AWTEvent coalescedEvt = srccmp.coalesceEvents(qevt, evt);
-                if (false && coalescedEvt != null)
+                if (coalescedEvt != null)
                   {
                     // Yes. Replace the existing event with the combined event.
                     if (qevt != coalescedEvt)
                       {
                         if (previous != null)
                           {
                             assert previous.queueNext == qevt;
                             previous.queueNext = coalescedEvt;
                           }
                         else
                           {
-                            assert queueHead == qevt;
-                            queueHead = coalescedEvt;
+                            assert q.queueHead == qevt;
+                            q.queueHead = coalescedEvt;
                           }
                         coalescedEvt.queueNext = qevt.queueNext;
+                        if (q.queueTail == qevt)
+                          q.queueTail = coalescedEvt;
                         qevt.queueNext = null;
                       }
                     return;
                   }
               }
             previous = qevt;
           }
       }
 
-    if (queueHead == null)
+    if (q.queueHead == null)
       {
         // We have an empty queue. Set this event both as head and as tail.
-        queueHead = evt;
-        queueTail = evt;
+        q.queueHead = evt;
+        q.queueTail = evt;
       }
     else
       {
         // Note: queueTail should not be null here.
-        queueTail.queueNext = evt;
-        queueTail = evt;
+        q.queueTail.queueNext = evt;
+        q.queueTail = evt;
       }
 
     if (dispatchThread == null || !dispatchThread.isAlive())
       {
         dispatchThread = new EventDispatchThread(this);
         dispatchThread.start();
       }
 
     notify();
   }
 
   /**
    * Causes runnable to have its run method called in the dispatch thread of the
    * EventQueue. This will happen after all pending events are processed. The
    * call blocks until this has happened. This method will throw an Error if
Index: javax/swing/RepaintManager.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/RepaintManager.java,v
retrieving revision 1.45
diff -u -1 -5 -r1.45 RepaintManager.java
--- javax/swing/RepaintManager.java	23 Aug 2006 22:03:23 -0000	1.45
+++ javax/swing/RepaintManager.java	27 Sep 2006 20:16:35 -0000
@@ -26,64 +26,95 @@
 As a special exception, the copyright holders of this library give you
 permission to link this library with independent modules to produce an
 executable, regardless of the license terms of these independent
 modules, and to copy and distribute the resulting executable under
 terms of your choice, provided that you also meet, for each linked
 independent module, the terms and conditions of the license of that
 module.  An independent module is a module which is not derived from
 or based on this library.  If you modify this library, you may extend
 this exception to your version of the library, but you are not
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version. */
 
 
 package javax.swing;
 
+import gnu.java.awt.LowPriorityEvent;
+
 import java.applet.Applet;
 import java.awt.Component;
 import java.awt.Dimension;
+import java.awt.EventQueue;
 import java.awt.Graphics;
 import java.awt.Image;
 import java.awt.Rectangle;
+import java.awt.Toolkit;
 import java.awt.Window;
+import java.awt.event.InvocationEvent;
 import java.awt.image.VolatileImage;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.WeakHashMap;
 
 /**
  * <p>The repaint manager holds a set of dirty regions, invalid components,
  * and a double buffer surface.  The dirty regions and invalid components
  * are used to coalesce multiple revalidate() and repaint() calls in the
  * component tree into larger groups to be refreshed "all at once"; the
  * double buffer surface is used by root components to paint
  * themselves.</p>
  *
  * <p>See <a
  * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html";>this
  * document</a> for more details.</p>
  *
  * @author Roman Kennke ([EMAIL PROTECTED])
  * @author Graydon Hoare ([EMAIL PROTECTED])
  * @author Audrius Meskauskas ([EMAIL PROTECTED])
  */
 public class RepaintManager
 {
   /**
+   * An InvocationEvent subclass that implements LowPriorityEvent. This is used
+   * to defer the execution of RepaintManager requests as long as possible on
+   * the event queue. This way we make sure that all available input is
+   * processed before getting active with the RepaintManager. This allows
+   * for better optimization (more validate and repaint requests can be
+   * coalesced) and thus has a positive effect on performance for GUI
+   * applications under heavy load.
+   */
+  private static class RepaintWorkerEvent
+    extends InvocationEvent
+    implements LowPriorityEvent
+  {
+
+    /**
+     * Creates a new RepaintManager event.
+     *
+     * @param source the source
+     * @param runnable the runnable to execute
+     */
+    public RepaintWorkerEvent(Object source, Runnable runnable)
+    {
+      super(source, runnable);
+    }
+  }
+  
+  /**
    * The current repaint managers, indexed by their ThreadGroups.
    */
   static WeakHashMap currentRepaintManagers;
 
   /**
    * A rectangle object to be reused in damaged regions calculation.
    */
   private static Rectangle rectCache = new Rectangle();
 
   /**
    * <p>A helper class which is placed into the system event queue at
    * various times in order to facilitate repainting and layout. There is
    * typically only one of these objects active at any time. When the
    * [EMAIL PROTECTED] RepaintManager} is told to queue a repaint, it checks to see if
    * a [EMAIL PROTECTED] RepaintWorker} is "live" in the system event queue, and if
@@ -328,31 +359,31 @@
           return;
         c = c.getParent();
       }
 
     if (invalidComponents.contains(validateRoot))
       return;
 
     //synchronized (invalidComponents)
     //  {
         invalidComponents.add(validateRoot);
     //  }
 
     if (! repaintWorker.isLive())
       {
         repaintWorker.setLive(true);
-        SwingUtilities.invokeLater(repaintWorker);
+        invokeLater(repaintWorker);
       }
   }
 
   /**
    * Remove a component from the [EMAIL PROTECTED] #invalidComponents} vector.
    *
    * @param component The component to remove
    *
    * @see #addInvalidComponent
    */
   public void removeInvalidComponent(JComponent component)
   {
     synchronized (invalidComponents)
       {
         invalidComponents.remove(component);
@@ -393,31 +424,31 @@
             SwingUtilities.computeUnion(rectCache.x, rectCache.y,
                                         rectCache.width, rectCache.height,
                                    (Rectangle) dirtyComponents.get(component));
           }
         else
           {
             synchronized (dirtyComponents)
               {
                 dirtyComponents.put(component, rectCache.getBounds());
               }
           }
 
         if (! repaintWorker.isLive())
           {
             repaintWorker.setLive(true);
-            SwingUtilities.invokeLater(repaintWorker);
+            invokeLater(repaintWorker);
           }
       }
   }
 
   /**
    * Get the dirty region associated with a component, or <code>null</code>
    * if the component has no dirty region.
    *
    * @param component The component to get the dirty region of
    *
    * @return The dirty region of the component
    *
    * @see #dirtyComponents
    * @see #addDirtyRegion
    * @see #isCompletelyDirty
@@ -681,37 +712,42 @@
    * @param y the area to paint on screen, in comp coordinates
    * @param w the area to paint on screen, in comp coordinates
    * @param h the area to paint on screen, in comp coordinates
    */
   void commitBuffer(Component comp, int x, int y, int w, int h)
   {
     Component root = comp;
     while (root != null
 	   && ! (root instanceof Window || root instanceof Applet))
       {
 	x += root.getX();
 	y += root.getY();
 	root = root.getParent();
       }
 
-    Graphics g = root.getGraphics();
-    Image buffer = (Image) offscreenBuffers.get(root);
-
-    // Make sure we have a sane clip at this point.
-    g.clipRect(x, y, w, h);
-    g.drawImage(buffer, 0, 0, root);
-    g.dispose();
+    if (root != null)
+      {
+        Graphics g = root.getGraphics();
+        Image buffer = (Image) offscreenBuffers.get(root);
+        if (buffer != null)
+          {
+            // Make sure we have a sane clip at this point.
+            g.clipRect(x, y, w, h);
+            g.drawImage(buffer, 0, 0, root);
+            g.dispose();
+          }
+      }
   }
 
   /**
    * Creates and returns a volatile offscreen buffer for the specified
    * component that can be used as a double buffer. The returned image
    * is a [EMAIL PROTECTED] VolatileImage}. Its size will be <code>(proposedWidth,
    * proposedHeight)</code> except when the maximum double buffer size
    * has been set in this RepaintManager.
    *
    * @param comp the Component for which to create a volatile buffer
    * @param proposedWidth the proposed width of the buffer
    * @param proposedHeight the proposed height of the buffer
    *
    * @since 1.4
    *
@@ -779,16 +815,30 @@
    * Get the value of the [EMAIL PROTECTED] #doubleBufferingEnabled} property.
    *
    * @return The current value of the property
    *
    * @see #setDoubleBufferingEnabled
    */
   public boolean isDoubleBufferingEnabled()
   {
     return doubleBufferingEnabled;
   }
   
   public String toString()
   {
     return "RepaintManager";
   }
+
+  /**
+   * Sends an RepaintManagerEvent to the event queue with the specified
+   * runnable. This is similar to SwingUtilities.invokeLater(), only that the
+   * event is a low priority event in order to defer the execution a little
+   * more.
+   */
+  private void invokeLater(Runnable runnable)
+  {
+    Toolkit tk = Toolkit.getDefaultToolkit();
+    EventQueue evQueue = tk.getSystemEventQueue();
+    InvocationEvent ev = new RepaintWorkerEvent(this, runnable);
+    evQueue.postEvent(ev);
+  }
 }

Reply via email to