From 9619298bd73dfdb18a45a9dc58b97de4e00b8916 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Tue, 31 May 2016 12:11:55 +0300 Subject: [PATCH] make performance not quadratic revisited: persuaded swing to post all events via IdeEventQueue.postEvent(), test added --- .../src/com/intellij/ide/IdeEventQueue.java | 36 +++++++++-- .../com/intellij/ide/IdeEventQueueTest.java | 62 ++++++++++++++++++- .../testFramework/PlatformTestUtil.java | 25 ++++++-- 3 files changed, 113 insertions(+), 10 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java index 4f23ce1b4c9a..7b107da1bdf6 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java @@ -50,17 +50,20 @@ import com.intellij.util.ui.MouseEventAdapter; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import sun.awt.AppContext; import javax.swing.*; import javax.swing.plaf.basic.ComboPopup; import java.awt.*; import java.awt.event.*; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; /** * @author Vladimir Kondratyev @@ -108,6 +111,8 @@ public class IdeEventQueue extends EventQueue { * Swing event. */ private int myEventCount; + final AtomicInteger myKeyboardEventsPosted = new AtomicInteger(); + final AtomicInteger myKeyboardEventsDispatched = new AtomicInteger(); private boolean myIsInInputEvent; private AWTEvent myCurrentEvent; private long myLastActiveTime; @@ -150,8 +155,22 @@ public class IdeEventQueue extends EventQueue { }); addDispatcher(new WindowsAltSuppressor(), null); + + abracadabraDaberBoreh(); } + private void abracadabraDaberBoreh() { + try { + Class aClass = Class.forName("sun.awt.PostEventQueue"); + Constructor constructor = aClass.getDeclaredConstructor(EventQueue.class); + constructor.setAccessible(true); + Object postEventQueue = constructor.newInstance(this); + AppContext.getAppContext().put("PostEventQueue", postEventQueue); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } public void setWindowManager(final WindowManagerEx windowManager) { myWindowManager = windowManager; @@ -373,6 +392,15 @@ public class IdeEventQueue extends EventQueue { } } + @Override + public AWTEvent getNextEvent() throws InterruptedException { + AWTEvent event = super.getNextEvent(); + if (isKeyboardEvent(event) && myKeyboardEventsDispatched.incrementAndGet() > myKeyboardEventsPosted.get()) { + throw new RuntimeException(event + "; posted: " + myKeyboardEventsPosted + "; dispatched: " + myKeyboardEventsDispatched); + } + return event; + } + @Nullable private static AccessToken startActivity(boolean userActivity) { if (ourTransactionGuard == null && appIsLoaded()) { @@ -558,10 +586,7 @@ public class IdeEventQueue extends EventQueue { enterSuspendModeIfNeeded(e); } - myKeyboardBusy = e instanceof KeyEvent || - peekEvent(KeyEvent.KEY_PRESSED) != null || - peekEvent(KeyEvent.KEY_RELEASED) != null || - peekEvent(KeyEvent.KEY_TYPED) != null; + myKeyboardBusy = e instanceof KeyEvent || myKeyboardEventsPosted.get() > myKeyboardEventsDispatched.get(); if (e instanceof KeyEvent) { if (e.getID() == KeyEvent.KEY_RELEASED && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_SHIFT) { @@ -1117,6 +1142,9 @@ public class IdeEventQueue extends EventQueue { @Override public void postEvent(@NotNull AWTEvent event) { myFrequentEventDetector.eventHappened(event); + if (isKeyboardEvent(event)) { + myKeyboardEventsPosted.incrementAndGet(); + } super.postEvent(event); } diff --git a/platform/platform-tests/testSrc/com/intellij/ide/IdeEventQueueTest.java b/platform/platform-tests/testSrc/com/intellij/ide/IdeEventQueueTest.java index e94411ca48c4..9012ab2a7696 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/IdeEventQueueTest.java +++ b/platform/platform-tests/testSrc/com/intellij/ide/IdeEventQueueTest.java @@ -20,11 +20,16 @@ import com.intellij.testFramework.PlatformTestUtil; import com.intellij.util.ui.UIUtil; import javax.swing.*; +import java.awt.*; +import java.awt.event.InputEvent; +import java.awt.event.InvocationEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; import java.util.concurrent.atomic.AtomicInteger; public class IdeEventQueueTest extends PlatformTestCase { public void testManyEvents() { - int N = 100/*000*/; + int N = 100000; PlatformTestUtil.startPerformanceTest("Event queue dispatch", 10000, () -> { UIUtil.dispatchAllInvocationEvents(); AtomicInteger count = new AtomicInteger(); @@ -35,4 +40,59 @@ public class IdeEventQueueTest extends PlatformTestCase { assertEquals(N, count.get()); }).assertTiming(); } + + public void testKeyboardEventsAreDetected() throws InterruptedException { + assertTrue(EventQueue.isDispatchThread()); + + IdeEventQueue ideEventQueue = IdeEventQueue.getInstance(); + EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue(); + assertSame(ideEventQueue, eventQueue); + PlatformTestUtil.dispatchAllEventsInIdeEventQueue(); + + int posted = ideEventQueue.myKeyboardEventsPosted.get(); + int dispatched = ideEventQueue.myKeyboardEventsDispatched.get(); + KeyEvent pressX = new KeyEvent(new JLabel(), KeyEvent.KEY_PRESSED, 1, InputEvent.ALT_DOWN_MASK, 11, 'x'); + eventQueue.postEvent(pressX); + assertEquals(posted+1, ideEventQueue.myKeyboardEventsPosted.get()); + assertEquals(dispatched, ideEventQueue.myKeyboardEventsDispatched.get()); + assertEquals(pressX, PlatformTestUtil.dispatchNextEventIfAny(ideEventQueue)); + + assertEquals(posted+1, ideEventQueue.myKeyboardEventsPosted.get()); + assertEquals(dispatched+1, ideEventQueue.myKeyboardEventsDispatched.get()); + + dispatchAllInvocationEventsOnly(ideEventQueue); + + // do not react to other events + MouseEvent mouseClick = new MouseEvent(new JLabel(), MouseEvent.BUTTON1, 1, InputEvent.BUTTON1_DOWN_MASK, 12, 14, 1, true); + eventQueue.postEvent(mouseClick); + assertEquals(posted+1, ideEventQueue.myKeyboardEventsPosted.get()); + assertEquals(dispatched+1, ideEventQueue.myKeyboardEventsDispatched.get()); + assertEquals(mouseClick, PlatformTestUtil.dispatchNextEventIfAny(ideEventQueue)); + + assertEquals(posted+1, ideEventQueue.myKeyboardEventsPosted.get()); + assertEquals(dispatched+1, ideEventQueue.myKeyboardEventsDispatched.get()); + + dispatchAllInvocationEventsOnly(ideEventQueue); + + KeyEvent keyRelease = new KeyEvent(new JLabel(), KeyEvent.KEY_RELEASED, 1, InputEvent.ALT_DOWN_MASK, 11, 'x'); + eventQueue.postEvent(keyRelease); + assertEquals(posted+2, ideEventQueue.myKeyboardEventsPosted.get()); + assertEquals(dispatched+1, ideEventQueue.myKeyboardEventsDispatched.get()); + assertEquals(keyRelease, PlatformTestUtil.dispatchNextEventIfAny(ideEventQueue)); + + assertEquals(posted+2, ideEventQueue.myKeyboardEventsPosted.get()); + assertEquals(dispatched+2, ideEventQueue.myKeyboardEventsDispatched.get()); + + dispatchAllInvocationEventsOnly(ideEventQueue); + } + + // need this because IdeEventQueue.dispatchEvent() calls invokeLater() and everybody else does too + private static void dispatchAllInvocationEventsOnly(IdeEventQueue ideEventQueue) throws InterruptedException { + while (true) { + AWTEvent event = ideEventQueue.peekEvent(); + if (!(event instanceof InvocationEvent)) break; + AWTEvent event1 = ideEventQueue.getNextEvent(); + ideEventQueue.dispatchEvent(event1); + } + } } diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java index c62d13ff820f..b6dd3e391813 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java @@ -294,17 +294,32 @@ public class PlatformTestUtil { @TestOnly public static void dispatchAllInvocationEventsInIdeEventQueue() throws InterruptedException { assert SwingUtilities.isEventDispatchThread() : Thread.currentThread(); - final EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue(); + final IdeEventQueue eventQueue = (IdeEventQueue)Toolkit.getDefaultToolkit().getSystemEventQueue(); while (true) { AWTEvent event = eventQueue.peekEvent(); if (event == null) break; - AWTEvent event1 = eventQueue.getNextEvent(); - if (event1 instanceof InvocationEvent) { - IdeEventQueue.getInstance().dispatchEvent(event1); - } + AWTEvent event1 = eventQueue.getNextEvent(); + if (event1 instanceof InvocationEvent) { + eventQueue.dispatchEvent(event1); + } } } + @TestOnly + public static void dispatchAllEventsInIdeEventQueue() throws InterruptedException { + assert SwingUtilities.isEventDispatchThread() : Thread.currentThread(); + final IdeEventQueue eventQueue = (IdeEventQueue)Toolkit.getDefaultToolkit().getSystemEventQueue(); + while (dispatchNextEventIfAny(eventQueue) != null); + } + + public static AWTEvent dispatchNextEventIfAny(@NotNull IdeEventQueue eventQueue) throws InterruptedException { + AWTEvent event = eventQueue.peekEvent(); + if (event == null) return null; + AWTEvent event1 = eventQueue.getNextEvent(); + eventQueue.dispatchEvent(event1); + return event1; + } + private static Date raidDate(Bombed bombed) { final Calendar instance = Calendar.getInstance(); instance.set(Calendar.YEAR, bombed.year());