Skip to content

Commit 49c8162

Browse files
authored
Unify ListenerList cache invalidation logic (#65)
1 parent da2bba6 commit 49c8162

File tree

1 file changed

+27
-12
lines changed

1 file changed

+27
-12
lines changed

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

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import java.util.Collections;
1313
import java.util.List;
1414
import java.util.concurrent.Semaphore;
15-
import java.util.concurrent.atomic.AtomicReference;
1615

1716
public class ListenerList {
1817
private static final List<ListenerList> allLists = new ArrayList<>();
@@ -99,9 +98,14 @@ private static class ListenerListInst {
9998
// Enum#values() performs a defensive copy for each call.
10099
// As we never modify the returned values array in this class, we can safely reuse it.
101100
private static final EventPriority[] EVENT_PRIORITY_VALUES = EventPriority.values();
101+
private static final IEventListener[] NO_LISTENERS = new IEventListener[0];
102102

103-
private boolean rebuild = true;
104-
private AtomicReference<IEventListener[]> listeners = new AtomicReference<>();
103+
/**
104+
* A lazy-loaded cache of listeners for all priority levels and any phase tracking notifiers.
105+
* <p><code>null</code> indicates that the cache needs to be rebuilt.</p>
106+
* @see #getListeners()
107+
*/
108+
private volatile @Nullable IEventListener[] listeners = NO_LISTENERS;
105109

106110
/** A lazy-loaded array of lists containing listeners for each priority level. */
107111
@SuppressWarnings("unchecked")
@@ -130,7 +134,7 @@ public void dispose() {
130134
}
131135
writeLock.release();
132136
parent = null;
133-
listeners = null;
137+
listeners = NO_LISTENERS;
134138
if (children != null)
135139
children.clear();
136140
}
@@ -164,16 +168,19 @@ public ArrayList<IEventListener> getListeners(EventPriority priority) {
164168
* @return Array containing listeners
165169
*/
166170
public IEventListener[] getListeners() {
167-
if (shouldRebuild()) buildCache();
168-
return listeners.get();
171+
var listeners = this.listeners;
172+
if (listeners != null)
173+
return listeners;
174+
175+
return buildCache();
169176
}
170177

171178
protected boolean shouldRebuild() {
172-
return rebuild;// || (parent != null && parent.shouldRebuild());
179+
return this.listeners == null;
173180
}
174181

175182
protected void forceRebuild() {
176-
this.rebuild = true;
183+
this.listeners = null;
177184
if (this.children != null) {
178185
synchronized (this.children) {
179186
for (ListenerListInst child : this.children)
@@ -189,9 +196,15 @@ private void addChild(ListenerListInst child) {
189196
}
190197

191198
/**
192-
* Rebuild the local Array of listeners, returns early if there is no work to do.
199+
* Rebuilds the cache of listeners, setting the {@link #listeners} field to the new array.
200+
*
201+
* <p>
202+
* Important: To avoid a race condition, you must use the return value of this method as the source of truth.
203+
* Attempting to read the {@link #listeners} field immediately after calling this method may observe
204+
* unexpected results caused by concurrent calls to this method and/or {@link #forceRebuild()}.
205+
* </p>
193206
*/
194-
private void buildCache() {
207+
private IEventListener[] buildCache() {
195208
if (parent != null && parent.shouldRebuild())
196209
parent.buildCache();
197210

@@ -202,8 +215,10 @@ private void buildCache() {
202215
ret.add(value); // Add the priority to notify the event of its current phase.
203216
ret.addAll(listeners);
204217
}
205-
this.listeners.set(ret.toArray(new IEventListener[0]));
206-
rebuild = false;
218+
219+
var retArray = ret.isEmpty() ? NO_LISTENERS : ret.toArray(new IEventListener[0]);
220+
this.listeners = retArray;
221+
return retArray;
207222
}
208223

209224
public void register(EventPriority priority, IEventListener listener) {

0 commit comments

Comments
 (0)