Skip to content

Commit 76ff4ca

Browse files
authored
Reduce memory usage by lazy-loading internal listener priority lists (#64)
2 parents e0e273a + 3aba5d8 commit 76ff4ca

File tree

1 file changed

+43
-26
lines changed

1 file changed

+43
-26
lines changed

src/main/java/net/minecraftforge/eventbus/ListenerList.java

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@
99
import org.jetbrains.annotations.Nullable;
1010

1111
import java.util.ArrayList;
12-
import java.util.Arrays;
1312
import java.util.Collections;
1413
import java.util.List;
1514
import java.util.concurrent.Semaphore;
1615
import java.util.concurrent.atomic.AtomicReference;
1716

18-
1917
public class ListenerList {
2018
private static final List<ListenerList> allLists = new ArrayList<>();
2119
private static int maxSize = 0;
@@ -98,38 +96,45 @@ public static synchronized void unregisterAll(int id, IEventListener listener) {
9896
}
9997

10098
private static class ListenerListInst {
99+
// Enum#values() performs a defensive copy for each call.
100+
// As we never modify the returned values array in this class, we can safely reuse it.
101+
private static final EventPriority[] EVENT_PRIORITY_VALUES = EventPriority.values();
102+
101103
private boolean rebuild = true;
102104
private AtomicReference<IEventListener[]> listeners = new AtomicReference<>();
103-
private final ArrayList<ArrayList<IEventListener>> priorities;
105+
106+
/** A lazy-loaded array of lists containing listeners for each priority level. */
107+
@SuppressWarnings("unchecked")
108+
private final @Nullable ArrayList<IEventListener>[] priorities =
109+
(ArrayList<IEventListener>[]) new ArrayList[EVENT_PRIORITY_VALUES.length];
110+
104111
private ListenerListInst parent;
105112
private List<ListenerListInst> children;
106113
private final Semaphore writeLock = new Semaphore(1, true);
107114

108-
private ListenerListInst() {
109-
int count = EventPriority.values().length;
110-
priorities = new ArrayList<>(count);
115+
private ListenerListInst() {}
111116

112-
for (int x = 0; x < count; x++)
113-
priorities.add(new ArrayList<>());
117+
private ListenerListInst(ListenerListInst parent) {
118+
this.parent = parent;
119+
this.parent.addChild(this);
114120
}
115121

116122
public void dispose() {
117123
writeLock.acquireUninterruptibly();
118-
priorities.forEach(ArrayList::clear);
119-
priorities.clear();
124+
for (int i = 0; i < priorities.length; i++) {
125+
@Nullable ArrayList<IEventListener> priority = priorities[i];
126+
if (priority != null) {
127+
priority.clear();
128+
priorities[i] = null;
129+
}
130+
}
120131
writeLock.release();
121132
parent = null;
122133
listeners = null;
123134
if (children != null)
124135
children.clear();
125136
}
126137

127-
private ListenerListInst(ListenerListInst parent) {
128-
this();
129-
this.parent = parent;
130-
this.parent.addChild(this);
131-
}
132-
133138
/**
134139
* Returns a ArrayList containing all listeners for this event,
135140
* and all parent events for the specified priority.
@@ -141,7 +146,7 @@ private ListenerListInst(ListenerListInst parent) {
141146
*/
142147
public ArrayList<IEventListener> getListeners(EventPriority priority) {
143148
writeLock.acquireUninterruptibly();
144-
ArrayList<IEventListener> ret = new ArrayList<>(priorities.get(priority.ordinal()));
149+
ArrayList<IEventListener> ret = new ArrayList<>(getListenersForPriority(priority));
145150
writeLock.release();
146151
if (parent != null)
147152
ret.addAll(parent.getListeners(priority));
@@ -187,33 +192,45 @@ private void addChild(ListenerListInst child) {
187192
* Rebuild the local Array of listeners, returns early if there is no work to do.
188193
*/
189194
private void buildCache() {
190-
if(parent != null && parent.shouldRebuild())
195+
if (parent != null && parent.shouldRebuild())
191196
parent.buildCache();
192197

193198
ArrayList<IEventListener> ret = new ArrayList<>();
194-
Arrays.stream(EventPriority.values()).forEach(value -> {
199+
for (EventPriority value : EVENT_PRIORITY_VALUES) {
195200
List<IEventListener> listeners = getListeners(value);
196-
if (listeners.size() > 0) {
197-
ret.add(value); //Add the priority to notify the event of it's current phase.
198-
ret.addAll(listeners);
199-
}
200-
});
201+
if (listeners.isEmpty()) continue;
202+
ret.add(value); // Add the priority to notify the event of its current phase.
203+
ret.addAll(listeners);
204+
}
201205
this.listeners.set(ret.toArray(new IEventListener[0]));
202206
rebuild = false;
203207
}
204208

205209
public void register(EventPriority priority, IEventListener listener) {
206210
if (listener == null) return;
207211
writeLock.acquireUninterruptibly();
208-
priorities.get(priority.ordinal()).add(listener);
212+
getListenersForPriority(priority).add(listener);
209213
writeLock.release();
210214
this.forceRebuild();
211215
}
212216

213217
public void unregister(IEventListener listener) {
214218
writeLock.acquireUninterruptibly();
215-
priorities.stream().filter(list -> list.remove(listener)).forEach(list -> this.forceRebuild());
219+
boolean needsRebuild = false;
220+
for (var list : priorities) {
221+
if (list == null) continue;
222+
needsRebuild |= list.remove(listener);
223+
}
224+
if (needsRebuild) this.forceRebuild();
216225
writeLock.release();
217226
}
227+
228+
private ArrayList<IEventListener> getListenersForPriority(EventPriority priority) {
229+
var listenersForPriority = priorities[priority.ordinal()];
230+
if (listenersForPriority == null)
231+
listenersForPriority = priorities[priority.ordinal()] = new ArrayList<>();
232+
233+
return listenersForPriority;
234+
}
218235
}
219236
}

0 commit comments

Comments
 (0)