diff --git a/ProtocolLib/src/com/comphenix/protocol/events/ListenerPriority.java b/ProtocolLib/src/com/comphenix/protocol/events/ListenerPriority.java
new file mode 100644
index 00000000..f3d27dfd
--- /dev/null
+++ b/ProtocolLib/src/com/comphenix/protocol/events/ListenerPriority.java
@@ -0,0 +1,51 @@
+package com.comphenix.protocol.events;
+
+/**
+ * Represents a packet event priority, similar to the Bukkit EventPriority.
+ *
+ * @author Kristian
+ */
+public enum ListenerPriority {
+ /**
+ * Event call is of very low importance and should be ran first, to allow
+ * other plugins to further customise the outcome.
+ */
+ LOWEST(0),
+ /**
+ * Event call is of low importance.
+ */
+ LOW(1),
+ /**
+ * Event call is neither important or unimportant, and may be ran normally.
+ */
+ NORMAL(2),
+ /**
+ * Event call is of high importance.
+ */
+ HIGH(3),
+ /**
+ * Event call is critical and must have the final say in what happens to the
+ * event.
+ */
+ HIGHEST(4),
+ /**
+ * Event is listened to purely for monitoring the outcome of an event.
+ *
+ * No modifications to the event should be made under this priority.
+ */
+ MONITOR(5);
+
+ private final int slot;
+
+ private ListenerPriority(int slot) {
+ this.slot = slot;
+ }
+
+ /**
+ * A low slot represents a low priority.
+ * @return Integer representation of this priorty.
+ */
+ public int getSlot() {
+ return slot;
+ }
+}
diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketAdapter.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketAdapter.java
index 4e3ff3ca..d5aea595 100644
--- a/ProtocolLib/src/com/comphenix/protocol/events/PacketAdapter.java
+++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketAdapter.java
@@ -34,6 +34,7 @@ public abstract class PacketAdapter implements PacketListener {
protected Plugin plugin;
protected Set packetsID;
protected ConnectionSide connectionSide;
+ protected ListenerPriority listenerPriority;
/**
* Initialize a packet listener.
@@ -42,9 +43,21 @@ public abstract class PacketAdapter implements PacketListener {
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
+ this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
+ }
+
+ /**
+ * Initialize a packet listener.
+ * @param plugin - the plugin that spawned this listener.
+ * @param connectionSide - the packet type the listener is looking for.
+ * @param listenerPriority - the event priority.
+ * @param packets - the packet IDs the listener is looking for.
+ */
+ public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) {
this.plugin = plugin;
this.connectionSide = connectionSide;
this.packetsID = Sets.newHashSet(packets);
+ this.listenerPriority = listenerPriority;
}
@Override
@@ -72,6 +85,11 @@ public abstract class PacketAdapter implements PacketListener {
return plugin;
}
+ @Override
+ public ListenerPriority getListenerPriority() {
+ return listenerPriority;
+ }
+
/**
* Retrieves the name of the plugin that has been associated with the listener.
* @return Name of the associated plugin.
diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketListener.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketListener.java
index e263d15b..682b3780 100644
--- a/ProtocolLib/src/com/comphenix/protocol/events/PacketListener.java
+++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketListener.java
@@ -48,6 +48,12 @@ public interface PacketListener {
*/
public ConnectionSide getConnectionSide();
+ /**
+ * Retrieve the priority in the execution order of this packet listener. Highest priority will be executed last.
+ * @return Execution order in terms of priority.
+ */
+ public ListenerPriority getListenerPriority();
+
/**
* Set of packet ids we expect to recieve.
* @return Packets IDs.
diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java b/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java
new file mode 100644
index 00000000..9fac1868
--- /dev/null
+++ b/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java
@@ -0,0 +1,209 @@
+package com.comphenix.protocol.injector;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketEvent;
+import com.comphenix.protocol.events.PacketListener;
+import com.google.common.base.Objects;
+import com.google.common.primitives.Ints;
+
+/**
+ * A thread-safe implementation of a listener multimap.
+ *
+ * @author Kristian
+ */
+public class ConcurrentListenerMultimap {
+
+ // The core of our map
+ protected ConcurrentMap> listeners =
+ new ConcurrentHashMap>();
+
+ /**
+ * Adds a listener to its requested list of packet recievers.
+ * @param listener - listener with a list of packets to recieve notifcations for.
+ */
+ public void addListener(PacketListener listener) {
+ for (Integer packetID : listener.getPacketsID()) {
+ addListener(packetID, listener);
+ }
+ }
+
+ // Add the listener to a specific packet notifcation list
+ private void addListener(Integer packetID, PacketListener listener) {
+
+ SortedArrayList list = listeners.get(packetID);
+
+ // We don't want to create this for every lookup
+ if (list == null) {
+ // It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
+ // which is a essential feature for our purposes.
+ final SortedArrayList value = new SortedArrayList(new Comparator() {
+ @Override
+ public int compare(PacketListener o1, PacketListener o2) {
+ // This ensures that lower priority listeners are executed first
+ return Ints.compare(o1.getListenerPriority().getSlot(),
+ o2.getListenerPriority().getSlot());
+ }
+ });
+
+ list = listeners.putIfAbsent(packetID, value);
+
+ // We may end up creating multiple multisets, but we'll agree
+ // on the one to use.
+ if (list == null) {
+ list = value;
+ }
+ }
+
+ // Careful when modifying the set
+ synchronized(list) {
+ list.insertSorted(listener);
+ }
+ }
+
+ /**
+ * Removes the given listener from the packet event list.
+ * @param listener - listener to remove.
+ * @return Every packet ID that was removed due to no listeners.
+ */
+ public List removeListener(PacketListener listener) {
+
+ List removedPackets = new ArrayList();
+
+ // Again, not terribly efficient. But adding or removing listeners should be a rare event.
+ for (Integer packetID : listener.getPacketsID()) {
+
+ SortedArrayList list = listeners.get(packetID);
+
+ // Remove any listeners
+ if (list != null) {
+ synchronized(list) {
+ // Don't remove from newly created lists
+ if (list.size() > 0) {
+ list.removeAll(listener);
+
+ if (list.size() == 0) {
+ listeners.remove(packetID);
+ removedPackets.add(packetID);
+ }
+ }
+ }
+ }
+
+ // Move on to the next
+ }
+
+ return removedPackets;
+ }
+
+ /**
+ * Invokes the given packet event for every registered listener.
+ * @param logger - the logger that will be used to inform about listener exceptions.
+ * @param event - the packet event to invoke.
+ */
+ public void invokePacketRecieving(Logger logger, PacketEvent event) {
+ SortedArrayList list = listeners.get(event.getPacketID());
+
+ if (list == null)
+ return;
+
+ // We have to be careful. Cannot modify the underlying list when sending notifications.
+ synchronized (list) {
+ for (PacketListener listener : list) {
+ try {
+ listener.onPacketReceiving(event);
+ } catch (Exception e) {
+ // Minecraft doesn't want your Exception.
+ logger.log(Level.SEVERE,
+ "Exception occured in onPacketReceiving() for " + PacketAdapter.getPluginName(listener), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Invokes the given packet event for every registered listener.
+ * @param logger - the logger that will be used to inform about listener exceptions.
+ * @param event - the packet event to invoke.
+ */
+ public void invokePacketSending(Logger logger, PacketEvent event) {
+ SortedArrayList list = listeners.get(event.getPacketID());
+
+ if (list == null)
+ return;
+
+ synchronized (list) {
+ for (PacketListener listener : list) {
+ try {
+ listener.onPacketSending(event);
+ } catch (Exception e) {
+ // Minecraft doesn't want your Exception.
+ logger.log(Level.SEVERE,
+ "Exception occured in onPacketReceiving() for " + PacketAdapter.getPluginName(listener), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * An implicitly sorted array list that preserves insertion order and maintains duplicates.
+ *
+ * Note that only the {@link insertSorted} method will update the list correctly,
+ * @param - type of the sorted list.
+ */
+ private class SortedArrayList implements Iterable {
+
+ private Comparator comparator;
+ private List list = new ArrayList();
+
+ public SortedArrayList(Comparator comparator) {
+ this.comparator = comparator;
+ }
+
+ /**
+ * Inserts the given element in the proper location.
+ * @param value - element to insert.
+ */
+ public void insertSorted(T value) {
+ list.add(value);
+ for (int i = list.size() - 1; i > 0 && comparator.compare(value, list.get(i-1)) < 0; i--) {
+ T tmp = list.get(i);
+ list.set(i, list.get(i-1));
+ list.set(i-1, tmp);
+ }
+ }
+
+ /**
+ * Removes every instance of the given element.
+ * @param element - element to remove.
+ */
+ public void removeAll(T element) {
+ for (Iterator it = list.iterator(); it.hasNext(); ) {
+ if (Objects.equal(it.next(), element)) {
+ it.remove();
+ }
+ }
+ }
+
+ /**
+ * Retrieve the size of the list.
+ * @return Size of the list.
+ */
+ public int size() {
+ return list.size();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return list.iterator();
+ }
+ }
+}
diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java
index 8647d982..e9099f81 100644
--- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java
+++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java
@@ -20,10 +20,12 @@ package com.comphenix.protocol.injector;
import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -43,10 +45,8 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.ProtocolManager;
-import com.comphenix.protocol.events.ConnectionSide;
-import com.comphenix.protocol.events.PacketContainer;
-import com.comphenix.protocol.events.PacketEvent;
-import com.comphenix.protocol.events.PacketListener;
+import com.comphenix.protocol.events.*;
+import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
@@ -57,14 +57,18 @@ public final class PacketFilterManager implements ProtocolManager {
private Set packetListeners = new CopyOnWriteArraySet();
// Player injection
- private Map connectionLookup = new HashMap();
+ private Map connectionLookup = new ConcurrentHashMap();
private Map playerInjection = new HashMap();
// Packet injection
private PacketInjector packetInjector;
// Enabled packet filters
- private Set packetFilters = new HashSet();
+ private Set sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap());
+
+ // The two listener containers
+ private ConcurrentListenerMultimap recievedListeners = new ConcurrentListenerMultimap();
+ private ConcurrentListenerMultimap sendingListeners = new ConcurrentListenerMultimap();
// Whether or not this class has been closed
private boolean hasClosed;
@@ -107,10 +111,18 @@ public final class PacketFilterManager implements ProtocolManager {
public void addPacketListener(PacketListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL.");
-
+
+ ConnectionSide side = listener.getConnectionSide();
packetListeners.add(listener);
- enablePacketFilters(listener.getConnectionSide(),
- listener.getPacketsID());
+
+ // Add listeners
+ if (side.isForServer())
+ sendingListeners.addListener(listener);
+ if (side.isForClient())
+ recievedListeners.addListener(listener);
+
+ // Inform our injected hooks
+ enablePacketFilters(side, listener.getPacketsID());
}
@Override
@@ -118,9 +130,24 @@ public final class PacketFilterManager implements ProtocolManager {
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL");
+ ConnectionSide side = listener.getConnectionSide();
+ List sendingRemoved = null;
+ List receivingRemoved = null;
+
+ // Remove from the overal list of listeners
packetListeners.remove(listener);
- disablePacketFilters(listener.getConnectionSide(),
- listener.getPacketsID());
+
+ // Add listeners
+ if (side.isForServer())
+ sendingRemoved = sendingListeners.removeListener(listener);
+ if (side.isForClient())
+ receivingRemoved = recievedListeners.removeListener(listener);
+
+ // Remove hooks, if needed
+ if (sendingRemoved != null && sendingRemoved.size() > 0)
+ disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved);
+ if (receivingRemoved != null && receivingRemoved.size() > 0)
+ disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved);
}
@Override
@@ -132,7 +159,7 @@ public final class PacketFilterManager implements ProtocolManager {
// Remove the listener
if (Objects.equal(listener.getPlugin(), plugin)) {
- packetListeners.remove(listener);
+ removePacketListener(listener);
}
}
}
@@ -142,15 +169,7 @@ public final class PacketFilterManager implements ProtocolManager {
* @param event - the packet event to invoke.
*/
public void invokePacketRecieving(PacketEvent event) {
- for (PacketListener listener : packetListeners) {
- try {
- if (canHandlePacket(listener, event))
- listener.onPacketReceiving(event);
- } catch (Exception e) {
- // Minecraft doesn't want your Exception.
- logger.log(Level.SEVERE, "Exception occured in onPacketReceiving() for " + listener.toString(), e);
- }
- }
+ recievedListeners.invokePacketRecieving(logger, event);
}
/**
@@ -158,26 +177,7 @@ public final class PacketFilterManager implements ProtocolManager {
* @param event - the packet event to invoke.
*/
public void invokePacketSending(PacketEvent event) {
- for (PacketListener listener : packetListeners) {
- try {
- if (canHandlePacket(listener, event))
- listener.onPacketSending(event);
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Exception occured in onPacketReceiving() for " + listener.toString(), e);
- }
- }
- }
-
- private boolean canHandlePacket(PacketListener listener, PacketEvent event) {
- // Make sure the listener is looking for this packet
- if (!listener.getPacketsID().contains(event.getPacket().getID()))
- return false;
-
- // And this type of packet
- if (event.isServerPacket())
- return listener.getConnectionSide().isForServer();
- else
- return listener.getConnectionSide().isForClient();
+ sendingListeners.invokePacketSending(logger, event);
}
/**
@@ -188,13 +188,13 @@ public final class PacketFilterManager implements ProtocolManager {
* @param side - which side the event will arrive from.
* @param packets - the packet id(s).
*/
- private void enablePacketFilters(ConnectionSide side, Set packets) {
+ private void enablePacketFilters(ConnectionSide side, Iterable packets) {
if (side == null)
throw new IllegalArgumentException("side cannot be NULL.");
for (int packetID : packets) {
- if (side.isForServer())
- packetFilters.add(packetID);
+ if (side.isForServer())
+ sendingFilters.add(packetID);
if (side.isForClient() && packetInjector != null)
packetInjector.addPacketHandler(packetID);
}
@@ -205,13 +205,13 @@ public final class PacketFilterManager implements ProtocolManager {
* @param packets - the packet id(s).
* @param side - which side the event no longer should arrive from.
*/
- private void disablePacketFilters(ConnectionSide side, Set packets) {
+ private void disablePacketFilters(ConnectionSide side, Iterable packets) {
if (side == null)
throw new IllegalArgumentException("side cannot be NULL.");
for (int packetID : packets) {
if (side.isForServer())
- packetFilters.remove(packetID);
+ sendingFilters.remove(packetID);
if (side.isForClient() && packetInjector != null)
packetInjector.removePacketHandler(packetID);
}
@@ -268,7 +268,7 @@ public final class PacketFilterManager implements ProtocolManager {
if (!skipDefaults) {
try {
packet.getModifier().writeDefaults();
- } catch (IllegalAccessException e) {
+ } catch (FieldAccessException e) {
throw new RuntimeException("Security exception.", e);
}
}
@@ -279,9 +279,9 @@ public final class PacketFilterManager implements ProtocolManager {
@Override
public Set getPacketFilters() {
if (packetInjector != null)
- return Sets.union(packetFilters, packetInjector.getPacketHandlers());
+ return Sets.union(sendingFilters, packetInjector.getPacketHandlers());
else
- return packetFilters;
+ return sendingFilters;
}
/**
@@ -297,7 +297,7 @@ public final class PacketFilterManager implements ProtocolManager {
// Don't inject if the class has closed
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
try {
- PlayerInjector injector = new PlayerInjector(player, this, packetFilters);
+ PlayerInjector injector = new PlayerInjector(player, this, sendingFilters);
injector.injectManager();
playerInjection.put(player, injector);
diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java
index 88407308..6cd5244a 100644
--- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java
+++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java
@@ -84,15 +84,15 @@ class PlayerInjector {
// The packet manager and filters
private PacketFilterManager manager;
- private Set packetFilters;
+ private Set sendingFilters;
// Previous data input
private DataInputStream cachedInput;
- public PlayerInjector(Player player, PacketFilterManager manager, Set packetFilters) throws IllegalAccessException {
+ public PlayerInjector(Player player, PacketFilterManager manager, Set sendingFilters) throws IllegalAccessException {
this.player = player;
this.manager = manager;
- this.packetFilters = packetFilters;
+ this.sendingFilters = sendingFilters;
initialize();
}
@@ -342,7 +342,7 @@ class PlayerInjector {
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
// Make sure we're listening
- if (packetFilters.contains(id)) {
+ if (sendingFilters.contains(id)) {
// A packet has been sent guys!
PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = PacketEvent.fromServer(manager, container, player);
diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java b/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java
index 6b8eab6f..bc80d438 100644
--- a/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java
+++ b/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java
@@ -28,7 +28,7 @@ import com.comphenix.protocol.reflect.StructureModifier;
* Caches structure modifiers.
* @author Kristian
*/
-class StructureCache {
+public class StructureCache {
// Structure modifiers
private static Map> structureModifiers = new HashMap>();