make performance not quadratic revisited: persuaded swing to post all events via IdeEventQueue.postEvent(), test added

This commit is contained in:
Alexey Kudravtsev
2016-05-31 12:16:31 +03:00
parent f79220b072
commit 9619298bd7
3 changed files with 113 additions and 10 deletions
@@ -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);
}
@@ -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);
}
}
}
@@ -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());