-
-
Notifications
You must be signed in to change notification settings - Fork 261
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add new collections for tracking packet types and listeners
- Loading branch information
Showing
6 changed files
with
676 additions
and
0 deletions.
There are no files selected for viewing
122 changes: 122 additions & 0 deletions
122
src/main/java/com/comphenix/protocol/concurrent/PacketTypeListenerSet.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package com.comphenix.protocol.concurrent; | ||
|
||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
|
||
import com.comphenix.protocol.PacketType; | ||
import com.comphenix.protocol.events.PacketListener; | ||
import com.google.common.collect.ImmutableSet; | ||
|
||
/** | ||
* Manages the association between packet types and their corresponding | ||
* listeners. | ||
* <p> | ||
* This class is thread-safe for modifications. All read methods work on a | ||
* lock-free, best-effort principle, ensuring fast access. | ||
* </p> | ||
*/ | ||
public class PacketTypeListenerSet { | ||
|
||
// Map to store packet types and their associated listeners | ||
private final Map<PacketType, Set<PacketListener>> typeMap = new HashMap<>(); | ||
// Set to store packet classes of packet types that have listeners | ||
private final Set<Class<?>> classSet = new HashSet<>(); | ||
|
||
/** | ||
* Adds a listener for a specific packet type. | ||
* | ||
* @param packetType the packet type | ||
* @param listener the listener to add | ||
* @return {@code true} if the listener and packetType was added to the set; | ||
* {@code false} otherwise | ||
* @throws NullPointerException if the packetType or listener is null | ||
*/ | ||
public synchronized boolean add(PacketType packetType, PacketListener listener) { | ||
Objects.requireNonNull(packetType, "packetType cannot be null"); | ||
Objects.requireNonNull(listener, "listener cannot be null"); | ||
|
||
Set<PacketListener> listenerSet = this.typeMap.computeIfAbsent(packetType, key -> new HashSet<>()); | ||
if (!listenerSet.add(listener)) { | ||
return false; | ||
} | ||
|
||
// we can always add the packet class here as long as the listener got added | ||
this.classSet.add(packetType.getPacketClass()); | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Removes a listener for a specific packet type. | ||
* | ||
* @param packetType the packet type | ||
* @param listener the listener to remove | ||
* @return {@code true} if the set contained the specified listener; | ||
* {@code false} otherwise | ||
* @throws NullPointerException if the packetType or listener is null | ||
*/ | ||
public synchronized boolean remove(PacketType packetType, PacketListener listener) { | ||
Objects.requireNonNull(packetType, "packetType cannot be null"); | ||
Objects.requireNonNull(listener, "listener cannot be null"); | ||
|
||
Set<PacketListener> listenerSet = this.typeMap.get(packetType); | ||
if (listenerSet == null) { | ||
// this should never happen so better check for it during unit tests | ||
assert !classSet.contains(packetType.getPacketClass()); | ||
return false; | ||
} | ||
|
||
if (!listenerSet.remove(listener)) { | ||
// return since the listenerSet didn't change | ||
return false; | ||
} | ||
|
||
// packet type has no listeners remove type | ||
if (listenerSet.isEmpty()) { | ||
this.typeMap.remove(packetType); | ||
this.classSet.remove(packetType.getPacketClass()); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Checks if there are any listeners for a specific packet type. | ||
* | ||
* @param packetType the packet type | ||
* @return true if there are listeners for the packet type, false otherwise | ||
*/ | ||
public boolean contains(PacketType packetType) { | ||
return this.typeMap.containsKey(packetType); | ||
} | ||
|
||
/** | ||
* Checks if there are any listeners for a specific packet class. | ||
* | ||
* @param packetClass the packet class | ||
* @return true if there are listeners for the packet class, false otherwise | ||
*/ | ||
public boolean contains(Class<?> packetClass) { | ||
return this.classSet.contains(packetClass); | ||
} | ||
|
||
/** | ||
* Gets all the packet types that have listeners. | ||
* | ||
* @return a set of packet types that have listeners | ||
*/ | ||
public ImmutableSet<PacketType> values() { | ||
return ImmutableSet.copyOf(this.typeMap.keySet()); | ||
} | ||
|
||
/** | ||
* Clears all listeners and their associated packet types. | ||
*/ | ||
public void clear() { | ||
this.typeMap.clear(); | ||
this.classSet.clear(); | ||
} | ||
} |
151 changes: 151 additions & 0 deletions
151
src/main/java/com/comphenix/protocol/concurrent/PacketTypeMultiMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package com.comphenix.protocol.concurrent; | ||
|
||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
import com.comphenix.protocol.PacketType; | ||
import com.comphenix.protocol.events.ListenerPriority; | ||
import com.comphenix.protocol.events.ListeningWhitelist; | ||
import com.google.common.collect.ImmutableSet; | ||
|
||
/** | ||
* A map-like data structure that associates {@link PacketType}s with sets of | ||
* values. The values are stored in a {@link SortedCopyOnWriteSet}, which | ||
* ensures that the elements are kept in a sorted order based on the | ||
* {@link ListenerPriority} of the {@link ListeningWhitelist}, while maintaining | ||
* their insertion order for elements with equal priorities. | ||
* <p> | ||
* This class is thread-safe for modifications and guarantees a | ||
* modification-free iteration of associated values per packet type. All read methods | ||
* work on a lock-free, best-effort principle, ensuring fast access. | ||
* </p> | ||
* | ||
* @param <T> the type of elements maintained by this map | ||
*/ | ||
public class PacketTypeMultiMap<T> { | ||
|
||
private final Map<PacketType, SortedCopyOnWriteSet<T, PriorityHolder>> typeMap = new HashMap<>(); | ||
|
||
/** | ||
* Adds a value to the map, associating it with the {@link PacketType}s | ||
* contained in the specified {@link ListeningWhitelist}. If the value is | ||
* already present in the set (as determined by {@code equals}), it will not be | ||
* added again. | ||
* | ||
* @param key the whitelist containing the packet types to associate the value | ||
* with | ||
* @param value the value to be added | ||
* @throws NullPointerException if the key or value is null | ||
*/ | ||
public synchronized void put(ListeningWhitelist key, T value) { | ||
Objects.requireNonNull(key, "key cannot be null"); | ||
Objects.requireNonNull(value, "value cannot be null"); | ||
|
||
for (PacketType packetType : key.getTypes()) { | ||
this.typeMap.computeIfAbsent(packetType, type -> new SortedCopyOnWriteSet<>()).add(value, | ||
new PriorityHolder(key)); | ||
} | ||
} | ||
|
||
/** | ||
* Removes a value from the map, disassociating it from the {@link PacketType}s | ||
* contained in the specified {@link ListeningWhitelist}. If the value is not | ||
* present, the map remains unchanged. | ||
* | ||
* @param key the whitelist containing the packet types to disassociate the | ||
* value from | ||
* @param value the value to be removed | ||
* @throws NullPointerException if the key or value is null | ||
*/ | ||
public synchronized void remove(ListeningWhitelist key, T value) { | ||
Objects.requireNonNull(key, "key cannot be null"); | ||
Objects.requireNonNull(value, "value cannot be null"); | ||
|
||
for (PacketType packetType : key.getTypes()) { | ||
SortedCopyOnWriteSet<T, PriorityHolder> entrySet = this.typeMap.get(packetType); | ||
if (entrySet == null) { | ||
continue; | ||
} | ||
|
||
// we shouldn't have empty entrySets | ||
assert !entrySet.isEmpty(); | ||
|
||
// continue if value wasn't removed | ||
if (!entrySet.remove(value)) { | ||
continue; | ||
} | ||
|
||
// remove packet type without entries | ||
if (entrySet.isEmpty()) { | ||
this.typeMap.remove(packetType); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Returns an immutable set of all {@link PacketType}s currently in the map. | ||
* | ||
* @return an immutable set of packet types | ||
*/ | ||
public ImmutableSet<PacketType> getPacketTypes() { | ||
return ImmutableSet.copyOf(this.typeMap.keySet()); | ||
} | ||
|
||
/** | ||
* Checks if a specified {@link PacketType} is contained in the map. | ||
* | ||
* @param packetType the packet type to check for | ||
* @return {@code true} if the packet type is contained in the map, | ||
* {@code false} otherwise | ||
*/ | ||
public boolean contains(PacketType packetType) { | ||
return this.typeMap.containsKey(packetType); | ||
} | ||
|
||
/** | ||
* Returns an iterable of values associated with a specified {@link PacketType}. | ||
* If no values are associated with the packet type, an empty iterator is | ||
* returned. | ||
* | ||
* @param packetType the packet type to retrieve values for | ||
* @return an iterable of values associated with the packet type | ||
*/ | ||
public Iterable<T> get(PacketType packetType) { | ||
return () -> { | ||
SortedCopyOnWriteSet<T, PriorityHolder> entrySet = this.typeMap.get(packetType); | ||
|
||
if (entrySet != null) { | ||
return entrySet.iterator(); | ||
} | ||
|
||
return Collections.emptyIterator(); | ||
}; | ||
} | ||
|
||
/** | ||
* Clears all entries from the map. | ||
*/ | ||
public synchronized void clear() { | ||
this.typeMap.clear(); | ||
} | ||
|
||
/** | ||
* A holder for priority information used to order elements within the | ||
* {@link SortedCopyOnWriteSet}. | ||
*/ | ||
private static class PriorityHolder implements Comparable<PriorityHolder> { | ||
|
||
private final ListenerPriority priority; | ||
|
||
public PriorityHolder(ListeningWhitelist key) { | ||
this.priority = key.getPriority(); | ||
} | ||
|
||
@Override | ||
public int compareTo(PriorityHolder other) { | ||
return Integer.compare(this.priority.getSlot(), other.priority.getSlot()); | ||
} | ||
} | ||
} |
Oops, something went wrong.