Skip to content

Commit

Permalink
Improve EventListenerList Performance Bugzilla #255534
Browse files Browse the repository at this point in the history
By separating the event listener types into individual lists and using a
map the startup performance could be greatly reduced. It is for small
number of EditParts (i.e., 2000) already twice as fast as the previous
implementation.
Furthermore iterating through the event listeners is simpler and most
probably also faster.

The work is based on the patch submitted by Iwan Birrer to the following
Bugzilla entry:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=255534
  • Loading branch information
azoitl committed Nov 11, 2024
1 parent 8081afe commit 8d8e63e
Showing 1 changed file with 19 additions and 72 deletions.
91 changes: 19 additions & 72 deletions org.eclipse.draw2d/src/org/eclipse/draw2d/EventListenerList.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@
*******************************************************************************/
package org.eclipse.draw2d;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedDeque;

/**
* This class is intended for internal use only. TODO: If this is for internal
* use only, we should move it to the internal package.
*/
public final class EventListenerList {

private volatile Object[] array;
private final Map<Class<?>, Deque<Object>> listeners = new HashMap<>();
private static final Deque<Object> EMPTY_DEQUE = new ArrayDeque<>();

/**
* Adds a listener of type <i>c</i> to the list.
Expand All @@ -33,15 +39,7 @@ public synchronized <T> void addListener(Class<T> c, Object listener) {
throw new IllegalArgumentException();
}

int oldSize = (array == null) ? 0 : array.length;
Object[] newArray = new Object[oldSize + 2];
if (oldSize != 0) {
System.arraycopy(array, 0, newArray, 0, oldSize);
}
newArray[oldSize] = c;
oldSize++;
newArray[oldSize] = listener;
array = newArray;
listeners.computeIfAbsent(c, newC -> new ConcurrentLinkedDeque<>()).add(listener);
}

/**
Expand All @@ -52,50 +50,8 @@ public synchronized <T> void addListener(Class<T> c, Object listener) {
* @return whether this list contains a listener of type <i>c</i>
*/
public synchronized <T> boolean containsListener(Class<T> c) {
if (array == null) {
return false;
}
for (int i = 0; i < array.length; i += 2) {
if (array[i] == c) {
return true;
}
}
return false;
}

static class TypeIterator<T> implements Iterator<T> {
private final Object[] items;
private final Class<T> type;
private int index;

TypeIterator(Object[] items, Class<T> type) {
this.items = items;
this.type = type;
}

@Override
public T next() {
@SuppressWarnings("unchecked") // check is performed in hasNext
T result = (T) items[index + 1];
index += 2;
return result;
}

@Override
public boolean hasNext() {
if (items == null) {
return false;
}
while (index < items.length && items[index] != type) {
index += 2;
}
return index < items.length;
}

@Override
public void remove() {
throw new UnsupportedOperationException("Iterator removal not supported"); //$NON-NLS-1$
}
Deque<Object> specListeners = listeners.get(c);
return specListeners != null && !specListeners.isEmpty();
}

/**
Expand All @@ -104,8 +60,9 @@ public void remove() {
* @param listenerType the type
* @return an Iterator of all the listeners of type <i>c</i>
*/
@SuppressWarnings("unchecked")
public synchronized <T> Iterator<T> getListeners(final Class<T> listenerType) {
return new TypeIterator<>(array, listenerType);
return (Iterator<T>) listeners.getOrDefault(listenerType, EMPTY_DEQUE).iterator();
}

/**
Expand All @@ -115,8 +72,9 @@ public synchronized <T> Iterator<T> getListeners(final Class<T> listenerType) {
* @return an Iterable of all the listeners of type <i>c</i>
* @since 3.13
*/
@SuppressWarnings("unchecked")
public synchronized <T> Iterable<T> getListenersIterable(final Class<T> listenerType) {
return () -> new TypeIterator<>(array, listenerType);
return (Iterable<T>) listeners.getOrDefault(listenerType, EMPTY_DEQUE);
}

/**
Expand All @@ -126,28 +84,17 @@ public synchronized <T> Iterable<T> getListenersIterable(final Class<T> listener
* @param listener the listener
*/
public synchronized <T> void removeListener(Class<T> c, Object listener) {
if (array == null || array.length == 0) {
return;
}
if (listener == null || c == null) {
throw new IllegalArgumentException();
}

int index = 0;
while (index < array.length) {
if (array[index] == c && array[index + 1] == listener) {
break;
Deque<Object> specListeners = listeners.get(c);
if (specListeners != null) {
specListeners.remove(listener);
if (specListeners.isEmpty()) {
listeners.remove(c);
}
index += 2;
}
if (index == array.length) {
return; // listener was not found
}

Object[] newArray = new Object[array.length - 2];
System.arraycopy(array, 0, newArray, 0, index);
System.arraycopy(array, index + 2, newArray, index, array.length - index - 2);
array = newArray;
}

}

0 comments on commit 8d8e63e

Please sign in to comment.