From 54242debaafe978d6d9a93a5174e33036d94aa8b Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 19 Sep 2012 19:25:11 +0200 Subject: [PATCH 01/50] Made structure modifiers more overridable. --- .../protocol/injector/EntityUtilities.java | 2 +- .../injector/PacketFilterManager.java | 2 - .../protocol/reflect/StructureModifier.java | 71 ++++++++++++++----- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java b/ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java index d358bcd7..c7c4d82d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java @@ -36,7 +36,7 @@ class EntityUtilities { private static Method scanPlayersMethod; public static void updateEntity(Entity entity, List observers) throws FieldAccessException { - + World world = entity.getWorld(); Object worldServer = ((CraftWorld) world).getHandle(); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index cd380899..8b331ddb 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -343,8 +343,6 @@ public final class PacketFilterManager implements ProtocolManager { @Override public void updateEntity(Entity entity, List observers) throws FieldAccessException { - - EntityUtilities.updateEntity(entity, observers); } diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java index 0f9aed1f..11df07b1 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java @@ -34,21 +34,21 @@ import com.google.common.collect.ImmutableList; public class StructureModifier { // Object and its type - private Class targetType; - private Object target; + protected Class targetType; + protected Object target; // Converter. May be NULL. - private EquivalentConverter converter; + protected EquivalentConverter converter; // The fields to read in order - private Class fieldType; - private List data = new ArrayList(); + protected Class fieldType; + protected List data = new ArrayList(); // Improved default values - private Set defaultFields; + protected Set defaultFields; // Cache of previous types - private Map subtypeCache; + protected Map subtypeCache; /** * Creates a structure modifier. @@ -63,16 +63,20 @@ public class StructureModifier { initialize(targetType, Object.class, fields, defaults, null, new HashMap()); } - private StructureModifier(StructureModifier other, Object target) { - initialize(other.targetType, other.fieldType, other.data, other.defaultFields, other.converter, other.subtypeCache); - this.target = target; - } - private StructureModifier() { // Consumers of this method should call "initialize" } - private void initialize(Class targetType, Class fieldType, + /** + * Initialize every field of this class. + * @param targetType - type of the object we're reading and writing from. + * @param fieldType - the common type of the fields we're modifying. + * @param data - list of fields to modify. + * @param defaultFields - list of fields that will be automatically initialized. + * @param converter - converts between the common field type and the actual type the consumer expects. + * @param subTypeCache - a structure modifier cache. + */ + protected void initialize(Class targetType, Class fieldType, List data, Set defaultFields, EquivalentConverter converter, Map subTypeCache) { this.targetType = targetType; @@ -235,9 +239,7 @@ public class StructureModifier { } // Cache structure modifiers - result = new StructureModifier(); - result.initialize(targetType, fieldType, filtered, defaults, - converter, new HashMap()); + result = withFieldType(fieldType, filtered, defaults, converter); if (fieldType != null) subtypeCache.put(fieldType, result); @@ -296,13 +298,36 @@ public class StructureModifier { return data.size(); } + /** + * Create a new structure modifier for the new field type. + * @param fieldType - common type of each field. + * @param filtered - list of fields after filtering the original modifier. + * @param defaults - list of default values after filtering the original. + * @param converter - the new converter. + * @return A new structure modifier. + */ + protected StructureModifier withFieldType( + Class fieldType, List filtered, + Set defaults, EquivalentConverter converter) { + + StructureModifier result = new StructureModifier(); + result.initialize(targetType, fieldType, filtered, defaults, + converter, new HashMap()); + return result; + } + /** * Retrieves a structure modifier of the same type for a different object target. * @param target - different target of the same type. * @return Structure modifier with the new target. */ public StructureModifier withTarget(Object target) { - return new StructureModifier(this, target); + StructureModifier copy = new StructureModifier(); + + // Create a new instance + copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache); + copy.target = target; + return copy; } /** @@ -312,12 +337,20 @@ public class StructureModifier { */ @SuppressWarnings("unchecked") private StructureModifier withConverter(EquivalentConverter converter) { - StructureModifier copy = new StructureModifier(this, target); + StructureModifier copy = withTarget(target); - copy.converter = converter; + copy.setConverter(converter); return copy; } + /** + * Set the current object converter. Should only be called during construction. + * @param converter - current object converter. + */ + protected void setConverter(EquivalentConverter converter) { + this.converter = converter; + } + /** * Retrieves a list of the fields matching the constraints of this structure modifier. * @return List of fields. From 9ad6dcb41c0003901bf0199e8c5aa2ca86b4c9f7 Mon Sep 17 00:00:00 2001 From: mbax Date: Wed, 19 Sep 2012 13:45:34 -0400 Subject: [PATCH 02/50] TagAPI is compatible as of version 1.7 --- ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java | 2 +- Readme.md | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index 7a1ba112..7abc4ff1 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -71,7 +71,7 @@ public class ProtocolLibrary extends JavaPlugin { private void checkForIncompatibility(PluginManager manager) { // Plugin authors: Notify me to remove these - String[] incompatiblePlugins = { "TagAPI" }; + String[] incompatiblePlugins = {}; for (String plugin : incompatiblePlugins) { if (manager.getPlugin(plugin) != null) { diff --git a/Readme.md b/Readme.md index 528610c9..c182ce0a 100644 --- a/Readme.md +++ b/Readme.md @@ -129,5 +129,3 @@ types. It's remarkably consistent across different versions. ### Incompatiblity The following plugins (to be expanded) are not compatible with ProtocolLib: - - * TagAPI \ No newline at end of file From 8839c03948dcb12853336d7c834ed95be81c18c4 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 20 Sep 2012 18:35:45 +0200 Subject: [PATCH 03/50] Fixed a number of simple bugs discovered by FindBugs. --- .../protocol/events/ListeningWhitelist.java | 2 +- .../protocol/injector/MinecraftRegistry.java | 4 ++-- .../injector/NetworkFieldInjector.java | 2 +- .../injector/PacketFilterManager.java | 4 ++-- .../protocol/reflect/StructureModifier.java | 21 +++++++++++++++---- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java b/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java index ee1a3870..31cfadbb 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java @@ -62,7 +62,7 @@ public class ListeningWhitelist { * @return TRUE if there are any packets, FALSE otherwise. */ public boolean isEnabled() { - return whitelist != null || whitelist.size() > 0; + return whitelist != null && whitelist.size() > 0; } /** diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java b/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java index 173f7e0b..c66ef956 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java @@ -88,8 +88,8 @@ class MinecraftRegistry { Map lookup = forceVanilla ? previousValues : overwrittenPackets; // Optimized lookup - if (lookup.containsKey(packetToID)) { - return lookup.get(packetToID); + if (lookup.containsKey(packetID)) { + return lookup.get(packetID); } // Will most likely not be used diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java index e3f6923d..d6f56ec3 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java @@ -48,7 +48,7 @@ class NetworkFieldInjector extends PlayerInjector { } @Override - protected void initialize() throws IllegalAccessException { + protected synchronized void initialize() throws IllegalAccessException { super.initialize(); // Get the sync field as well diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 8b331ddb..0bac1231 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -522,12 +522,12 @@ public final class PacketFilterManager implements ProtocolManager { if (!hasClosed && player != null) { PlayerInjector injector = playerInjection.get(player); - DataInputStream input = injector.getInputStream(true); if (injector != null) { + DataInputStream input = injector.getInputStream(true); injector.cleanupAll(); - playerInjection.remove(injector); + playerInjection.remove(player); connectionLookup.remove(input); } } diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java index 11df07b1..aecd71bd 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java @@ -63,8 +63,20 @@ public class StructureModifier { initialize(targetType, Object.class, fields, defaults, null, new HashMap()); } - private StructureModifier() { - // Consumers of this method should call "initialize" + /** + * Consumers of this method should call "initialize". + */ + protected StructureModifier() { + + } + + /** + * Initialize using the same field types. + * @param other - information to set. + */ + protected void initialize(StructureModifier other) { + initialize(other.targetType, other.fieldType, other.data, + other.defaultFields, other.converter, other.subtypeCache); } /** @@ -358,7 +370,7 @@ public class StructureModifier { public List getFields() { return ImmutableList.copyOf(data); } - + /** * Retrieve every value stored in the fields of the current type. * @return Every field value. @@ -367,8 +379,9 @@ public class StructureModifier { public List getValues() throws FieldAccessException { List values = new ArrayList(); - for (int i = 0; i < size(); i++) + for (int i = 0; i < size(); i++) { values.add(read(i)); + } return values; } From 7f69c0204d5fac36afbed6d83ee053ee2a26d084 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 25 Sep 2012 17:13:23 +0200 Subject: [PATCH 04/50] Added a bit of commentary. --- .../protocol/injector/EntityUtilities.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java b/ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java index c7c4d82d..e3c4a2c9 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/EntityUtilities.java @@ -35,6 +35,37 @@ class EntityUtilities { private static Method hashGetMethod; private static Method scanPlayersMethod; + /* + * While this function may look pretty bad, it's essentially just a reflection-warped + * version of the following: + * + * @SuppressWarnings("unchecked") + * public static void updateEntity2(Entity entity, List observers) { + * + * World world = entity.getWorld(); + * WorldServer worldServer = ((CraftWorld) world).getHandle(); + * + * EntityTracker tracker = worldServer.tracker; + * EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId()); + * + * List nmsPlayers = getNmsPlayers(observers); + * + * entry.trackedPlayers.removeAll(nmsPlayers); + * entry.scanPlayers(nmsPlayers); + * } + * + * private static List getNmsPlayers(List players) { + * List nsmPlayers = new ArrayList(); + * + * for (Player bukkitPlayer : players) { + * CraftPlayer craftPlayer = (CraftPlayer) bukkitPlayer; + * nsmPlayers.add(craftPlayer.getHandle()); + * } + * + * return nsmPlayers; + * } + * + */ public static void updateEntity(Entity entity, List observers) throws FieldAccessException { World world = entity.getWorld(); From e0c03186c3e0aed2bf43339eb49953896a4729eb Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 25 Sep 2012 23:09:02 +0200 Subject: [PATCH 05/50] Add support for Java serialization of PacketEvent. It might be useful. --- .../protocol/events/PacketContainer.java | 79 ++++++- .../protocol/events/PacketEvent.java | 27 ++- .../events/SerializedOfflinePlayer.java | 216 ++++++++++++++++++ 3 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/events/SerializedOfflinePlayer.java diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketContainer.java index 09207da9..34bd0a49 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketContainer.java @@ -17,6 +17,12 @@ package com.comphenix.protocol.events; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -40,13 +46,18 @@ import net.minecraft.server.Packet; * * @author Kristian */ -public class PacketContainer { +public class PacketContainer implements Serializable { - protected Packet handle; - protected int id; + /** + * Generated by Eclipse. + */ + private static final long serialVersionUID = 2074805748222377230L; + protected int id; + protected transient Packet handle; + // Current structure modifier - protected StructureModifier structureModifier; + protected transient StructureModifier structureModifier; // Check whether or not certain classes exists private static boolean hasWorldType = false; @@ -54,6 +65,10 @@ public class PacketContainer { // The getEntity method private static Method getEntity; + // Support for serialization + private static Method writeMethod; + private static Method readMethod; + static { try { Class.forName("net.minecraft.server.WorldType"); @@ -291,4 +306,60 @@ public class PacketContainer { public int getID() { return id; } + + private void writeObject(ObjectOutputStream output) throws IOException { + // Default serialization + output.defaultWriteObject(); + + // We'll take care of NULL packets as well + output.writeBoolean(handle != null); + + // Retrieve the write method by reflection + if (writeMethod == null) + writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class); + + try { + // Call the write-method + writeMethod.invoke(handle, new DataOutputStream(output)); + } catch (IllegalArgumentException e) { + throw new IOException("Minecraft packet doesn't support DataOutputStream", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Insufficient security privileges.", e); + } catch (InvocationTargetException e) { + throw new IOException("Could not serialize Minecraft packet.", e); + } + } + + private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { + // Default deserialization + input.defaultReadObject(); + + // Get structure modifier + structureModifier = StructureCache.getStructure(id); + + // Don't read NULL packets + if (input.readBoolean()) { + + // Create a default instance of the packet + handle = StructureCache.newPacket(id); + + // Retrieve the read method by reflection + if (readMethod == null) + readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class); + + // Call the read method + try { + readMethod.invoke(handle, new DataInputStream(input)); + } catch (IllegalArgumentException e) { + throw new IOException("Minecraft packet doesn't support DataInputStream", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Insufficient security privileges.", e); + } catch (InvocationTargetException e) { + throw new IOException("Could not deserialize Minecraft packet.", e); + } + + // And we're done + structureModifier = structureModifier.withTarget(handle); + } + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java index e4734e17..956227ee 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java @@ -17,6 +17,9 @@ package com.comphenix.protocol.events; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.EventObject; import org.bukkit.entity.Player; @@ -28,11 +31,11 @@ public class PacketEvent extends EventObject implements Cancellable { */ private static final long serialVersionUID = -5360289379097430620L; + private transient Player player; private PacketContainer packet; - private Player player; private boolean serverPacket; private boolean cancel; - + /** * Use the static constructors to create instances of this event. * @param source - the event source. @@ -125,4 +128,24 @@ public class PacketEvent extends EventObject implements Cancellable { public boolean isServerPacket() { return serverPacket; } + + private void writeObject(ObjectOutputStream output) throws IOException { + // Default serialization + output.defaultWriteObject(); + + // Write the name of the player (or NULL if it's not set) + output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null); + } + + private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { + // Default deserialization + input.defaultReadObject(); + + final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject(); + + if (offlinePlayer != null) { + // Better than nothing + player = offlinePlayer.getProxyPlayer(); + } + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/events/SerializedOfflinePlayer.java b/ProtocolLib/src/com/comphenix/protocol/events/SerializedOfflinePlayer.java new file mode 100644 index 00000000..4e222e57 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/events/SerializedOfflinePlayer.java @@ -0,0 +1,216 @@ +package com.comphenix.protocol.events; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.entity.Player; + +/** + * Represents a player object that can be serialized by Java. + * + * @author Kristian + */ +class SerializedOfflinePlayer implements OfflinePlayer, Serializable { + + /** + * Generated by Eclipse. + */ + private static final long serialVersionUID = -2728976288470282810L; + + private transient Location bedSpawnLocation; + + // Relevant data about an offline player + private String name; + private long firstPlayed; + private long lastPlayed; + private boolean operator; + private boolean banned; + private boolean playedBefore; + private boolean online; + private boolean whitelisted; + + // Proxy helper + private static Map lookup = new ConcurrentHashMap(); + + /** + * Constructor used by serialization. + */ + public SerializedOfflinePlayer() { + // Do nothing + } + + /** + * Initialize this serializable offline player from another player. + * @param offline - another player. + */ + public SerializedOfflinePlayer(OfflinePlayer offline) { + this.name = offline.getName(); + this.firstPlayed = offline.getFirstPlayed(); + this.lastPlayed = offline.getLastPlayed(); + this.operator = offline.isOp(); + this.banned = offline.isBanned(); + this.playedBefore = offline.hasPlayedBefore(); + this.online = offline.isOnline(); + this.whitelisted = offline.isWhitelisted(); + } + + @Override + public boolean isOp() { + return operator; + } + + @Override + public void setOp(boolean operator) { + this.operator = operator; + } + + @Override + public Map serialize() { + throw new UnsupportedOperationException(); + } + + @Override + public Location getBedSpawnLocation() { + return bedSpawnLocation; + } + + @Override + public long getFirstPlayed() { + return firstPlayed; + } + + @Override + public long getLastPlayed() { + return lastPlayed; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean hasPlayedBefore() { + return playedBefore; + } + + @Override + public boolean isBanned() { + return banned; + } + + @Override + public void setBanned(boolean banned) { + this.banned = banned; + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public boolean isWhitelisted() { + return whitelisted; + } + + @Override + public void setWhitelisted(boolean whitelisted) { + this.whitelisted = whitelisted; + } + + private void writeObject(ObjectOutputStream output) throws IOException { + output.defaultWriteObject(); + + // Serialize the bed spawn location + output.writeUTF(bedSpawnLocation.getWorld().getName()); + output.writeDouble(bedSpawnLocation.getX()); + output.writeDouble(bedSpawnLocation.getY()); + output.writeDouble(bedSpawnLocation.getZ()); + } + + private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { + input.defaultReadObject(); + + // Well, this is a problem + bedSpawnLocation = new Location( + getWorld(input.readUTF()), + input.readDouble(), + input.readDouble(), + input.readDouble() + ); + } + + private World getWorld(String name) { + try { + // Try to get the world at least + return Bukkit.getServer().getWorld(name); + } catch (Exception e) { + // Screw it + return null; + } + } + + @Override + public Player getPlayer() { + try { + // Try to get the real player underneath + return Bukkit.getServer().getPlayerExact(name); + } catch (Exception e) { + return getProxyPlayer(); + } + } + + /** + * Retrieve a player object that implements OfflinePlayer by refering to this object. + *

+ * All other methods cause an exception. + * @return Proxy object. + */ + public Player getProxyPlayer() { + + // Remember to initialize the method filter + if (lookup.size() == 0) { + // Add all public methods + for (Method method : OfflinePlayer.class.getMethods()) { + lookup.put(method.getName(), method); + } + } + + // MORE CGLIB magic! + Enhancer ex = new Enhancer(); + ex.setSuperclass(Player.class); + ex.setCallback(new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + + // There's no overloaded methods, so we don't care + Method offlineMethod = lookup.get(method.getName()); + + // Ignore all other methods + if (offlineMethod == null) { + throw new UnsupportedOperationException( + "The method " + method.getName() + " is not supported for offline players."); + } + + // Invoke our on method + return offlineMethod.invoke(SerializedOfflinePlayer.this, args); + } + }); + + return (Player) ex.create(); + } +} From d8b300e3a6c6d845b4e195d57990f07de314f02f Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 25 Sep 2012 23:10:08 +0200 Subject: [PATCH 06/50] Try to get the local player object if possible. --- ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java index 956227ee..f3fa4ae2 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java @@ -145,7 +145,7 @@ public class PacketEvent extends EventObject implements Cancellable { if (offlinePlayer != null) { // Better than nothing - player = offlinePlayer.getProxyPlayer(); + player = offlinePlayer.getPlayer(); } } } From 9e402a3ab4cb3e509383e7597495b8ffebc371a2 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 26 Sep 2012 02:01:44 +0200 Subject: [PATCH 07/50] Added a "canInject" property in the injectors. --- .../protocol/injector/NetworkFieldInjector.java | 7 +++++++ .../protocol/injector/NetworkObjectInjector.java | 8 ++++++++ .../protocol/injector/PacketFilterManager.java | 14 +++++++++----- .../protocol/injector/PlayerInjector.java | 6 ++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java index d6f56ec3..7275d5e1 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java @@ -83,6 +83,13 @@ class NetworkFieldInjector extends PlayerInjector { } } + + @Override + public boolean canInject() { + // Probably + return true; + } + @Override public void injectManager() { diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java index 02efd317..c9bb001d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java @@ -42,6 +42,14 @@ class NetworkObjectInjector extends PlayerInjector { } } + @Override + public boolean canInject() { + // We only support 1.3.0 at the moment. Fixing it require us to + // add jMock, which would add another dependency. + return networkManager != null && + networkManagerField.getType().isInterface(); + } + @Override public void injectManager() { diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 0bac1231..a74b0853 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -67,7 +67,7 @@ public final class PacketFilterManager implements ProtocolManager { /** * Override the network handler object itself. */ - NETWORK_MANAGER_OBJECT + NETWORK_MANAGER_OBJECT, } // Create a concurrent set @@ -362,16 +362,20 @@ public final class PacketFilterManager implements ProtocolManager { * @throws IllegalAccessException Unable to do our reflection magic. */ protected PlayerInjector getPlayerHookInstance(Player player) throws IllegalAccessException { - + return getHookInstance(player, playerHook); + } + + // Helper + private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException { // Construct the correct player hook - switch (playerHook) { + switch (hook) { case NETWORK_HANDLER_FIELDS: return new NetworkFieldInjector(player, this, sendingFilters); case NETWORK_MANAGER_OBJECT: return new NetworkObjectInjector(player, this, sendingFilters); + default: + throw new IllegalArgumentException("Cannot construct a player injector."); } - - throw new IllegalArgumentException("Cannot construct a player injector."); } /** diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index 79842052..b57baa09 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -190,6 +190,12 @@ abstract class PlayerInjector { */ public abstract void cleanupAll(); + /** + * Determine if we actually can inject. + * @return TRUE if this injector is compatible with the current CraftBukkit version, FALSE otherwise. + */ + public abstract boolean canInject(); + /** * Allows a packet to be recieved by the listeners. * @param packet - packet to recieve. From e04a78fc0446df8a82e1cb2bb3a7d871155c2d1c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 26 Sep 2012 02:26:44 +0200 Subject: [PATCH 08/50] Added yet another player hook method. This method works by injecting into the NetServerHandler object of a player. That way, we can also intercept map chunk packets. --- .../injector/NetworkServerInjector.java | 115 ++++++++++++++++++ .../injector/PacketFilterManager.java | 17 ++- .../protocol/injector/PlayerInjector.java | 19 ++- 3 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java new file mode 100644 index 00000000..7569f9b8 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java @@ -0,0 +1,115 @@ +package com.comphenix.protocol.injector; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Set; + +import net.minecraft.server.Packet; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.Factory; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +import org.bukkit.entity.Player; + +import com.comphenix.protocol.reflect.FuzzyReflection; + +/** + * Represents a player hook into the NetServerHandler class. + * + * @author Kristian + */ +public class NetworkServerInjector extends PlayerInjector { + + private static Method sendPacket; + + public NetworkServerInjector(Player player, PacketFilterManager manager, Set sendingFilters) throws IllegalAccessException { + super(player, manager, sendingFilters); + } + + @Override + protected void initialize() throws IllegalAccessException { + super.initialize(); + + // Get the send packet method! + if (hasInitialized) { + if (sendPacket == null) + sendPacket = FuzzyReflection.fromObject(serverHandler).getMethodByParameters("sendPacket", Packet.class); + } + } + + @Override + public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException { + Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue(); + + if (serverDeleage != null) { + try { + // Note that invocation target exception is a wrapper for a checked exception + sendPacket.invoke(serverDeleage, packet); + + } catch (IllegalArgumentException e) { + throw e; + } catch (InvocationTargetException e) { + throw e; + } catch (IllegalAccessException e) { + throw new IllegalStateException("Unable to access send packet method.", e); + } + } else { + throw new IllegalStateException("Unable to load server handler. Cannot send packet."); + } + } + + @Override + public void injectManager() { + if (serverHandlerRef == null) + throw new IllegalStateException("Cannot find server handler."); + // Don't inject twice + if (serverHandlerRef.getValue() instanceof Factory) + return; + + Enhancer ex = new Enhancer(); + ex.setClassLoader(manager.getClassLoader()); + ex.setSuperclass(serverHandler.getClass()); + ex.setCallback(new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + // The send packet method! + if (method.equals(sendPacket)) { + Packet packet = (Packet) args[0]; + + if (packet != null) { + packet = handlePacketRecieved(packet); + + // A NULL packet indicate cancelling + if (packet != null) + args[0] = packet; + else + return null; + } + } + + // Delegate to our underlying class + try { + return method.invoke(serverHandler, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + }); + + // Inject it now + serverHandlerRef.setValue(ex.create()); + } + + @Override + public void cleanupAll() { + if (serverHandlerRef != null) + serverHandlerRef.revertValue(); + } + + @Override + public boolean canInject() { + // Probably always + return true; + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index a74b0853..9ce1a23b 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -60,14 +60,23 @@ public final class PacketFilterManager implements ProtocolManager { */ public enum PlayerInjectHooks { /** - * Override the packet queue lists in NetworkHandler. + * Override the packet queue lists in NetworkHandler. + *

+ * Cannot intercept MapChunk packets. */ NETWORK_HANDLER_FIELDS, /** - * Override the network handler object itself. + * Override the network handler object itself. Only works in 1.3. + *

+ * Cannot intercept MapChunk packets. */ NETWORK_MANAGER_OBJECT, + + /** + * Override the server handler object. Versatile, but slow. + */ + NETWORK_SERVER_OBJECT; } // Create a concurrent set @@ -79,7 +88,7 @@ public final class PacketFilterManager implements ProtocolManager { private Map playerInjection = new HashMap(); // Player injection type - private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_HANDLER_FIELDS; + private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; // Packet injection private PacketInjector packetInjector; @@ -373,6 +382,8 @@ public final class PacketFilterManager implements ProtocolManager { return new NetworkFieldInjector(player, this, sendingFilters); case NETWORK_MANAGER_OBJECT: return new NetworkObjectInjector(player, this, sendingFilters); + case NETWORK_SERVER_OBJECT: + return new NetworkServerInjector(player, this, sendingFilters); default: throw new IllegalArgumentException("Cannot construct a player injector."); } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index b57baa09..5dd24cf2 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -56,9 +56,11 @@ abstract class PlayerInjector { // Reference to the player's network manager protected VolatileField networkManagerRef; + protected VolatileField serverHandlerRef; protected Object networkManager; // Current net handler + protected Object serverHandler; protected Object netHandler; // The packet manager and filters @@ -75,12 +77,18 @@ abstract class PlayerInjector { initialize(); } + /** + * Retrieve the notch (NMS) entity player object. + * @return Notch player object. + */ + protected EntityPlayer getEntityPlayer() { + CraftPlayer craft = (CraftPlayer) player; + return craft.getHandle(); + } + protected void initialize() throws IllegalAccessException { - CraftPlayer craft = (CraftPlayer) player; - EntityPlayer notchEntity = craft.getHandle(); - - Object serverHandler = null; + EntityPlayer notchEntity = getEntityPlayer(); if (!hasInitialized) { // Do this first, in case we encounter an exception @@ -89,7 +97,8 @@ abstract class PlayerInjector { // Retrieve the server handler if (serverHandlerField == null) serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler"); - serverHandler = FieldUtils.readField(serverHandlerField, notchEntity); + serverHandlerRef = new VolatileField(serverHandlerField, notchEntity); + serverHandler = serverHandlerRef.getValue(); // Next, get the network manager if (networkManagerField == null) From 22f2f45d1eefdfd9c27e1c5e3589d30422d9c6a1 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 26 Sep 2012 03:31:59 +0200 Subject: [PATCH 09/50] Added support for a hook method that can intercept map chunk packets. --- .../injector/NetworkServerInjector.java | 49 +++++++++-- .../injector/PacketFilterManager.java | 2 +- .../protocol/injector/PlayerInjector.java | 2 +- .../reflect/instances/DefaultInstances.java | 84 +++++++++++++------ .../reflect/instances/ExistingGenerator.java | 83 ++++++++++++++++++ 5 files changed, 186 insertions(+), 34 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/reflect/instances/ExistingGenerator.java diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java index 7569f9b8..eaa1d65e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java @@ -12,7 +12,12 @@ import net.sf.cglib.proxy.MethodProxy; import org.bukkit.entity.Player; +import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.instances.CollectionGenerator; +import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.comphenix.protocol.reflect.instances.ExistingGenerator; +import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; /** * Represents a player hook into the NetServerHandler class. @@ -21,7 +26,7 @@ import com.comphenix.protocol.reflect.FuzzyReflection; */ public class NetworkServerInjector extends PlayerInjector { - private static Method sendPacket; + private static Method sendPacketMethod; public NetworkServerInjector(Player player, PacketFilterManager manager, Set sendingFilters) throws IllegalAccessException { super(player, manager, sendingFilters); @@ -33,8 +38,8 @@ public class NetworkServerInjector extends PlayerInjector { // Get the send packet method! if (hasInitialized) { - if (sendPacket == null) - sendPacket = FuzzyReflection.fromObject(serverHandler).getMethodByParameters("sendPacket", Packet.class); + if (sendPacketMethod == null) + sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*"); } } @@ -45,7 +50,7 @@ public class NetworkServerInjector extends PlayerInjector { if (serverDeleage != null) { try { // Note that invocation target exception is a wrapper for a checked exception - sendPacket.invoke(serverDeleage, packet); + sendPacketMethod.invoke(serverDeleage, packet); } catch (IllegalArgumentException e) { throw e; @@ -67,14 +72,17 @@ public class NetworkServerInjector extends PlayerInjector { if (serverHandlerRef.getValue() instanceof Factory) return; + Class serverClass = serverHandler.getClass(); + Enhancer ex = new Enhancer(); ex.setClassLoader(manager.getClassLoader()); - ex.setSuperclass(serverHandler.getClass()); + ex.setSuperclass(serverClass); ex.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + // The send packet method! - if (method.equals(sendPacket)) { + if (method.equals(sendPacketMethod)) { Packet packet = (Packet) args[0]; if (packet != null) { @@ -97,14 +105,39 @@ public class NetworkServerInjector extends PlayerInjector { } }); + // Use the existing field values when we create our copy + DefaultInstances serverInstances = DefaultInstances.fromArray( + ExistingGenerator.fromObjectFields(serverHandler), + PrimitiveGenerator.INSTANCE, + CollectionGenerator.INSTANCE); + + Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass); + // Inject it now - serverHandlerRef.setValue(ex.create()); + if (proxyObject != null) { + serverHandlerRef.setValue(proxyObject); + } } @Override public void cleanupAll() { - if (serverHandlerRef != null) + if (serverHandlerRef != null) { serverHandlerRef.revertValue(); + } + + try { + if (getNetHandler() != null) { + // Restore packet listener + try { + FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true); + } catch (IllegalAccessException e) { + // Oh well + e.printStackTrace(); + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } } @Override diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 9ce1a23b..18fc3738 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -490,7 +490,7 @@ public final class PacketFilterManager implements ProtocolManager { if (event instanceof PlayerJoinEvent) injectPlayer(((PlayerJoinEvent) event).getPlayer()); else if (event instanceof PlayerQuitEvent) - injectPlayer(((PlayerQuitEvent) event).getPlayer()); + uninjectPlayer(((PlayerQuitEvent) event).getPlayer()); } return null; } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index 5dd24cf2..a05b30a7 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -127,7 +127,7 @@ abstract class PlayerInjector { * @return Current net handler. * @throws IllegalAccessException Unable to find or retrieve net handler. */ - private Object getNetHandler() throws IllegalAccessException { + protected Object getNetHandler() throws IllegalAccessException { // What a mess try { diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java index 6ab5c1dd..7c240ea0 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java @@ -20,6 +20,10 @@ package com.comphenix.protocol.reflect.instances; import java.lang.reflect.Constructor; import java.util.*; +import javax.print.CancelablePrintJob; + +import net.sf.cglib.proxy.Enhancer; + import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; @@ -100,6 +104,38 @@ public class DefaultInstances { return getDefaultInternal(type, registered, 0); } + /** + * Retrieve the constructor with the fewest number of parameters. + * @param type - type to construct. + * @return A constructor with the fewest number of parameters, or NULL if the type has no constructors. + */ + @SuppressWarnings("unchecked") + public Constructor getMinimumConstructor(Class type) { + + Constructor minimum = null; + int lastCount = Integer.MAX_VALUE; + + // Find the constructor with the fewest parameters + for (Constructor candidate : type.getConstructors()) { + Class[] types = candidate.getParameterTypes(); + + // Note that we don't allow recursive types - that is, types that + // require itself in the constructor. + if (types.length < lastCount) { + if (!contains(types, type)) { + minimum = (Constructor) candidate; + lastCount = types.length; + + // Don't loop again if we've already found the best possible constructor + if (lastCount == 0) + break; + } + } + } + + return minimum; + } + /** * Retrieves a default instance or value that is assignable to this type. *

@@ -136,36 +172,18 @@ public class DefaultInstances { if (value != null) return (T) value; } - - Constructor minimum = null; - int lastCount = Integer.MAX_VALUE; - - // Find the constructor with the fewest parameters - for (Constructor candidate : type.getConstructors()) { - Class[] types = candidate.getParameterTypes(); - - // Note that we don't allow recursive types - that is, types that - // require itself in the constructor. - if (types.length < lastCount) { - if (!contains(types, type)) { - minimum = (Constructor) candidate; - lastCount = types.length; - - // Don't loop again if we've already found the best possible constructor - if (lastCount == 0) - break; - } - } - } - + + Constructor minimum = getMinimumConstructor(type); + int parameterCount = minimum.getParameterTypes().length; + // Create the type with this constructor using default values. This might fail, though. try { if (minimum != null) { - Object[] params = new Object[lastCount]; + Object[] params = new Object[parameterCount]; Class[] types = minimum.getParameterTypes(); // Fill out - for (int i = 0; i < lastCount; i++) { + for (int i = 0; i < parameterCount; i++) { params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1); } @@ -180,6 +198,24 @@ public class DefaultInstances { return null; } + /** + * Construct default instances using the CGLIB enhancer object instead. + * @param enhancer - a CGLIB enhancer to use. + * @return A default instance generator that uses the CGLIB enhancer. + */ + public DefaultInstances forEnhancer(Enhancer enhancer) { + final Enhancer ex = enhancer; + + return new DefaultInstances(registered) { + @SuppressWarnings("unchecked") + @Override + protected T createInstance(Class type, Constructor constructor, Class[] types, Object[] params) { + // Use the enhancer instead + return (T) ex.create(types, params); + } + }; + } + /** * Used by the default instance provider to create a class from a given constructor. * The default method uses reflection. diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/ExistingGenerator.java b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/ExistingGenerator.java new file mode 100644 index 00000000..882bd614 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/ExistingGenerator.java @@ -0,0 +1,83 @@ +package com.comphenix.protocol.reflect.instances; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; + +/** + * Provides instance constructors using a list of existing values. + *

+ * Only one instance per individual class. + * @author Kristian + */ +public class ExistingGenerator implements InstanceProvider { + + @SuppressWarnings("rawtypes") + private Map existingValues = new HashMap(); + + private ExistingGenerator() { + // Only accessible to the constructors + } + + /** + * Automatically create an instance provider from a objects public and private fields. + *

+ * If two or more fields share the same type, the last declared non-null field will take + * precedent. + * @param object - object to create an instance generator from. + * @return The instance generator. + */ + public static ExistingGenerator fromObjectFields(Object object) { + ExistingGenerator generator = new ExistingGenerator(); + + // Read instances from every field. + for (Field field : FuzzyReflection.fromObject(object, true).getFields()) { + try { + Object value = FieldUtils.readField(field, object, true); + + if (value != null) + generator.addObject(value); + + } catch (Exception e) { + // Yes, swallow it. No, really. + } + } + + return generator; + } + + /** + * Create an instance generator from a pre-defined array of values. + * @param values - values to provide. + * @return An instance provider that uses these values. + */ + public static ExistingGenerator fromObjectArray(Object[] values) { + ExistingGenerator generator = new ExistingGenerator(); + + for (Object value : values) + generator.addObject(value); + + return generator; + } + + private void addObject(Object value) { + if (value == null) + throw new IllegalArgumentException("Value cannot be NULL."); + + existingValues.put(value.getClass(), value); + } + + @Override + public Object create(@Nullable Class type) { + + Object value = existingValues.get(type); + + // NULL values indicate that the generator failed + return value; + } +} From 9efb85e7c3d7f03159f1e836cdcb4f2c972305bd Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 26 Sep 2012 04:06:10 +0200 Subject: [PATCH 10/50] Handle 1.3 correctly. --- .../protocol/injector/NetworkServerInjector.java | 5 ++++- .../comphenix/protocol/injector/PlayerInjector.java | 13 +++++++++---- .../reflect/instances/DefaultInstances.java | 2 -- .../reflect/instances/ExistingGenerator.java | 8 +++++++- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java index eaa1d65e..d5f145e9 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java @@ -112,10 +112,13 @@ public class NetworkServerInjector extends PlayerInjector { CollectionGenerator.INSTANCE); Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass); - + // Inject it now if (proxyObject != null) { serverHandlerRef.setValue(proxyObject); + } else { + throw new RuntimeException( + "Cannot hook player: Unable to find a valid constructor for the NetServerHandler object."); } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index a05b30a7..612e6e70 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -132,21 +132,26 @@ abstract class PlayerInjector { // What a mess try { if (netHandlerField == null) - netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true). + netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true). getFieldByType("net\\.minecraft\\.NetHandler"); } catch (RuntimeException e1) { + // Swallow it + } + + // Second attempt + if (netHandlerField == null) { try { // Well, that sucks. Try just Minecraft objects then. - netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true). + netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true). getFieldByType(FuzzyReflection.MINECRAFT_OBJECT); } catch (RuntimeException e2) { - return new IllegalAccessException("Cannot locate net handler. " + e2.getMessage()); + throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage()); } } // Get the handler - if (netHandler != null) + if (netHandler == null) netHandler = FieldUtils.readField(netHandlerField, networkManager, true); return netHandler; } diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java index 7c240ea0..0e548ff0 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java @@ -20,8 +20,6 @@ package com.comphenix.protocol.reflect.instances; import java.lang.reflect.Constructor; import java.util.*; -import javax.print.CancelablePrintJob; - import net.sf.cglib.proxy.Enhancer; import com.google.common.base.Objects; diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/ExistingGenerator.java b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/ExistingGenerator.java index 882bd614..97bad0a6 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/ExistingGenerator.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/ExistingGenerator.java @@ -40,8 +40,9 @@ public class ExistingGenerator implements InstanceProvider { try { Object value = FieldUtils.readField(field, object, true); + // Use the type of the field, not the object itself if (value != null) - generator.addObject(value); + generator.addObject(field.getType(), value); } catch (Exception e) { // Yes, swallow it. No, really. @@ -72,6 +73,11 @@ public class ExistingGenerator implements InstanceProvider { existingValues.put(value.getClass(), value); } + private void addObject(Class type, Object value) { + existingValues.put(type, value); + } + + @Override public Object create(@Nullable Class type) { From 90970d1b9b5dfa3c80e9d222ea0701f7c94cb87c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 26 Sep 2012 06:00:26 +0200 Subject: [PATCH 11/50] Ensure that server operators are informed about incompatibility. --- .../protocol/events/ListeningWhitelist.java | 17 ++++ .../injector/NetworkFieldInjector.java | 13 ++- .../injector/NetworkObjectInjector.java | 15 ++-- .../injector/NetworkServerInjector.java | 9 ++- .../injector/PacketFilterManager.java | 81 ++++++++++++++----- .../protocol/injector/PlayerInjector.java | 9 ++- 6 files changed, 110 insertions(+), 34 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java b/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java index 31cfadbb..cca5740d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/ListeningWhitelist.java @@ -86,6 +86,23 @@ public class ListeningWhitelist { return Objects.hashCode(priority, whitelist); } + /** + * Determine if any of the given IDs can be found in the whitelist. + * @param whitelist - whitelist to test. + * @param idList - list of packet IDs to find. + * @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise. + */ + public static boolean containsAny(ListeningWhitelist whitelist, int... idList) { + if (whitelist != null) { + for (int i = 0; i < idList.length; i++) { + if (whitelist.getWhitelist().contains(idList[i])) + return true; + } + } + + return false; + } + @Override public boolean equals(final Object obj){ if(obj instanceof ListeningWhitelist){ diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java index 7275d5e1..56903e35 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkFieldInjector.java @@ -10,6 +10,9 @@ import java.util.concurrent.ConcurrentHashMap; import org.bukkit.entity.Player; +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; @@ -83,11 +86,13 @@ class NetworkFieldInjector extends PlayerInjector { } } - @Override - public boolean canInject() { - // Probably - return true; + public void checkListener(PacketListener listener) { + // Unfortunately, we don't support chunk packets + if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), + Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) { + throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners."); + } } @Override diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java index c9bb001d..9091390d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkObjectInjector.java @@ -11,6 +11,10 @@ import java.util.Set; import org.bukkit.entity.Player; +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.events.PacketListener; + /** * Injection method that overrides the NetworkHandler itself, and it's sendPacket-method. * @@ -43,11 +47,12 @@ class NetworkObjectInjector extends PlayerInjector { } @Override - public boolean canInject() { - // We only support 1.3.0 at the moment. Fixing it require us to - // add jMock, which would add another dependency. - return networkManager != null && - networkManagerField.getType().isInterface(); + public void checkListener(PacketListener listener) { + // Unfortunately, we don't support chunk packets + if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), + Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) { + throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners."); + } } @Override diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java index d5f145e9..ba9d6ee0 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java @@ -12,6 +12,7 @@ import net.sf.cglib.proxy.MethodProxy; import org.bukkit.entity.Player; +import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.instances.CollectionGenerator; @@ -66,6 +67,7 @@ public class NetworkServerInjector extends PlayerInjector { @Override public void injectManager() { + if (serverHandlerRef == null) throw new IllegalStateException("Cannot find server handler."); // Don't inject twice @@ -142,10 +144,9 @@ public class NetworkServerInjector extends PlayerInjector { e.printStackTrace(); } } - + @Override - public boolean canInject() { - // Probably always - return true; + public void checkListener(PacketListener listener) { + // We support everything } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 18fc3738..3d5d94b3 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -74,7 +74,7 @@ public final class PacketFilterManager implements ProtocolManager { NETWORK_MANAGER_OBJECT, /** - * Override the server handler object. Versatile, but slow. + * Override the server handler object. Versatile, but a tad slower. */ NETWORK_SERVER_OBJECT; } @@ -106,6 +106,9 @@ public final class PacketFilterManager implements ProtocolManager { // The default class loader private ClassLoader classLoader; + // The last successful player hook + private PlayerInjector lastSuccessfulHook; + // Error logger private Logger logger; @@ -142,6 +145,13 @@ public final class PacketFilterManager implements ProtocolManager { */ public void setPlayerHook(PlayerInjectHooks playerHook) { this.playerHook = playerHook; + + // Make sure the current listeners are compatible + if (lastSuccessfulHook != null) { + for (PacketListener listener : packetListeners) { + checkListener(listener); + } + } } public Logger getLogger() { @@ -176,6 +186,9 @@ public final class PacketFilterManager implements ProtocolManager { if (hasReceiving) { recievedListeners.addListener(listener, receiving); enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); + + // We don't know if we've hooked any players yet + checkListener(listener); } // Inform our injected hooks @@ -183,6 +196,20 @@ public final class PacketFilterManager implements ProtocolManager { } } + /** + * Determine if a listener is valid or not. + * @param listener - listener to check. + * @throws IllegalStateException If the given listener's whitelist cannot be fulfilled. + */ + public void checkListener(PacketListener listener) { + try { + if (lastSuccessfulHook != null) + lastSuccessfulHook.checkListener(listener); + } catch (Exception e) { + throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e); + } + } + @Override public void removePacketListener(PacketListener listener) { if (listener == null) @@ -367,15 +394,11 @@ public final class PacketFilterManager implements ProtocolManager { /** * Used to construct a player hook. * @param player - the player to hook. + * @param hook - the hook type. * @return A new player hoook * @throws IllegalAccessException Unable to do our reflection magic. */ - protected PlayerInjector getPlayerHookInstance(Player player) throws IllegalAccessException { - return getHookInstance(player, playerHook); - } - - // Helper - private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException { + protected PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException { // Construct the correct player hook switch (hook) { case NETWORK_HANDLER_FIELDS: @@ -394,20 +417,42 @@ public final class PacketFilterManager implements ProtocolManager { * @param player - player to hook. */ protected void injectPlayer(Player player) { + + PlayerInjector injector = null; + PlayerInjectHooks currentHook = playerHook; + boolean firstPlayer = lastSuccessfulHook == null; + // Don't inject if the class has closed if (!hasClosed && player != null && !playerInjection.containsKey(player)) { - try { - PlayerInjector injector = getPlayerHookInstance(player); - - injector.injectManager(); - playerInjection.put(player, injector); - connectionLookup.put(injector.getInputStream(false), player); - - } catch (IllegalAccessException e) { - // Mark this injection attempt as a failure - playerInjection.put(player, null); - logger.log(Level.SEVERE, "Unable to access fields.", e); + while (true) { + try { + injector = getHookInstance(player, currentHook); + injector.injectManager(); + playerInjection.put(player, injector); + connectionLookup.put(injector.getInputStream(false), player); + break; + + } catch (Exception e) { + // Mark this injection attempt as a failure + logger.log(Level.SEVERE, "Player hook " + currentHook.toString() + " failed.", e); + + if (currentHook.ordinal() > 0) { + // Choose the previous player hook type + currentHook = PlayerInjectHooks.values()[currentHook.ordinal() - 1]; + logger.log(Level.INFO, "Switching to " + currentHook.toString() + " instead."); + } else { + // UTTER FAILURE + playerInjection.put(player, null); + return; + } + } } + + // Update values + if (injector != null) + lastSuccessfulHook = injector; + if (currentHook != playerHook || firstPlayer) + setPlayerHook(currentHook); } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index 612e6e70..9bacc311 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -31,6 +31,7 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; @@ -205,10 +206,12 @@ abstract class PlayerInjector { public abstract void cleanupAll(); /** - * Determine if we actually can inject. - * @return TRUE if this injector is compatible with the current CraftBukkit version, FALSE otherwise. + * Invoked before a new listener is registered. + *

+ * The player injector should throw an exception if this listener cannot be properly supplied with packet events. + * @param listener - the listener that is about to be registered. */ - public abstract boolean canInject(); + public abstract void checkListener(PacketListener listener); /** * Allows a packet to be recieved by the listeners. From 240df9dc7a48bcbbd2db831c080c5bb4b2da0a4b Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 27 Sep 2012 03:24:34 +0200 Subject: [PATCH 12/50] Complicated feature - auto-compilation of structure modifier. Using ASM we can automatically generate a faster structure modifier that doesn't use reflection to read or write public fields. Note that because the compilation itself is a bit slow (10 ms++), we have to create a background compilation thread. Future work: * Disable the thread if it's idle after 60 seconds. * Don't recreate the thread when the server reloads. --- .../comphenix/protocol/ProtocolLibrary.java | 17 + .../protocol/compiler/BackgroundCompiler.java | 162 +++++++ .../protocol/compiler/BoxingHelper.java | 275 +++++++++++ .../compiler/CompiledStructureModifier.java | 45 ++ .../protocol/compiler/MethodDescriptor.java | 220 +++++++++ .../protocol/compiler/StructureCompiler.java | 432 ++++++++++++++++++ .../protocol/reflect/StructureModifier.java | 54 ++- 7 files changed, 1189 insertions(+), 16 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/compiler/BackgroundCompiler.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/compiler/BoxingHelper.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/compiler/CompiledStructureModifier.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/compiler/MethodDescriptor.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/compiler/StructureCompiler.java diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index 7abc4ff1..39075681 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -25,6 +25,7 @@ import org.bukkit.Server; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import com.comphenix.protocol.compiler.BackgroundCompiler; import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.metrics.Statistics; @@ -39,6 +40,9 @@ public class ProtocolLibrary extends JavaPlugin { // Metrics and statistisc private Statistics statistisc; + // Structure compiler + private BackgroundCompiler backgroundCompiler; + @Override public void onLoad() { logger = getLoggerSafely(); @@ -50,6 +54,12 @@ public class ProtocolLibrary extends JavaPlugin { Server server = getServer(); PluginManager manager = server.getPluginManager(); + // Initialize background compiler + if (backgroundCompiler == null) { + backgroundCompiler = new BackgroundCompiler(getClassLoader()); + BackgroundCompiler.setInstance(backgroundCompiler); + } + // Notify server managers of incompatible plugins checkForIncompatibility(manager); @@ -83,6 +93,13 @@ public class ProtocolLibrary extends JavaPlugin { @Override public void onDisable() { + // Disable compiler + if (backgroundCompiler != null) { + backgroundCompiler.shutdownAll(); + backgroundCompiler = null; + BackgroundCompiler.setInstance(null); + } + protocolManager.close(); protocolManager = null; statistisc = null; diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/BackgroundCompiler.java b/ProtocolLib/src/com/comphenix/protocol/compiler/BackgroundCompiler.java new file mode 100644 index 00000000..313593ab --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/compiler/BackgroundCompiler.java @@ -0,0 +1,162 @@ +package com.comphenix.protocol.compiler; + +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.comphenix.protocol.reflect.StructureModifier; + +/** + * Compiles structure modifiers on a background thread. + *

+ * This is necessary as we cannot block the main thread. + * + * @author Kristian + */ +public class BackgroundCompiler { + + // How long to wait for a shutdown + public static final int SHUTDOWN_DELAY_MS = 2000; + + // The single background compiler we're using + private static BackgroundCompiler backgroundCompiler; + + private StructureCompiler compiler; + private boolean enabled; + private boolean shuttingDown; + + private ExecutorService executor; + + /** + * Retrieves the current background compiler. + * @return Current background compiler. + */ + public static BackgroundCompiler getInstance() { + return backgroundCompiler; + } + + /** + * Sets the single background compiler we're using. + * @param backgroundCompiler - current background compiler, or NULL if the library is not loaded. + */ + public static void setInstance(BackgroundCompiler backgroundCompiler) { + BackgroundCompiler.backgroundCompiler = backgroundCompiler; + } + + /** + * Initialize a background compiler. + * @param loader - class loader from Bukkit. + */ + public BackgroundCompiler(ClassLoader loader) { + this(loader, Executors.newSingleThreadExecutor()); + } + + /** + * Initialize a background compiler utilizing the given thread pool. + * @param loader - class loader from Bukkit. + * @param executor - thread pool we'll use. + */ + public BackgroundCompiler(ClassLoader loader, ExecutorService executor) { + if (loader == null) + throw new IllegalArgumentException("loader cannot be NULL"); + if (executor == null) + throw new IllegalArgumentException("executor cannot be NULL"); + + this.compiler = new StructureCompiler(loader); + this.executor = executor; + this.enabled = true; + } + + /** + * Ensure that the indirectly given structure modifier is eventually compiled. + * @param cache - store of structure modifiers. + * @param key - key of the structure modifier to compile. + */ + @SuppressWarnings("rawtypes") + public void scheduleCompilation(final Map cache, final Class key) { + + // Only schedule if we're enabled + if (enabled && !shuttingDown) { + + // Don't try to schedule anything + if (executor == null || executor.isShutdown()) + return; + + try { + executor.submit(new Callable() { + @Override + public Object call() throws Exception { + + StructureModifier modifier = cache.get(key); + + // Update the cache! + modifier = compiler.compile(modifier); + cache.put(key, modifier); + + // We'll also return the new structure modifier + return modifier; + } + }); + } catch (RejectedExecutionException e) { + // Occures when the underlying queue is overflowing. Since the compilation + // is only an optmization and not really essential we'll just log this failure + // and move on. + Logger.getLogger("Minecraft").log(Level.WARNING, "Unable to schedule compilation task.", e); + } + } + } + + /** + * Clean up after ourselves using the default timeout. + */ + public void shutdownAll() { + shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS); + } + + /** + * Clean up after ourselves. + * @param timeout - the maximum time to wait. + * @param unit - the time unit of the timeout argument. + */ + public void shutdownAll(long timeout, TimeUnit unit) { + setEnabled(false); + shuttingDown = true; + executor.shutdown(); + + try { + executor.awaitTermination(timeout, unit); + } catch (InterruptedException e) { + // Unlikely to ever occur. + e.printStackTrace(); + } + } + + /** + * Retrieve whether or not the background compiler is enabled. + * @return TRUE if it is enabled, FALSE otherwise. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Sets whether or not the background compiler is enabled. + * @param enabled - TRUE to enable it, FALSE otherwise. + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * Retrieve the current structure compiler. + * @return Current structure compiler. + */ + public StructureCompiler getCompiler() { + return compiler; + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/BoxingHelper.java b/ProtocolLib/src/com/comphenix/protocol/compiler/BoxingHelper.java new file mode 100644 index 00000000..3692381e --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/compiler/BoxingHelper.java @@ -0,0 +1,275 @@ +package com.comphenix.protocol.compiler; + +import net.sf.cglib.asm.*; + +/** + * Used by the compiler to automatically box and unbox values. + */ +class BoxingHelper { + + private final static Type BYTE_TYPE = Type.getObjectType("java/lang/Byte"); + private final static Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean"); + private final static Type SHORT_TYPE = Type.getObjectType("java/lang/Short"); + private final static Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character"); + private final static Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer"); + private final static Type FLOAT_TYPE = Type.getObjectType("java/lang/Float"); + private final static Type LONG_TYPE = Type.getObjectType("java/lang/Long"); + private final static Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double"); + private final static Type NUMBER_TYPE = Type.getObjectType("java/lang/Number"); + private final static Type OBJECT_TYPE = Type.getObjectType("java/lang/Object"); + + private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()"); + private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()"); + private final static MethodDescriptor INT_VALUE = MethodDescriptor.getMethod("int intValue()"); + private final static MethodDescriptor FLOAT_VALUE = MethodDescriptor.getMethod("float floatValue()"); + private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()"); + private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()"); + + private MethodVisitor mv; + + public BoxingHelper(MethodVisitor mv) { + this.mv = mv; + } + + /** + * Generates the instructions to box the top stack value. This value is + * replaced by its boxed equivalent on top of the stack. + * + * @param type the type of the top stack value. + */ + public void box(final Type type){ + if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { + return; + } + + if(type == Type.VOID_TYPE) { + push((String) null); + } else { + Type boxed = type; + + switch(type.getSort()) { + case Type.BYTE: + boxed = BYTE_TYPE; + break; + case Type.BOOLEAN: + boxed = BOOLEAN_TYPE; + break; + case Type.SHORT: + boxed = SHORT_TYPE; + break; + case Type.CHAR: + boxed = CHARACTER_TYPE; + break; + case Type.INT: + boxed = INTEGER_TYPE; + break; + case Type.FLOAT: + boxed = FLOAT_TYPE; + break; + case Type.LONG: + boxed = LONG_TYPE; + break; + case Type.DOUBLE: + boxed = DOUBLE_TYPE; + break; + } + + newInstance(boxed); + if(type.getSize() == 2) { + // Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o + dupX2(); + dupX2(); + pop(); + } else { + // p -> po -> opo -> oop -> o + dupX1(); + swap(); + } + + invokeConstructor(boxed, new MethodDescriptor("", Type.VOID_TYPE, new Type[] {type})); + } + } + + /** + * Generates the instruction to invoke a constructor. + * + * @param type the class in which the constructor is defined. + * @param method the constructor to be invoked. + */ + public void invokeConstructor(final Type type, final MethodDescriptor method){ + invokeInsn(Opcodes.INVOKESPECIAL, type, method); + } + + /** + * Generates a DUP_X1 instruction. + */ + public void dupX1(){ + mv.visitInsn(Opcodes.DUP_X1); + } + + /** + * Generates a DUP_X2 instruction. + */ + public void dupX2(){ + mv.visitInsn(Opcodes.DUP_X2); + } + + /** + * Generates a POP instruction. + */ + public void pop(){ + mv.visitInsn(Opcodes.POP); + } + + /** + * Generates a SWAP instruction. + */ + public void swap(){ + mv.visitInsn(Opcodes.SWAP); + } + + /** + * Generates the instruction to push the given value on the stack. + * + * @param value the value to be pushed on the stack. + */ + public void push(final boolean value){ + push(value ? 1 : 0); + } + + /** + * Generates the instruction to push the given value on the stack. + * + * @param value the value to be pushed on the stack. + */ + public void push(final int value) { + if (value >= -1 && value <= 5) { + mv.visitInsn(Opcodes.ICONST_0 + value); + } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { + mv.visitIntInsn(Opcodes.BIPUSH, value); + } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { + mv.visitIntInsn(Opcodes.SIPUSH, value); + } else { + mv.visitLdcInsn(new Integer(value)); + } + } + + /** + * Generates the instruction to create a new object. + * + * @param type the class of the object to be created. + */ + public void newInstance(final Type type){ + typeInsn(Opcodes.NEW, type); + } + + /** + * Generates the instruction to push the given value on the stack. + * + * @param value the value to be pushed on the stack. May be null. + */ + public void push(final String value) { + if (value == null) { + mv.visitInsn(Opcodes.ACONST_NULL); + } else { + mv.visitLdcInsn(value); + } + } + + /** + * Generates the instructions to unbox the top stack value. This value is + * replaced by its unboxed equivalent on top of the stack. + * + * @param type + * the type of the top stack value. + */ + public void unbox(final Type type){ + Type t = NUMBER_TYPE; + MethodDescriptor sig = null; + + switch(type.getSort()) { + case Type.VOID: + return; + case Type.CHAR: + t = CHARACTER_TYPE; + sig = CHAR_VALUE; + break; + case Type.BOOLEAN: + t = BOOLEAN_TYPE; + sig = BOOLEAN_VALUE; + break; + case Type.DOUBLE: + sig = DOUBLE_VALUE; + break; + case Type.FLOAT: + sig = FLOAT_VALUE; + break; + case Type.LONG: + sig = LONG_VALUE; + break; + case Type.INT: + case Type.SHORT: + case Type.BYTE: + sig = INT_VALUE; + } + + if(sig == null) { + checkCast(type); + } else { + checkCast(t); + invokeVirtual(t, sig); + } + } + + /** + * Generates the instruction to check that the top stack value is of the + * given type. + * + * @param type a class or interface type. + */ + public void checkCast(final Type type){ + if(!type.equals(OBJECT_TYPE)) { + typeInsn(Opcodes.CHECKCAST, type); + } + } + + /** + * Generates the instruction to invoke a normal method. + * + * @param owner the class in which the method is defined. + * @param method the method to be invoked. + */ + public void invokeVirtual(final Type owner, final MethodDescriptor method){ + invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method); + } + + /** + * Generates an invoke method instruction. + * + * @param opcode the instruction's opcode. + * @param type the class in which the method is defined. + * @param method the method to be invoked. + */ + private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){ + String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName(); + mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor()); + } + + /** + * Generates a type dependent instruction. + * + * @param opcode the instruction's opcode. + * @param type the instruction's operand. + */ + private void typeInsn(final int opcode, final Type type){ + String desc; + + if(type.getSort() == Type.ARRAY) { + desc = type.getDescriptor(); + } else { + desc = type.getInternalName(); + } + + mv.visitTypeInsn(opcode, desc); + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/CompiledStructureModifier.java b/ProtocolLib/src/com/comphenix/protocol/compiler/CompiledStructureModifier.java new file mode 100644 index 00000000..570a06b6 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/compiler/CompiledStructureModifier.java @@ -0,0 +1,45 @@ +package com.comphenix.protocol.compiler; + +import java.lang.reflect.Field; +import java.util.Map; + +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.instances.DefaultInstances; + +/** + * Represents a compiled structure modifier. + * + * @author Kristian + * @param Field type. + */ +public class CompiledStructureModifier extends StructureModifier { + // Used to compile instances of structure modifiers + protected StructureCompiler compiler; + + // Speed up the default writer + @SuppressWarnings("unchecked") + @Override + public StructureModifier writeDefaults() throws FieldAccessException { + + DefaultInstances generator = DefaultInstances.DEFAULT; + + // Write a default instance to every field + for (Map.Entry entry : defaultFields.entrySet()) { + Integer index = entry.getValue(); + Field field = entry.getKey(); + + write(index, (TField) generator.getDefault(field.getType())); + } + + return this; + } + + @Override + public StructureModifier withTarget(Object target) { + if (compiler != null) + return compiler.compile(super.withTarget(target)); + else + return super.withTarget(target); + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/MethodDescriptor.java b/ProtocolLib/src/com/comphenix/protocol/compiler/MethodDescriptor.java new file mode 100644 index 00000000..4f5e8c5c --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/compiler/MethodDescriptor.java @@ -0,0 +1,220 @@ +package com.comphenix.protocol.compiler; + +import java.util.HashMap; +import java.util.Map; + +import net.sf.cglib.asm.Type; + +/** + * Represents a method. + */ +class MethodDescriptor { + + /** + * The method name. + */ + private final String name; + + /** + * The method descriptor. + */ + private final String desc; + + /** + * Maps primitive Java type names to their descriptors. + */ + private static final Map DESCRIPTORS; + + static { + DESCRIPTORS = new HashMap(); + DESCRIPTORS.put("void", "V"); + DESCRIPTORS.put("byte", "B"); + DESCRIPTORS.put("char", "C"); + DESCRIPTORS.put("double", "D"); + DESCRIPTORS.put("float", "F"); + DESCRIPTORS.put("int", "I"); + DESCRIPTORS.put("long", "J"); + DESCRIPTORS.put("short", "S"); + DESCRIPTORS.put("boolean", "Z"); + } + + /** + * Creates a new {@link Method}. + * + * @param name the method's name. + * @param desc the method's descriptor. + */ + public MethodDescriptor(final String name, final String desc) { + this.name = name; + this.desc = desc; + } + + /** + * Creates a new {@link Method}. + * + * @param name the method's name. + * @param returnType the method's return type. + * @param argumentTypes the method's argument types. + */ + public MethodDescriptor( + final String name, + final Type returnType, + final Type[] argumentTypes) + { + this(name, Type.getMethodDescriptor(returnType, argumentTypes)); + } + + /** + * Returns a {@link Method} corresponding to the given Java method + * declaration. + * + * @param method a Java method declaration, without argument names, of the + * form "returnType name (argumentType1, ... argumentTypeN)", where + * the types are in plain Java (e.g. "int", "float", + * "java.util.List", ...). Classes of the java.lang package can be + * specified by their unqualified name; all other classes names must + * be fully qualified. + * @return a {@link Method} corresponding to the given Java method + * declaration. + * @throws IllegalArgumentException if method could not get + * parsed. + */ + public static MethodDescriptor getMethod(final String method) + throws IllegalArgumentException + { + return getMethod(method, false); + } + + /** + * Returns a {@link Method} corresponding to the given Java method + * declaration. + * + * @param method a Java method declaration, without argument names, of the + * form "returnType name (argumentType1, ... argumentTypeN)", where + * the types are in plain Java (e.g. "int", "float", + * "java.util.List", ...). Classes of the java.lang package may be + * specified by their unqualified name, depending on the + * defaultPackage argument; all other classes names must be fully + * qualified. + * @param defaultPackage true if unqualified class names belong to the + * default package, or false if they correspond to java.lang classes. + * For instance "Object" means "Object" if this option is true, or + * "java.lang.Object" otherwise. + * @return a {@link Method} corresponding to the given Java method + * declaration. + * @throws IllegalArgumentException if method could not get + * parsed. + */ + public static MethodDescriptor getMethod( + final String method, + final boolean defaultPackage) throws IllegalArgumentException + { + int space = method.indexOf(' '); + int start = method.indexOf('(', space) + 1; + int end = method.indexOf(')', start); + if (space == -1 || start == -1 || end == -1) { + throw new IllegalArgumentException(); + } + String returnType = method.substring(0, space); + String methodName = method.substring(space + 1, start - 1).trim(); + StringBuffer sb = new StringBuffer(); + sb.append('('); + int p; + do { + String s; + p = method.indexOf(',', start); + if (p == -1) { + s = map(method.substring(start, end).trim(), defaultPackage); + } else { + s = map(method.substring(start, p).trim(), defaultPackage); + start = p + 1; + } + sb.append(s); + } while (p != -1); + sb.append(')'); + sb.append(map(returnType, defaultPackage)); + return new MethodDescriptor(methodName, sb.toString()); + } + + private static String map(final String type, final boolean defaultPackage) { + if ("".equals(type)) { + return type; + } + + StringBuffer sb = new StringBuffer(); + int index = 0; + while ((index = type.indexOf("[]", index) + 1) > 0) { + sb.append('['); + } + + String t = type.substring(0, type.length() - sb.length() * 2); + String desc = (String) DESCRIPTORS.get(t); + if (desc != null) { + sb.append(desc); + } else { + sb.append('L'); + if (t.indexOf('.') < 0) { + if (!defaultPackage) { + sb.append("java/lang/"); + } + sb.append(t); + } else { + sb.append(t.replace('.', '/')); + } + sb.append(';'); + } + return sb.toString(); + } + + /** + * Returns the name of the method described by this object. + * + * @return the name of the method described by this object. + */ + public String getName() { + return name; + } + + /** + * Returns the descriptor of the method described by this object. + * + * @return the descriptor of the method described by this object. + */ + public String getDescriptor() { + return desc; + } + + /** + * Returns the return type of the method described by this object. + * + * @return the return type of the method described by this object. + */ + public Type getReturnType() { + return Type.getReturnType(desc); + } + + /** + * Returns the argument types of the method described by this object. + * + * @return the argument types of the method described by this object. + */ + public Type[] getArgumentTypes() { + return Type.getArgumentTypes(desc); + } + + public String toString() { + return name + desc; + } + + public boolean equals(final Object o) { + if (!(o instanceof MethodDescriptor)) { + return false; + } + MethodDescriptor other = (MethodDescriptor) o; + return name.equals(other.name) && desc.equals(other.desc); + } + + public int hashCode() { + return name.hashCode() ^ desc.hashCode(); + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/StructureCompiler.java b/ProtocolLib/src/com/comphenix/protocol/compiler/StructureCompiler.java new file mode 100644 index 00000000..f79a949b --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/compiler/StructureCompiler.java @@ -0,0 +1,432 @@ +package com.comphenix.protocol.compiler; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.comphenix.protocol.reflect.PrimitiveUtils; +import com.comphenix.protocol.reflect.StructureModifier; +import com.google.common.base.Objects; + +import net.sf.cglib.asm.*; + +// This class will automatically generate the following type of structure modifier: +// +// public class CompiledStructure$Packet20NamedEntitySpawnObject extends CompiledStructureModifier { +// +// private Packet20NamedEntitySpawn typedTarget; +// +// public CompiledStructure$Packet20NamedEntitySpawnObject(StructureModifier other, StructureCompiler compiler) { +// initialize(other); +// this.typedTarget = (Packet20NamedEntitySpawn) other.getTarget(); +// this.compiler = compiler; +// } +// +// @SuppressWarnings("unchecked") +// @Override +// public TField read(int fieldIndex) throws FieldAccessException { +// +// Packet20NamedEntitySpawn target = typedTarget; +// +// switch (fieldIndex) { +// case 0: return (TField) (Object) target.a; +// case 1: return (TField) (Object) target.b; +// case 2: return (TField) (Object) target.c; +// case 3: return (TField) (Object) target.d; +// case 4: return (TField) (Object) target.e; +// case 5: return (TField) (Object) target.f; +// case 6: return (TField) (Object) target.g; +// case 7: return (TField) (Object) target.h; +// default: +// throw new IllegalArgumentException("Invalid index " + fieldIndex); +// } +// } +// +// @Override +// public StructureModifier write(int index, Object value) { +// +// Packet20NamedEntitySpawn target = typedTarget; +// +// switch (index) { +// case 0: target.a = (Integer) value; break; +// case 1: target.b = (String) value; break; +// case 2: target.c = (Integer) value; break; +// case 3: target.d = (Integer) value; break; +// case 4: target.e = (Integer) value; break; +// case 5: target.f = (Byte) value; break; +// case 6: target.g = (Byte) value; break; +// case 7: target.h = (Integer) value; break; +// default: +// throw new IllegalArgumentException("Invalid index " + index); +// } +// +// // Chaining +// return this; +// } +// } + +/** + * Represents a StructureModifier compiler. + * + * @author Kristian + */ +public final class StructureCompiler { + + // Used to store generated classes of different types + @SuppressWarnings("rawtypes") + private class StructureKey { + private Class targetType; + private Class fieldType; + + public StructureKey(Class targetType, Class fieldType) { + this.targetType = targetType; + this.fieldType = fieldType; + } + + @Override + public int hashCode() { + return Objects.hashCode(targetType, fieldType); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof StructureKey) { + StructureKey other = (StructureKey) obj; + return Objects.equal(targetType, other.targetType) && + Objects.equal(fieldType, other.fieldType); + } + return false; + } + } + + // Used to load classes + private static Method defineMethod; + + @SuppressWarnings("rawtypes") + private Map compiledCache = new HashMap(); + + // The class loader we'll store our classes + private ClassLoader loader; + + // References to other classes + private static String PACKAGE_NAME = "com/comphenix/protocol/compiler"; + private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier"; + private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier"; + + /** + * Construct a structure compiler. + * @param loader - main class loader. + */ + StructureCompiler(ClassLoader loader) { + this.loader = loader; + } + + /** + * Compiles the given structure modifier. + *

+ * WARNING: Do NOT call this method in the main thread. Compiling may easily take 10 ms, which is already + * over 1/4 of a tick (50 ms). Let the background thread automatically compile the structure modifiers instead. + * @param source - structure modifier to compile. + * @return A compiled structure modifier. + */ + @SuppressWarnings("unchecked") + public synchronized StructureModifier compile(StructureModifier source) { + + // We cannot optimize a structure modifier with no public fields + if (!isAnyPublic(source.getFields())) { + return source; + } + + StructureKey key = new StructureKey(source.getTargetType(), source.getFieldType()); + Class compiledClass = compiledCache.get(key); + + if (!compiledCache.containsKey(key)) { + compiledClass = generateClass(source); + compiledCache.put(key, compiledClass); + } + + // Next, create an instance of this class + try { + return (StructureModifier) compiledClass.getConstructor( + StructureModifier.class, StructureCompiler.class). + newInstance(source, this); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("Used invalid parameters in instance creation", e); + } catch (SecurityException e) { + throw new RuntimeException("Security limitation!", e); + } catch (InstantiationException e) { + throw new RuntimeException("Error occured while instancing generated class.", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Security limitation! Cannot create instance of dynamic class.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error occured while instancing generated class.", e); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Cannot happen.", e); + } + } + + private Class generateClass(StructureModifier source) { + + ClassWriter cw = new ClassWriter(0); + + @SuppressWarnings("rawtypes") + Class targetType = source.getTargetType(); + + String className = "CompiledStructure$" + targetType.getSimpleName() + source.getFieldType().getSimpleName(); + String targetSignature = Type.getDescriptor(targetType); + String targetName = targetType.getName().replace('.', '/'); + + cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className, + "L" + COMPILED_CLASS + ";", + COMPILED_CLASS, null); + + createFields(cw, targetSignature); + createConstructor(cw, className, targetSignature, targetName); + createReadMethod(cw, className, source.getFields(), targetSignature, targetName); + createWriteMethod(cw, className, source.getFields(), targetSignature, targetName); + cw.visitEnd(); + + byte[] data = cw.toByteArray(); + + // Call the define method + try { + if (defineMethod == null) { + defineMethod = ClassLoader.class.getDeclaredMethod("defineClass", + new Class[] { String.class, byte[].class, int.class, int.class }); + + // Awesome. Now, create and return it. + defineMethod.setAccessible(true); + } + + @SuppressWarnings("rawtypes") + Class clazz = (Class) defineMethod.invoke(loader, null, data, 0, data.length); + + // DEBUG CODE: Print the content of the generated class. + //org.objectweb.asm.ClassReader cr = new org.objectweb.asm.ClassReader(data); + //cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0); + + return clazz; + + } catch (SecurityException e) { + throw new RuntimeException("Cannot use reflection to dynamically load a class.", e); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Incompatible JVM.", e); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("Cannot call defineMethod - wrong JVM?", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Security limitation! Cannot dynamically load class.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error occured in code generator.", e); + } + } + + /** + * Determine if at least one of the given fields is public. + * @param fields - field to test. + * @return TRUE if one or more field is publically accessible, FALSE otherwise. + */ + private boolean isAnyPublic(List fields) { + // Are any of the fields public? + for (int i = 0; i < fields.size(); i++) { + if (isPublic(fields.get(i))) { + return true; + } + } + + return false; + } + + private boolean isPublic(Field field) { + return Modifier.isPublic(field.getModifiers()); + } + + private void createFields(ClassWriter cw, String targetSignature) { + FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null); + typedField.visitEnd(); + } + + private void createWriteMethod(ClassWriter cw, String className, List fields, String targetSignature, String targetName) { + + String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"; + String methodSignature = "(ITTField;)L" + SUPER_CLASS + ";"; + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, "write", methodDescriptor, methodSignature, + new String[] { "com/comphenix/protocol/reflect/FieldAccessException" }); + BoxingHelper boxingHelper = new BoxingHelper(mv); + + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature); + mv.visitVarInsn(Opcodes.ASTORE, 3); + mv.visitVarInsn(Opcodes.ILOAD, 1); + + // The last label is for the default switch + Label[] labels = new Label[fields.size()]; + Label errorLabel = new Label(); + Label returnLabel = new Label(); + + // Generate labels + for (int i = 0; i < fields.size(); i++) { + labels[i] = new Label(); + } + + mv.visitTableSwitchInsn(0, labels.length - 1, errorLabel, labels); + + for (int i = 0; i < fields.size(); i++) { + + Class outputType = fields.get(i).getType(); + Class inputType = PrimitiveUtils.wrap(outputType); + String typeDescriptor = Type.getDescriptor(outputType); + String inputPath = inputType.getName().replace('.', '/'); + + mv.visitLabel(labels[i]); + + // Push the compare object + if (i == 0) + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); + else + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + + // Only write to public fields + if (isPublic(fields.get(i))) { + mv.visitVarInsn(Opcodes.ALOAD, 3); + mv.visitVarInsn(Opcodes.ALOAD, 2); + + if (!PrimitiveUtils.isPrimitive(outputType)) + mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath); + else + boxingHelper.unbox(Type.getType(outputType)); + + mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).getName(), typeDescriptor); + + } else { + // Use reflection. We don't have a choice, unfortunately. + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "write", "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"); + mv.visitInsn(Opcodes.POP); + } + + mv.visitJumpInsn(Opcodes.GOTO, returnLabel); + } + + mv.visitLabel(errorLabel); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalArgumentException"); + mv.visitInsn(Opcodes.DUP); + mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); + mv.visitInsn(Opcodes.DUP); + mv.visitLdcInsn("Invalid index "); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V"); + mv.visitInsn(Opcodes.ATHROW); + + mv.visitLabel(returnLabel); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(5, 4); + mv.visitEnd(); + } + + private void createReadMethod(ClassWriter cw, String className, List fields, String targetSignature, String targetName) { + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, "read", "(I)Ljava/lang/Object;", "(I)TTField;", + new String[] { "com/comphenix/protocol/reflect/FieldAccessException" }); + BoxingHelper boxingHelper = new BoxingHelper(mv); + + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature); + mv.visitVarInsn(Opcodes.ASTORE, 2); + mv.visitVarInsn(Opcodes.ILOAD, 1); + + // The last label is for the default switch + Label[] labels = new Label[fields.size()]; + Label errorLabel = new Label(); + + // Generate labels + for (int i = 0; i < fields.size(); i++) { + labels[i] = new Label(); + } + + mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels); + + for (int i = 0; i < fields.size(); i++) { + Class outputType = fields.get(i).getType(); + String typeDescriptor = Type.getDescriptor(outputType); + + mv.visitLabel(labels[i]); + + // Push the compare object + if (i == 0) + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); + else + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + + // Note that byte code cannot access non-public fields + if (isPublic(fields.get(i))) { + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitFieldInsn(Opcodes.GETFIELD, targetName, fields.get(i).getName(), typeDescriptor); + + boxingHelper.box(Type.getType(outputType)); + } else { + // We have to use reflection for private and protected fields. + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "read", "(I)Ljava/lang/Object;"); + } + + mv.visitInsn(Opcodes.ARETURN); + } + + mv.visitLabel(errorLabel); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalArgumentException"); + mv.visitInsn(Opcodes.DUP); + mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); + mv.visitInsn(Opcodes.DUP); + mv.visitLdcInsn("Invalid index "); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V"); + mv.visitInsn(Opcodes.ATHROW); + mv.visitMaxs(5, 3); + mv.visitEnd(); + } + + private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) { + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", + "(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V", + "(L" + SUPER_CLASS + ";L" + SUPER_CLASS + ";)V", null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "", "()V"); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, PACKAGE_NAME + "/" + className, "initialize", "(L" + SUPER_CLASS + ";)V"); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;"); + mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;"); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;"); + mv.visitTypeInsn(Opcodes.CHECKCAST, targetName); + mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;"); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(2, 3); + mv.visitEnd(); + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java index aecd71bd..d79733b2 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java @@ -21,15 +21,23 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; 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 com.comphenix.protocol.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +/** + * Provides list-oriented access to the fields of a Minecraft packet. + *

+ * Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential. + * + * @author Kristian + * @param Type of the fields to retrieve. + */ @SuppressWarnings("rawtypes") public class StructureModifier { @@ -45,7 +53,7 @@ public class StructureModifier { protected List data = new ArrayList(); // Improved default values - protected Set defaultFields; + protected Map defaultFields; // Cache of previous types protected Map subtypeCache; @@ -58,9 +66,9 @@ public class StructureModifier { */ public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) { List fields = getFields(targetType, superclassExclude); - Set defaults = requireDefault ? generateDefaultFields(fields) : new HashSet(); + Map defaults = requireDefault ? generateDefaultFields(fields) : new HashMap(); - initialize(targetType, Object.class, fields, defaults, null, new HashMap()); + initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap()); } /** @@ -89,7 +97,7 @@ public class StructureModifier { * @param subTypeCache - a structure modifier cache. */ protected void initialize(Class targetType, Class fieldType, - List data, Set defaultFields, + List data, Map defaultFields, EquivalentConverter converter, Map subTypeCache) { this.targetType = targetType; this.fieldType = fieldType; @@ -213,7 +221,7 @@ public class StructureModifier { DefaultInstances generator = DefaultInstances.DEFAULT; // Write a default instance to every field - for (Field field : defaultFields) { + for (Field field : defaultFields.keySet()) { try { FieldUtils.writeField(field, target, generator.getDefault(field.getType()), true); @@ -239,22 +247,32 @@ public class StructureModifier { // Do we need to update the cache? if (result == null) { List filtered = new ArrayList(); - Set defaults = new HashSet(); + Map defaults = new HashMap(); + int index = 0; for (Field field : data) { if (fieldType != null && fieldType.isAssignableFrom(field.getType())) { filtered.add(field); - if (defaultFields.contains(field)) - defaults.add(field); + // Don't use the original index + if (defaultFields.containsKey(field)) + defaults.put(field, index); } + + // Keep track of the field index + index++; } // Cache structure modifiers result = withFieldType(fieldType, filtered, defaults, converter); - if (fieldType != null) + if (fieldType != null) { subtypeCache.put(fieldType, result); + + // Automatically compile the structure modifier + if (BackgroundCompiler.getInstance() != null) + BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType); + } } // Add the target too @@ -320,11 +338,11 @@ public class StructureModifier { */ protected StructureModifier withFieldType( Class fieldType, List filtered, - Set defaults, EquivalentConverter converter) { + Map defaults, EquivalentConverter converter) { StructureModifier result = new StructureModifier(); result.initialize(targetType, fieldType, filtered, defaults, - converter, new HashMap()); + converter, new ConcurrentHashMap()); return result; } @@ -387,10 +405,11 @@ public class StructureModifier { } // Used to generate plausible default values - private static Set generateDefaultFields(List fields) { + private static Map generateDefaultFields(List fields) { - Set requireDefaults = new HashSet(); + Map requireDefaults = new HashMap(); DefaultInstances generator = DefaultInstances.DEFAULT; + int index = 0; for (Field field : fields) { Class type = field.getType(); @@ -400,9 +419,12 @@ public class StructureModifier { // Next, see if we actually can generate a default value if (generator.getDefault(type) != null) { // If so, require it - requireDefaults.add(field); + requireDefaults.put(field, index); } } + + // Increment field index + index++; } return requireDefaults; From eb8abd4635b58db398b43c43b6c88d64c84232be Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 27 Sep 2012 05:22:08 +0200 Subject: [PATCH 13/50] Try to clean up after a failed hook. --- .../comphenix/protocol/injector/PacketFilterManager.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 3d5d94b3..f86723f3 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -433,9 +433,18 @@ public final class PacketFilterManager implements ProtocolManager { break; } catch (Exception e) { + // Mark this injection attempt as a failure logger.log(Level.SEVERE, "Player hook " + currentHook.toString() + " failed.", e); + // Clean up as much as possible + try { + if (injector != null) + injector.cleanupAll(); + } catch (Exception e2) { + logger.log(Level.WARNING, "Cleaing up after player hook failed.", e); + } + if (currentHook.ordinal() > 0) { // Choose the previous player hook type currentHook = PlayerInjectHooks.values()[currentHook.ordinal() - 1]; From 8293c71a4c88779e5412afa5ff29b8ff0b7a3fca Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 27 Sep 2012 05:26:02 +0200 Subject: [PATCH 14/50] Make sure the packet IDs are actually valid. --- .../protocol/injector/PacketFilterManager.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index f86723f3..05fcee54 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -180,10 +180,12 @@ public final class PacketFilterManager implements ProtocolManager { if (hasSending || hasReceiving) { // Add listeners and hooks if (hasSending) { + verifyWhitelist(listener, sending); sendingListeners.addListener(listener, sending); enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist()); } if (hasReceiving) { + verifyWhitelist(listener, receiving); recievedListeners.addListener(listener, receiving); enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); @@ -196,6 +198,20 @@ public final class PacketFilterManager implements ProtocolManager { } } + /** + * Determine if the packet IDs in a whitelist is valid. + * @param whitelist - whitelist of packet IDs. + */ + private void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) { + for (Integer id : whitelist.getWhitelist()) { + if (id >= 256 || id < 0) { + throw new IllegalArgumentException(String.format("Invalid packet id %s in listener %s.", + id, PacketAdapter.getPluginName(listener)) + ); + } + } + } + /** * Determine if a listener is valid or not. * @param listener - listener to check. From ea823e58f7c3b5bac590057338b4b0a5f246334b Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 27 Sep 2012 05:28:13 +0200 Subject: [PATCH 15/50] Move structure modifier compiler to the reflect package. --- ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java | 2 +- .../src/com/comphenix/protocol/reflect/StructureModifier.java | 2 +- .../protocol/{ => reflect}/compiler/BackgroundCompiler.java | 2 +- .../comphenix/protocol/{ => reflect}/compiler/BoxingHelper.java | 2 +- .../{ => reflect}/compiler/CompiledStructureModifier.java | 2 +- .../protocol/{ => reflect}/compiler/MethodDescriptor.java | 2 +- .../protocol/{ => reflect}/compiler/StructureCompiler.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) rename ProtocolLib/src/com/comphenix/protocol/{ => reflect}/compiler/BackgroundCompiler.java (95%) rename ProtocolLib/src/com/comphenix/protocol/{ => reflect}/compiler/BoxingHelper.java (95%) rename ProtocolLib/src/com/comphenix/protocol/{ => reflect}/compiler/CompiledStructureModifier.java (92%) rename ProtocolLib/src/com/comphenix/protocol/{ => reflect}/compiler/MethodDescriptor.java (96%) rename ProtocolLib/src/com/comphenix/protocol/{ => reflect}/compiler/StructureCompiler.java (97%) diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index 39075681..a0d3ce00 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -25,9 +25,9 @@ import org.bukkit.Server; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; -import com.comphenix.protocol.compiler.BackgroundCompiler; import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.metrics.Statistics; +import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; public class ProtocolLibrary extends JavaPlugin { diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java index d79733b2..d62ef3cc 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/StructureModifier.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import com.comphenix.protocol.compiler.BackgroundCompiler; +import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/BackgroundCompiler.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java similarity index 95% rename from ProtocolLib/src/com/comphenix/protocol/compiler/BackgroundCompiler.java rename to ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java index 313593ab..7bf702ab 100644 --- a/ProtocolLib/src/com/comphenix/protocol/compiler/BackgroundCompiler.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.compiler; +package com.comphenix.protocol.reflect.compiler; import java.util.Map; import java.util.concurrent.Callable; diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/BoxingHelper.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BoxingHelper.java similarity index 95% rename from ProtocolLib/src/com/comphenix/protocol/compiler/BoxingHelper.java rename to ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BoxingHelper.java index 3692381e..06d043a1 100644 --- a/ProtocolLib/src/com/comphenix/protocol/compiler/BoxingHelper.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BoxingHelper.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.compiler; +package com.comphenix.protocol.reflect.compiler; import net.sf.cglib.asm.*; diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/CompiledStructureModifier.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java similarity index 92% rename from ProtocolLib/src/com/comphenix/protocol/compiler/CompiledStructureModifier.java rename to ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java index 570a06b6..5ddb19f8 100644 --- a/ProtocolLib/src/com/comphenix/protocol/compiler/CompiledStructureModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.compiler; +package com.comphenix.protocol.reflect.compiler; import java.lang.reflect.Field; import java.util.Map; diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/MethodDescriptor.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java similarity index 96% rename from ProtocolLib/src/com/comphenix/protocol/compiler/MethodDescriptor.java rename to ProtocolLib/src/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java index 4f5e8c5c..b1aa1260 100644 --- a/ProtocolLib/src/com/comphenix/protocol/compiler/MethodDescriptor.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.compiler; +package com.comphenix.protocol.reflect.compiler; import java.util.HashMap; import java.util.Map; diff --git a/ProtocolLib/src/com/comphenix/protocol/compiler/StructureCompiler.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java similarity index 97% rename from ProtocolLib/src/com/comphenix/protocol/compiler/StructureCompiler.java rename to ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index f79a949b..c1a2136e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/compiler/StructureCompiler.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -1,4 +1,4 @@ -package com.comphenix.protocol.compiler; +package com.comphenix.protocol.reflect.compiler; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; From 0004fb530d47928acc462c2bfeb8cbb5001db1e3 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 27 Sep 2012 05:28:52 +0200 Subject: [PATCH 16/50] Update the package name in the compiler. --- .../comphenix/protocol/reflect/compiler/StructureCompiler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index c1a2136e..502f4c13 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -113,7 +113,7 @@ public final class StructureCompiler { private ClassLoader loader; // References to other classes - private static String PACKAGE_NAME = "com/comphenix/protocol/compiler"; + private static String PACKAGE_NAME = "com/comphenix/protocol/reflect/compiler"; private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier"; private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier"; From 42303ae0fca3477642c5063badf5475aa0589c57 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 27 Sep 2012 05:42:34 +0200 Subject: [PATCH 17/50] Use the FieldAccessException instead of InvalidArgumentException. Otherwise, the CompiledStructureModifier has a leaky abstraction. --- .../protocol/reflect/compiler/StructureCompiler.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index 502f4c13..2b989f9e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -116,6 +116,7 @@ public final class StructureCompiler { private static String PACKAGE_NAME = "com/comphenix/protocol/reflect/compiler"; private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier"; private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier"; + private static String FIELD_EXCEPTION_CLASS = "com/comphenix/protocol/reflect/FieldAccessException"; /** * Construct a structure compiler. @@ -254,7 +255,7 @@ public final class StructureCompiler { String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"; String methodSignature = "(ITTField;)L" + SUPER_CLASS + ";"; MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, "write", methodDescriptor, methodSignature, - new String[] { "com/comphenix/protocol/reflect/FieldAccessException" }); + new String[] { FIELD_EXCEPTION_CLASS }); BoxingHelper boxingHelper = new BoxingHelper(mv); mv.visitCode(); @@ -316,7 +317,7 @@ public final class StructureCompiler { mv.visitLabel(errorLabel); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalArgumentException"); + mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS); mv.visitInsn(Opcodes.DUP); mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); mv.visitInsn(Opcodes.DUP); @@ -325,7 +326,7 @@ public final class StructureCompiler { mv.visitVarInsn(Opcodes.ILOAD, 1); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V"); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "", "(Ljava/lang/String;)V"); mv.visitInsn(Opcodes.ATHROW); mv.visitLabel(returnLabel); @@ -388,7 +389,7 @@ public final class StructureCompiler { mv.visitLabel(errorLabel); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalArgumentException"); + mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS); mv.visitInsn(Opcodes.DUP); mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); mv.visitInsn(Opcodes.DUP); @@ -397,7 +398,7 @@ public final class StructureCompiler { mv.visitVarInsn(Opcodes.ILOAD, 1); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V"); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "", "(Ljava/lang/String;)V"); mv.visitInsn(Opcodes.ATHROW); mv.visitMaxs(5, 3); mv.visitEnd(); From 20126982750d9ca096adae044feb920034a7ff08 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 27 Sep 2012 06:04:29 +0200 Subject: [PATCH 18/50] Just in case, try to load the generated class first. We might have generated it from a previous run. --- .../reflect/compiler/StructureCompiler.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index 2b989f9e..438aca91 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -42,7 +42,7 @@ import net.sf.cglib.asm.*; // case 6: return (TField) (Object) target.g; // case 7: return (TField) (Object) target.h; // default: -// throw new IllegalArgumentException("Invalid index " + fieldIndex); +// throw new FieldAccessException("Invalid index " + fieldIndex); // } // } // @@ -61,7 +61,7 @@ import net.sf.cglib.asm.*; // case 6: target.g = (Byte) value; break; // case 7: target.h = (Integer) value; break; // default: -// throw new IllegalArgumentException("Invalid index " + index); +// throw new FieldAccessException("Invalid index " + index); // } // // // Chaining @@ -181,6 +181,16 @@ public final class StructureCompiler { String targetSignature = Type.getDescriptor(targetType); String targetName = targetType.getName().replace('.', '/'); + try { + // This class might have been generated before. Try to load it. + Class before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className); + + if (before != null) + return before; + } catch (ClassNotFoundException e) { + // That's ok. + } + cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className, "L" + COMPILED_CLASS + ";", COMPILED_CLASS, null); From 23e676533a749b81a80f64255619e13a51e8c1d5 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 01:00:12 +0200 Subject: [PATCH 19/50] Beginning to add support for asynchronous packet listeners. --- .../protocol/async/AsyncFilterManager.java | 17 ++ .../protocol/async/AsyncListener.java | 5 + .../comphenix/protocol/async/AsyncPacket.java | 123 +++++++++++ .../protocol/async/ListenerToken.java | 38 ++++ .../protocol/async/PacketProcessingQueue.java | 74 +++++++ .../AbstractConcurrentListenerMultimap.java | 94 +++++++++ .../injector/ConcurrentListenerMultimap.java | 194 ------------------ .../injector/PacketFilterManager.java | 4 +- .../injector/PrioritizedListener.java | 59 ++++++ .../injector/SortedPacketListenerList.java | 70 +++++++ 10 files changed, 482 insertions(+), 196 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java delete mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/PrioritizedListener.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java new file mode 100644 index 00000000..dc8e972a --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -0,0 +1,17 @@ +package com.comphenix.protocol.async; + +import java.util.concurrent.Future; + +/** + * Represents a filter manager for asynchronous packets. + * + * @author Kristian + */ +public class AsyncFilterManager { + + + public Future registerAsyncHandler() { + + } + +} diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java new file mode 100644 index 00000000..01ef4aba --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java @@ -0,0 +1,5 @@ +package com.comphenix.protocol.async; + +public interface AsyncListener { + public void onAsyncPacket(AsyncPacket packet); +} diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java new file mode 100644 index 00000000..e14d4073 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java @@ -0,0 +1,123 @@ +package com.comphenix.protocol.async; + +import java.io.Serializable; + +import com.comphenix.protocol.events.PacketEvent; +import com.google.common.primitives.Longs; + +/** + * Represents a packet that is being processed by asynchronous listeners. + * + * @author Kristian + */ +public class AsyncPacket implements Serializable, Comparable { + + /** + * Generated by Eclipse. + */ + private static final long serialVersionUID = -2621498096616187384L; + + /** + * Default number of milliseconds until a packet will rejected. + */ + public static final int DEFAULT_TIMEOUT_DETLA = 60000; + + /** + * The original synchronized packet. + */ + private PacketEvent packetEvent; + + // Timeout handling + private long initialTime; + private long timeout; + + // Packet order + private long originalSendingIndex; + private long newSendingIndex; + + + /** + * Create a container for asyncronous packets. + * @param packetEvent - the synchronous packet event. + * @param initialTime - the current time in milliseconds since 01.01.1970 00:00. + */ + public AsyncPacket(PacketEvent packetEvent, long sendingIndex, long initialTime) { + this.packetEvent = packetEvent; + + // Timeout + this.initialTime = initialTime; + this.timeout = initialTime + DEFAULT_TIMEOUT_DETLA; + + // Sending index + this.originalSendingIndex = sendingIndex; + this.newSendingIndex = sendingIndex; + } + + /** + * Retrieve the time the packet was initially queued for asynchronous processing. + * @return The initial time in number of milliseconds since 01.01.1970 00:00. + */ + public long getInitialTime() { + return initialTime; + } + + /** + * Retrieve the time the packet will be forcefully rejected. + * @return The time to reject the packet, in milliseconds since 01.01.1970 00:00. + */ + public long getTimeout() { + return timeout; + } + + /** + * Sets the time the packet will be forcefully rejected. + * @param timeout - the time to reject the packet, in milliseconds since 01.01.1970 00:00. + */ + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + /** + * Retrieve the order the packet was originally transmitted. + * @return The original packet index. + */ + public long getOriginalSendingIndex() { + return originalSendingIndex; + } + + /** + * Retrieve the desired sending order after processing has completed. + *

+ * Higher sending order means lower priority. + * @return Desired sending order. + */ + public long getNewSendingIndex() { + return newSendingIndex; + } + + /** + * Sets the desired sending order after processing has completed. + *

+ * Higher sending order means lower priority. + * @param newSendingIndex - new packet send index. + */ + public void setNewSendingIndex(long newSendingIndex) { + this.newSendingIndex = newSendingIndex; + } + + /** + * Retrieve the original synchronous packet event. + * @return The original packet event. + */ + public PacketEvent getPacketEvent() { + return packetEvent; + } + + @Override + public int compareTo(AsyncPacket o) { + if (o == null) + return 1; + else + return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex()); + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java new file mode 100644 index 00000000..c3a59b3d --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java @@ -0,0 +1,38 @@ +package com.comphenix.protocol.async; + +import java.util.concurrent.ArrayBlockingQueue; + +public class ListenerToken { + + // Cancel the async handler + private volatile boolean cancelled; + + public boolean isCancelled() { + return cancelled; + } + + /** + * Cancel the handler. + */ + public void cancel() { + cancelled = true; + } + + + public void beginListener(AsyncListener asyncListener) { + + try { + AsyncPacket packet = processingQueue.take(); + + // Now, + asyncListener.onAsyncPacket(packet); + + + } catch (InterruptedException e) { + + } + + + + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java new file mode 100644 index 00000000..620e9b3a --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -0,0 +1,74 @@ +package com.comphenix.protocol.async; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Semaphore; + +import com.comphenix.protocol.concurrency.SortedCopyOnWriteArray; + +/** + * Handles the processing of a certain packet type. + * + * @author Kristian + */ +class PacketProcessingQueue { + + + /** + * Default maximum number of packets to process concurrently. + */ + public static final int DEFAULT_MAXIMUM_CONCURRENCY = 5; + + /** + * Default maximum number of packets to queue for processing. + */ + public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60; + + /** + * Number of packets we're processing concurrently. + */ + private final int maximumConcurrency; + private Semaphore concurrentProcessing; + + // Queued packets for being processed + private ArrayBlockingQueue processingQueue; + + // Packet listeners + private SortedCopyOnWriteArray<> + + public PacketProcessingQueue() { + this(DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY); + } + + public PacketProcessingQueue(int queueLimit, int maximumConcurrency) { + this.processingQueue = new ArrayBlockingQueue(queueLimit); + this.maximumConcurrency = maximumConcurrency; + this.concurrentProcessing = new Semaphore(maximumConcurrency); + } + + public boolean queuePacket(AsyncPacket packet) { + try { + processingQueue.add(packet); + + // Begin processing packets + processPacket(); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + public void processPacket() { + if (concurrentProcessing.tryAcquire()) { + AsyncPacket packet = processingQueue.poll(); + + // Any packet queued? + if (packet != null) { + + } + } + } + + public int getMaximumConcurrency() { + return maximumConcurrency; + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java b/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java new file mode 100644 index 00000000..2fd5a1f3 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java @@ -0,0 +1,94 @@ +package com.comphenix.protocol.concurrency; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.injector.PrioritizedListener; + +/** + * A thread-safe implementation of a listener multimap. + * + * @author Kristian + */ +public abstract class AbstractConcurrentListenerMultimap { + + // 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. + * @param whitelist - the packet whitelist to use. + */ + public void addListener(TListener listener, ListeningWhitelist whitelist) { + + PrioritizedListener prioritized = new PrioritizedListener(listener, whitelist.getPriority()); + + for (Integer packetID : whitelist.getWhitelist()) { + addListener(packetID, prioritized); + } + } + + // Add the listener to a specific packet notifcation list + private void addListener(Integer packetID, PrioritizedListener listener) { + + SortedCopyOnWriteArray> 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 SortedCopyOnWriteArray> value = new SortedCopyOnWriteArray>(); + + 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; + } + } + + // Thread safe + list.add(listener); + } + + /** + * Removes the given listener from the packet event list. + * @param listener - listener to remove. + * @param whitelist - the packet whitelist that was used. + * @return Every packet ID that was removed due to no listeners. + */ + public List removeListener(TListener listener, ListeningWhitelist whitelist) { + + List removedPackets = new ArrayList(); + + // Again, not terribly efficient. But adding or removing listeners should be a rare event. + for (Integer packetID : whitelist.getWhitelist()) { + + SortedCopyOnWriteArray> list = listeners.get(packetID); + + // Remove any listeners + if (list != null) { + // Don't remove from newly created lists + if (list.size() > 0) { + // Remove this listener. Note that priority is generally ignored. + list.remove(new PrioritizedListener(listener, whitelist.getPriority())); + + if (list.size() == 0) { + listeners.remove(packetID); + removedPackets.add(packetID); + } + } + } + + // Move on to the next + } + + return removedPackets; + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java b/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java deleted file mode 100644 index 385e4d8c..00000000 --- a/ProtocolLib/src/com/comphenix/protocol/injector/ConcurrentListenerMultimap.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.comphenix.protocol.injector; - -import java.util.ArrayList; -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.concurrency.SortedCopyOnWriteArray; -import com.comphenix.protocol.events.ListenerPriority; -import com.comphenix.protocol.events.ListeningWhitelist; -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. - * @param whitelist - the packet whitelist to use. - */ - public void addListener(PacketListener listener, ListeningWhitelist whitelist) { - - PrioritizedListener prioritized = new PrioritizedListener(listener, whitelist.getPriority()); - - for (Integer packetID : whitelist.getWhitelist()) { - addListener(packetID, prioritized); - } - } - - // Add the listener to a specific packet notifcation list - private void addListener(Integer packetID, PrioritizedListener listener) { - - SortedCopyOnWriteArray 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 SortedCopyOnWriteArray value = new SortedCopyOnWriteArray(); - - 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; - } - } - - // Thread safe - list.add(listener); - } - - /** - * Removes the given listener from the packet event list. - * @param listener - listener to remove. - * @param whitelist - the packet whitelist that was used. - * @return Every packet ID that was removed due to no listeners. - */ - public List removeListener(PacketListener listener, ListeningWhitelist whitelist) { - - List removedPackets = new ArrayList(); - - // Again, not terribly efficient. But adding or removing listeners should be a rare event. - for (Integer packetID : whitelist.getWhitelist()) { - - SortedCopyOnWriteArray list = listeners.get(packetID); - - // Remove any listeners - if (list != null) { - // Don't remove from newly created lists - if (list.size() > 0) { - // Remove this listener. Note that priority is generally ignored. - list.remove(new PrioritizedListener(listener, whitelist.getPriority())); - - 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) { - SortedCopyOnWriteArray 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 (PrioritizedListener element : list) { - try { - element.getListener().onPacketReceiving(event); - } catch (Throwable e) { - // Minecraft doesn't want your Exception. - logger.log(Level.SEVERE, - "Exception occured in onPacketReceiving() for " + - PacketAdapter.getPluginName(element.getListener()), 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) { - SortedCopyOnWriteArray list = listeners.get(event.getPacketID()); - - if (list == null) - return; - - synchronized (list) { - for (PrioritizedListener element : list) { - try { - element.getListener().onPacketSending(event); - } catch (Throwable e) { - // Minecraft doesn't want your Exception. - logger.log(Level.SEVERE, - "Exception occured in onPacketReceiving() for " + - PacketAdapter.getPluginName(element.getListener()), e); - } - } - } - } - - /** - * A listener with an associated priority. - */ - private class PrioritizedListener implements Comparable { - private PacketListener listener; - private ListenerPriority priority; - - public PrioritizedListener(PacketListener listener, ListenerPriority priority) { - this.listener = listener; - this.priority = priority; - } - - @Override - public int compareTo(PrioritizedListener other) { - // This ensures that lower priority listeners are executed first - return Ints.compare(this.getPriority().getSlot(), - other.getPriority().getSlot()); - } - - // Note that this equals() method is NOT consistent with compareTo(). - // But, it's a private class so who cares. - @Override - public boolean equals(Object obj) { - // We only care about the listener - priority itself should not make a difference - if(obj instanceof PrioritizedListener){ - final PrioritizedListener other = (PrioritizedListener) obj; - return Objects.equal(listener, other.listener); - } else { - return false; - } - } - - public PacketListener getListener() { - return listener; - } - - public ListenerPriority getPriority() { - return priority; - } - } -} diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 05fcee54..f4084dbb 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -97,8 +97,8 @@ public final class PacketFilterManager implements ProtocolManager { private Set sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap()); // The two listener containers - private ConcurrentListenerMultimap recievedListeners = new ConcurrentListenerMultimap(); - private ConcurrentListenerMultimap sendingListeners = new ConcurrentListenerMultimap(); + private SortedPacketListenerList recievedListeners = new SortedPacketListenerList(); + private SortedPacketListenerList sendingListeners = new SortedPacketListenerList(); // Whether or not this class has been closed private boolean hasClosed; diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PrioritizedListener.java b/ProtocolLib/src/com/comphenix/protocol/injector/PrioritizedListener.java new file mode 100644 index 00000000..017aadea --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PrioritizedListener.java @@ -0,0 +1,59 @@ +package com.comphenix.protocol.injector; + +import com.comphenix.protocol.events.ListenerPriority; +import com.google.common.base.Objects; +import com.google.common.primitives.Ints; + +/** + * Represents a listener with a priority. + * + * @author Kristian + */ +public class PrioritizedListener implements Comparable> { + + private TListener listener; + private ListenerPriority priority; + + public PrioritizedListener(TListener listener, ListenerPriority priority) { + this.listener = listener; + this.priority = priority; + } + + @Override + public int compareTo(PrioritizedListener other) { + // This ensures that lower priority listeners are executed first + return Ints.compare( + this.getPriority().getSlot(), + other.getPriority().getSlot()); + } + + // Note that this equals() method is NOT consistent with compareTo(). + // But, it's a private class so who cares. + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + // We only care about the listener - priority itself should not make a difference + if(obj instanceof PrioritizedListener){ + final PrioritizedListener other = (PrioritizedListener) obj; + return Objects.equal(listener, other.listener); + } else { + return false; + } + } + + /** + * Retrieve the underlying listener. + * @return Underlying listener. + */ + public TListener getListener() { + return listener; + } + + /** + * Retrieve the priority of this listener. + * @return Listener priority. + */ + public ListenerPriority getPriority() { + return priority; + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java b/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java new file mode 100644 index 00000000..41c80e70 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java @@ -0,0 +1,70 @@ +package com.comphenix.protocol.injector; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap; +import com.comphenix.protocol.concurrency.SortedCopyOnWriteArray; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; + +/** + * A thread-safe implementation of a listener multimap. + * + * @author Kristian + */ +class SortedPacketListenerList extends AbstractConcurrentListenerMultimap { + + /** + * 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) { + SortedCopyOnWriteArray> 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 (PrioritizedListener element : list) { + try { + element.getListener().onPacketReceiving(event); + } catch (Throwable e) { + // Minecraft doesn't want your Exception. + logger.log(Level.SEVERE, + "Exception occured in onPacketReceiving() for " + + PacketAdapter.getPluginName(element.getListener()), 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) { + SortedCopyOnWriteArray> list = listeners.get(event.getPacketID()); + + if (list == null) + return; + + synchronized (list) { + for (PrioritizedListener element : list) { + try { + element.getListener().onPacketSending(event); + } catch (Throwable e) { + // Minecraft doesn't want your Exception. + logger.log(Level.SEVERE, + "Exception occured in onPacketReceiving() for " + + PacketAdapter.getPluginName(element.getListener()), e); + } + } + } + } + +} From 6f02e798027545bf4e61fc2e2113a1d8387f318a Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 18:05:08 +0200 Subject: [PATCH 20/50] Still a work in progress. I'm considering removing the async listener and use the packet listener for both sync and async processing. --- .../com/comphenix/protocol/PacketStream.java | 55 ++++++++ .../comphenix/protocol/ProtocolLibrary.java | 2 +- .../comphenix/protocol/ProtocolManager.java | 43 +----- .../protocol/async/AsyncFilterManager.java | 80 +++++++++++- .../protocol/async/AsyncListener.java | 12 ++ .../comphenix/protocol/async/AsyncPacket.java | 122 +++++++++++++++-- .../protocol/async/ListenerToken.java | 123 ++++++++++++++++-- .../protocol/async/PacketProcessingQueue.java | 73 +++++++++-- .../protocol/async/PacketSendingQueue.java | 41 ++++++ .../AbstractConcurrentListenerMultimap.java | 16 ++- .../injector/PacketFilterManager.java | 16 +++ .../injector/SortedPacketListenerList.java | 50 ++++--- 12 files changed, 522 insertions(+), 111 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/PacketStream.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java diff --git a/ProtocolLib/src/com/comphenix/protocol/PacketStream.java b/ProtocolLib/src/com/comphenix/protocol/PacketStream.java new file mode 100644 index 00000000..464f6f5c --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/PacketStream.java @@ -0,0 +1,55 @@ +package com.comphenix.protocol; + +import java.lang.reflect.InvocationTargetException; + +import org.bukkit.entity.Player; + +import com.comphenix.protocol.events.PacketContainer; + +/** + * Represents a object capable of sending or receiving packets. + * + * @author Kristian + */ +public interface PacketStream { + + /** + * Send a packet to the given player. + * @param reciever - the reciever. + * @param packet - packet to send. + * @throws InvocationTargetException - if an error occured when sending the packet. + */ + public void sendServerPacket(Player reciever, PacketContainer packet) + throws InvocationTargetException; + + /** + * Send a packet to the given player. + * @param reciever - the reciever. + * @param packet - packet to send. + * @param filters - whether or not to invoke any packet filters. + * @throws InvocationTargetException - if an error occured when sending the packet. + */ + public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) + throws InvocationTargetException; + + /** + * Simulate recieving a certain packet from a given player. + * @param sender - the sender. + * @param packet - the packet that was sent. + * @throws InvocationTargetException If the reflection machinery failed. + * @throws IllegalAccessException If the underlying method caused an error. + */ + public void recieveClientPacket(Player sender, PacketContainer packet) + throws IllegalAccessException, InvocationTargetException; + + /** + * Simulate recieving a certain packet from a given player. + * @param sender - the sender. + * @param packet - the packet that was sent. + * @param filters - whether or not to invoke any packet filters. + * @throws InvocationTargetException If the reflection machinery failed. + * @throws IllegalAccessException If the underlying method caused an error. + */ + public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) + throws IllegalAccessException, InvocationTargetException; +} diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index a0d3ce00..f41491ca 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -59,7 +59,7 @@ public class ProtocolLibrary extends JavaPlugin { backgroundCompiler = new BackgroundCompiler(getClassLoader()); BackgroundCompiler.setInstance(backgroundCompiler); } - + // Notify server managers of incompatible plugins checkForIncompatibility(manager); diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java index 87062685..2fa911e3 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java @@ -17,7 +17,6 @@ package com.comphenix.protocol; -import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Set; @@ -35,7 +34,7 @@ import com.google.common.collect.ImmutableSet; * Represents an API for accessing the Minecraft protocol. * @author Kristian */ -public interface ProtocolManager { +public interface ProtocolManager extends PacketStream { /** * Retrieves a list of every registered packet listener. @@ -66,46 +65,6 @@ public interface ProtocolManager { * @param plugin - the plugin to unload. */ public void removePacketListeners(Plugin plugin); - - /** - * Send a packet to the given player. - * @param reciever - the reciever. - * @param packet - packet to send. - * @throws InvocationTargetException - if an error occured when sending the packet. - */ - public void sendServerPacket(Player reciever, PacketContainer packet) - throws InvocationTargetException; - - /** - * Send a packet to the given player. - * @param reciever - the reciever. - * @param packet - packet to send. - * @param filters - whether or not to invoke any packet filters. - * @throws InvocationTargetException - if an error occured when sending the packet. - */ - public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) - throws InvocationTargetException; - - /** - * Simulate recieving a certain packet from a given player. - * @param sender - the sender. - * @param packet - the packet that was sent. - * @throws InvocationTargetException If the reflection machinery failed. - * @throws IllegalAccessException If the underlying method caused an error. - */ - public void recieveClientPacket(Player sender, PacketContainer packet) - throws IllegalAccessException, InvocationTargetException; - - /** - * Simulate recieving a certain packet from a given player. - * @param sender - the sender. - * @param packet - the packet that was sent. - * @param filters - whether or not to invoke any packet filters. - * @throws InvocationTargetException If the reflection machinery failed. - * @throws IllegalAccessException If the underlying method caused an error. - */ - public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) - throws IllegalAccessException, InvocationTargetException; /** * Constructs a new encapsulated Minecraft packet with the given ID. diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index dc8e972a..86f9d789 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -1,6 +1,12 @@ package com.comphenix.protocol.async; -import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.PacketStream; +import com.comphenix.protocol.events.PacketEvent; /** * Represents a filter manager for asynchronous packets. @@ -9,9 +15,77 @@ import java.util.concurrent.Future; */ public class AsyncFilterManager { - - public Future registerAsyncHandler() { + private PacketProcessingQueue processingQueue; + private PacketSendingQueue sendingQueue; + + private PacketStream packetStream; + private Logger logger; + + // The likely main thread + private Thread mainThread; + + // Current packet index + private AtomicInteger currentSendingIndex = new AtomicInteger(); + + public AsyncFilterManager(Logger logger, PacketStream packetStream) { + this.sendingQueue = new PacketSendingQueue(); + this.processingQueue = new PacketProcessingQueue(sendingQueue); + this.packetStream = packetStream; + this.logger = logger; + this.mainThread = Thread.currentThread(); } + public ListenerToken registerAsyncHandler(Plugin plugin, AsyncListener listener) { + ListenerToken token = new ListenerToken(plugin, mainThread, this, listener); + + processingQueue.addListener(token, listener.getSendingWhitelist()); + return token; + } + + public void unregisterAsyncHandler(ListenerToken listenerToken) { + if (listenerToken == null) + throw new IllegalArgumentException("listenerToken cannot be NULL"); + + listenerToken.cancel(); + } + + // Called by ListenerToken + void unregisterAsyncHandlerInternal(ListenerToken listenerToken) { + // Just remove it from the queue + processingQueue.removeListener(listenerToken, listenerToken.getAsyncListener().getSendingWhitelist()); + } + + public void enqueueSyncPacket(PacketEvent syncPacket, int sendingDelta, long timeoutDelta) { + AsyncPacket asyncPacket = new AsyncPacket(packetStream, syncPacket, + currentSendingIndex.getAndIncrement() + sendingDelta, + System.currentTimeMillis(), + timeoutDelta); + + // Start the process + sendingQueue.enqueue(asyncPacket); + processingQueue.enqueuePacket(asyncPacket); + } + + public PacketStream getPacketStream() { + return packetStream; + } + + public Logger getLogger() { + return logger; + } + + PacketProcessingQueue getProcessingQueue() { + return processingQueue; + } + + PacketSendingQueue getSendingQueue() { + return sendingQueue; + } + + public void cleanupAll() { + // Remove all listeners + + // We don't necessarily remove packets, as this might be a part of a server reload + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java index 01ef4aba..49342139 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java @@ -1,5 +1,17 @@ package com.comphenix.protocol.async; +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.events.ListeningWhitelist; + public interface AsyncListener { public void onAsyncPacket(AsyncPacket packet); + + public ListeningWhitelist getSendingWhitelist(); + + /** + * Retrieve the plugin that created this async packet listener. + * @return The plugin, or NULL if not available. + */ + public Plugin getPlugin(); } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java index e14d4073..9bcc2e53 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java @@ -1,8 +1,12 @@ package com.comphenix.protocol.async; import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; +import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.PrioritizedListener; import com.google.common.primitives.Longs; /** @@ -12,6 +16,11 @@ import com.google.common.primitives.Longs; */ public class AsyncPacket implements Serializable, Comparable { + /** + * Signal an end to the packet processing. + */ + static final AsyncPacket INTERUPT_PACKET = new AsyncPacket(); + /** * Generated by Eclipse. */ @@ -20,13 +29,23 @@ public class AsyncPacket implements Serializable, Comparable { /** * Default number of milliseconds until a packet will rejected. */ - public static final int DEFAULT_TIMEOUT_DETLA = 60000; + public static final int DEFAULT_TIMEOUT_DELTA = 60000; /** * The original synchronized packet. */ private PacketEvent packetEvent; + /** + * The packet stream responsible for transmitting the packet when it's done processing. + */ + private transient PacketStream packetStream; + + /** + * Current list of async packet listeners. + */ + private transient Iterator> listenerTraversal; + // Timeout handling private long initialTime; private long timeout; @@ -35,18 +54,39 @@ public class AsyncPacket implements Serializable, Comparable { private long originalSendingIndex; private long newSendingIndex; + // Whether or not the packet has been processed by the listeners + private volatile boolean processed; + + private AsyncPacket() { + // Used by the poision pill pattern + } + + /** + * Determine whether or not this is a signal for the async listener to interrupt processing. + * @return Interrupt packet processing. + */ + boolean isInteruptPacket() { + // This is only possble if we're dealing with the poision pill packet + return packetEvent == null || packetStream == null; + } /** * Create a container for asyncronous packets. * @param packetEvent - the synchronous packet event. * @param initialTime - the current time in milliseconds since 01.01.1970 00:00. */ - public AsyncPacket(PacketEvent packetEvent, long sendingIndex, long initialTime) { + public AsyncPacket(PacketStream packetStream, PacketEvent packetEvent, long sendingIndex, long initialTime, long timeoutDelta) { + if (packetEvent == null) + throw new IllegalArgumentException("packetEvent cannot be NULL"); + if (packetStream == null) + throw new IllegalArgumentException("packetStream cannot be NULL"); + + this.packetStream = packetStream; this.packetEvent = packetEvent; // Timeout this.initialTime = initialTime; - this.timeout = initialTime + DEFAULT_TIMEOUT_DETLA; + this.timeout = initialTime + timeoutDelta; // Sending index this.originalSendingIndex = sendingIndex; @@ -69,14 +109,6 @@ public class AsyncPacket implements Serializable, Comparable { return timeout; } - /** - * Sets the time the packet will be forcefully rejected. - * @param timeout - the time to reject the packet, in milliseconds since 01.01.1970 00:00. - */ - public void setTimeout(long timeout) { - this.timeout = timeout; - } - /** * Retrieve the order the packet was originally transmitted. * @return The original packet index. @@ -112,6 +144,74 @@ public class AsyncPacket implements Serializable, Comparable { public PacketEvent getPacketEvent() { return packetEvent; } + + /** + * Retrieve the packet ID of the underlying packet. + * @return Packet ID. + */ + public int getPacketID() { + return packetEvent.getPacketID(); + } + + /** + * Retrieve the packet stream responsible for transmitting this packet. + * @return The packet stream. + */ + public PacketStream getPacketStream() { + return packetStream; + } + + /** + * Sets the output packet stream responsible for transmitting this packet. + * @param packetStream - new output packet stream. + */ + public void setPacketStream(PacketStream packetStream) { + this.packetStream = packetStream; + } + + /** + * Retrieve whether or not this packet has been processed by the async listeners. + * @return TRUE if it has been processed, FALSE otherwise. + */ + boolean isProcessed() { + return processed; + } + + /** + * Sets whether or not this packet has been processed by the async listeners. + * @param processed - TRUE if it has, FALSE otherwise. + */ + void setProcessed(boolean processed) { + this.processed = processed; + } + + /** + * Retrieve iterator for the next listener in line. + * @return Next async packet listener iterator. + */ + Iterator> getListenerTraversal() { + return listenerTraversal; + } + + /** + * We're done processing. Send the packet. + */ + void sendPacket() { + try { + // We only support server packets at this stage + packetStream.sendServerPacket(packetEvent.getPlayer(), packetEvent.getPacket(), false); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + + /** + * Set the iterator for the next listener. + * @param listenerTraversal - the new async packet listener iterator. + */ + void setListenerTraversal(Iterator> listenerTraversal) { + this.listenerTraversal = listenerTraversal; + } @Override public int compareTo(AsyncPacket o) { diff --git a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java index c3a59b3d..b932b252 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java @@ -1,38 +1,139 @@ package com.comphenix.protocol.async; import java.util.concurrent.ArrayBlockingQueue; +import java.util.logging.Level; + +import org.bukkit.plugin.Plugin; public class ListenerToken { + // Default queue capacity + private static int DEFAULT_CAPACITY = 1024; + // Cancel the async handler private volatile boolean cancelled; + + // The packet listener + private AsyncListener listener; + + // The original plugin + private Plugin plugin; + + // The filter manager + private AsyncFilterManager filterManager; + + // List of queued packets + private ArrayBlockingQueue queuedPackets = new ArrayBlockingQueue(DEFAULT_CAPACITY); + // Minecraft main thread + private Thread mainThread; + + public ListenerToken(Plugin plugin, Thread mainThread, AsyncFilterManager filterManager, AsyncListener listener) { + if (filterManager == null) + throw new IllegalArgumentException("filterManager cannot be NULL"); + if (listener == null) + throw new IllegalArgumentException("listener cannot be NULL"); + + this.plugin = plugin; + this.mainThread = mainThread; + this.filterManager = filterManager; + this.listener = listener; + } + public boolean isCancelled() { return cancelled; } + public AsyncListener getAsyncListener() { + return listener; + } + /** * Cancel the handler. */ public void cancel() { - cancelled = true; + // Remove the listener as quickly as possible + close(); + + // Poison Pill Shutdown + queuedPackets.clear(); + queuedPackets.add(AsyncPacket.INTERUPT_PACKET); } - - public void beginListener(AsyncListener asyncListener) { + /** + * Queue a packet for processing. + * @param packet - a packet for processing. + * @throws IllegalStateException If the underlying packet queue is full. + */ + public void enqueuePacket(AsyncPacket packet) { + if (packet == null) + throw new IllegalArgumentException("packet is NULL"); + + queuedPackets.add(packet); + } + + /** + * Entry point for the background thread that will be processing the packet asynchronously. + *

+ * WARNING: + * Never call this method from the main thread. Doing so will block Minecraft. + */ + public void listenerLoop() { + // Danger, danger! + if (Thread.currentThread().getId() == mainThread.getId()) + throw new IllegalStateException("Do not call this method from the main thread."); try { - AsyncPacket packet = processingQueue.take(); - - // Now, - asyncListener.onAsyncPacket(packet); - + mainLoop: + while (!cancelled) { + AsyncPacket packet = queuedPackets.take(); + + // Handle cancel requests + if (packet == null || packet.isInteruptPacket()) { + break; + } + + // Here's the core of the asynchronous processing + try { + listener.onAsyncPacket(packet); + } catch (Throwable e) { + // Minecraft doesn't want your Exception. + filterManager.getLogger().log(Level.SEVERE, + "Unhandled exception occured in onAsyncPacket() for " + getPluginName(), e); + } + + // Now, get the next non-cancelled listener + for (; packet.getListenerTraversal().hasNext(); ) { + ListenerToken token = packet.getListenerTraversal().next().getListener(); + + if (!token.isCancelled()) { + token.enqueuePacket(packet); + continue mainLoop; + } + } + + // There are no more listeners - queue the packet for transmission + filterManager.getSendingQueue().signalPacketUpdate(packet); + filterManager.getProcessingQueue().signalProcessingDone(); + } } catch (InterruptedException e) { - + // We're done } - - + // Clean up + close(); + } + + private void close() { + // Remove the listener itself + if (!cancelled) { + filterManager.unregisterAsyncHandlerInternal(this); + cancelled = true; + } + } + + private String getPluginName() { + return plugin != null ? plugin.getName() : "UNKNOWN"; } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index 620e9b3a..9061f96f 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -1,17 +1,19 @@ package com.comphenix.protocol.async; +import java.util.Collection; +import java.util.Iterator; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Semaphore; -import com.comphenix.protocol.concurrency.SortedCopyOnWriteArray; +import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap; +import com.comphenix.protocol.injector.PrioritizedListener; /** - * Handles the processing of a certain packet type. + * Handles the processing of every packet type. * * @author Kristian */ -class PacketProcessingQueue { - +class PacketProcessingQueue extends AbstractConcurrentListenerMultimap { /** * Default maximum number of packets to process concurrently. @@ -31,44 +33,87 @@ class PacketProcessingQueue { // Queued packets for being processed private ArrayBlockingQueue processingQueue; - - // Packet listeners - private SortedCopyOnWriteArray<> - public PacketProcessingQueue() { - this(DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY); + // Packets for sending + private PacketSendingQueue sendingQueue; + + public PacketProcessingQueue(PacketSendingQueue sendingQueue) { + this(sendingQueue, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY); } - public PacketProcessingQueue(int queueLimit, int maximumConcurrency) { + public PacketProcessingQueue(PacketSendingQueue sendingQueue, int queueLimit, int maximumConcurrency) { + super(); this.processingQueue = new ArrayBlockingQueue(queueLimit); this.maximumConcurrency = maximumConcurrency; this.concurrentProcessing = new Semaphore(maximumConcurrency); + this.sendingQueue = sendingQueue; } - public boolean queuePacket(AsyncPacket packet) { + /** + * Enqueue a packet for processing by the asynchronous listeners. + * @param packet - packet to process. + * @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space. + */ + public boolean enqueuePacket(AsyncPacket packet) { try { processingQueue.add(packet); // Begin processing packets - processPacket(); + signalBeginProcessing(); return true; } catch (IllegalStateException e) { return false; } } - public void processPacket() { - if (concurrentProcessing.tryAcquire()) { + /** + * Called by the current method and each thread to signal that a packet might be ready for processing. + */ + public void signalBeginProcessing() { + while (concurrentProcessing.tryAcquire()) { AsyncPacket packet = processingQueue.poll(); // Any packet queued? if (packet != null) { + Collection> list = getListener(packet.getPacketID()); + if (list != null) { + Iterator> iterator = list.iterator(); + + if (iterator.hasNext()) { + packet.setListenerTraversal(iterator); + iterator.next().getListener().enqueuePacket(packet); + continue; + } + } + + // The packet has no listeners. Just send it. + sendingQueue.signalPacketUpdate(packet); + signalProcessingDone(); + + } else { + // No more queued packets. + return; } } } + + /** + * Called when a packet has been processed. + */ + public void signalProcessingDone() { + concurrentProcessing.release(); + } + /** + * Retrieve the maximum number of packets to process at any given time. + * @return Number of simultaneous packet to process. + */ public int getMaximumConcurrency() { return maximumConcurrency; } + + public void removeListeners() { + for (PrioritizedListener token : ) + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java new file mode 100644 index 00000000..db2223f4 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java @@ -0,0 +1,41 @@ +package com.comphenix.protocol.async; + +import java.util.concurrent.PriorityBlockingQueue; + +/** + * Represents packets ready to be transmitted to a client. + * @author Kristian + */ +class PacketSendingQueue { + + private PriorityBlockingQueue sendingQueue; + + /** + * Enqueue a packet for sending. + * @param packet + */ + public void enqueue(AsyncPacket packet) { + sendingQueue.add(packet); + } + + /** + * Invoked when one of the packets have finished processing. + */ + public synchronized void signalPacketUpdate(AsyncPacket packetUpdated) { + + // Mark this packet as finished + packetUpdated.setProcessed(true); + + // Transmit as many packets as we can + while (true) { + AsyncPacket current = sendingQueue.peek(); + + if (current != null && current.isProcessed()) { + current.sendPacket(); + sendingQueue.poll(); + } else { + break; + } + } + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java b/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java index 2fd5a1f3..ae61cf9b 100644 --- a/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java +++ b/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java @@ -1,6 +1,7 @@ package com.comphenix.protocol.concurrency; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -16,8 +17,8 @@ import com.comphenix.protocol.injector.PrioritizedListener; public abstract class AbstractConcurrentListenerMultimap { // The core of our map - protected ConcurrentMap>> listeners = - new ConcurrentHashMap>>(); + private ConcurrentMap>> listeners = + new ConcurrentHashMap>>(); /** * Adds a listener to its requested list of packet recievers. @@ -91,4 +92,15 @@ public abstract class AbstractConcurrentListenerMultimap { return removedPackets; } + + /** + * Retrieve the registered listeners, in order from the lowest to the highest priority. + *

+ * The returned list is thread-safe and doesn't require synchronization. + * @param packetID - packet ID. + * @return Registered listeners. + */ + public Collection> getListener(int packetID) { + return listeners.get(packetID); + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index f4084dbb..85329a2e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -46,6 +46,7 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.events.*; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -112,6 +113,9 @@ public final class PacketFilterManager implements ProtocolManager { // Error logger private Logger logger; + // The async packet handler + private AsyncFilterManager asyncFilterManager; + /** * Only create instances of this class if protocol lib is disabled. */ @@ -126,11 +130,20 @@ public final class PacketFilterManager implements ProtocolManager { this.classLoader = classLoader; this.logger = logger; this.packetInjector = new PacketInjector(classLoader, this, connectionLookup); + this.asyncFilterManager = new AsyncFilterManager(logger, this); } catch (IllegalAccessException e) { logger.log(Level.SEVERE, "Unable to initialize packet injector.", e); } } + /** + * Retrieve the current async packet filter manager. + * @return Async filter manager. + */ + public AsyncFilterManager getAsyncFilterManager() { + return asyncFilterManager; + } + /** * Retrieves how the server packets are read. * @return Injection method for reading server packets. @@ -656,6 +669,9 @@ public final class PacketFilterManager implements ProtocolManager { injection.cleanupAll(); } + // Clean up async handlers + asyncFilterManager.cleanupAll(); + // Remove packet handlers if (packetInjector != null) packetInjector.cleanupAll(); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java b/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java index 41c80e70..27df4585 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java @@ -1,16 +1,16 @@ package com.comphenix.protocol.injector; +import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap; -import com.comphenix.protocol.concurrency.SortedCopyOnWriteArray; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; /** - * A thread-safe implementation of a listener multimap. + * Registry of synchronous packet listeners. * * @author Kristian */ @@ -22,22 +22,20 @@ class SortedPacketListenerList extends AbstractConcurrentListenerMultimap> list = listeners.get(event.getPacketID()); + Collection> list = getListener(event.getPacketID()); if (list == null) return; - - // We have to be careful. Cannot modify the underlying list when sending notifications. - synchronized (list) { - for (PrioritizedListener element : list) { - try { - element.getListener().onPacketReceiving(event); - } catch (Throwable e) { - // Minecraft doesn't want your Exception. - logger.log(Level.SEVERE, - "Exception occured in onPacketReceiving() for " + - PacketAdapter.getPluginName(element.getListener()), e); - } + + // The returned list is thread-safe + for (PrioritizedListener element : list) { + try { + element.getListener().onPacketReceiving(event); + } catch (Throwable e) { + // Minecraft doesn't want your Exception. + logger.log(Level.SEVERE, + "Exception occured in onPacketReceiving() for " + + PacketAdapter.getPluginName(element.getListener()), e); } } } @@ -48,23 +46,21 @@ class SortedPacketListenerList extends AbstractConcurrentListenerMultimap> list = listeners.get(event.getPacketID()); + Collection> list = getListener(event.getPacketID()); if (list == null) return; - synchronized (list) { - for (PrioritizedListener element : list) { - try { - element.getListener().onPacketSending(event); - } catch (Throwable e) { - // Minecraft doesn't want your Exception. - logger.log(Level.SEVERE, - "Exception occured in onPacketReceiving() for " + - PacketAdapter.getPluginName(element.getListener()), e); - } + for (PrioritizedListener element : list) { + try { + element.getListener().onPacketSending(event); + } catch (Throwable e) { + // Minecraft doesn't want your Exception. + logger.log(Level.SEVERE, + "Exception occured in onPacketReceiving() for " + + PacketAdapter.getPluginName(element.getListener()), e); } - } + } } } From 285952b14d5dda7c542827aeda90f69cc58a8871 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 19:13:12 +0200 Subject: [PATCH 21/50] Use the normal packet listener instead of an async listener. --- .../protocol/async/AsyncFilterManager.java | 43 ++++++--- .../protocol/async/AsyncListener.java | 17 ---- .../comphenix/protocol/async/AsyncPacket.java | 87 ++++++------------- .../protocol/async/ListenerToken.java | 37 +++++--- .../protocol/async/PacketProcessingQueue.java | 24 +++-- .../protocol/async/PacketSendingQueue.java | 66 +++++++++++--- .../AbstractConcurrentListenerMultimap.java | 16 ++++ .../protocol/events/PacketEvent.java | 56 +++++++++++- .../injector/PacketFilterManager.java | 8 +- 9 files changed, 230 insertions(+), 124 deletions(-) delete mode 100644 ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index 86f9d789..36e6fc01 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -7,6 +7,7 @@ import org.bukkit.plugin.Plugin; import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; /** * Represents a filter manager for asynchronous packets. @@ -36,7 +37,7 @@ public class AsyncFilterManager { this.mainThread = Thread.currentThread(); } - public ListenerToken registerAsyncHandler(Plugin plugin, AsyncListener listener) { + public ListenerToken registerAsyncHandler(Plugin plugin, PacketListener listener) { ListenerToken token = new ListenerToken(plugin, mainThread, this, listener); processingQueue.addListener(token, listener.getSendingWhitelist()); @@ -56,15 +57,33 @@ public class AsyncFilterManager { processingQueue.removeListener(listenerToken, listenerToken.getAsyncListener().getSendingWhitelist()); } - public void enqueueSyncPacket(PacketEvent syncPacket, int sendingDelta, long timeoutDelta) { - AsyncPacket asyncPacket = new AsyncPacket(packetStream, syncPacket, - currentSendingIndex.getAndIncrement() + sendingDelta, - System.currentTimeMillis(), - timeoutDelta); + /** + * Enqueue a packet for asynchronous processing. + * @param syncPacket - synchronous packet event. + * @param asyncMarker - the asynchronous marker to use. + */ + public void enqueueSyncPacket(PacketEvent syncPacket, AsyncPacket asyncMarker) { + PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker); // Start the process - sendingQueue.enqueue(asyncPacket); - processingQueue.enqueuePacket(asyncPacket); + sendingQueue.enqueue(newEvent); + processingQueue.enqueuePacket(newEvent); + } + + /** + * Construct an async marker with the given sending priority delta and timeout delta. + * @param sendingDelta - how many packets we're willing to wait. + * @param timeoutDelta - how long (in ms) until the packet expire. + * @return An async marker. + */ + public AsyncPacket createAsyncMarker(long sendingDelta, long timeoutDelta) { + return createAsyncMarker(sendingDelta, timeoutDelta, + currentSendingIndex.incrementAndGet(), System.currentTimeMillis()); + } + + // Helper method + private AsyncPacket createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) { + return new AsyncPacket(packetStream, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta); } public PacketStream getPacketStream() { @@ -83,9 +102,11 @@ public class AsyncFilterManager { return sendingQueue; } + /** + * Remove listeners, close threads and transmit every delayed packet. + */ public void cleanupAll() { - // Remove all listeners - - // We don't necessarily remove packets, as this might be a part of a server reload + processingQueue.cleanupAll(); + sendingQueue.cleanupAll(); } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java deleted file mode 100644 index 49342139..00000000 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListener.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.comphenix.protocol.async; - -import org.bukkit.plugin.Plugin; - -import com.comphenix.protocol.events.ListeningWhitelist; - -public interface AsyncListener { - public void onAsyncPacket(AsyncPacket packet); - - public ListeningWhitelist getSendingWhitelist(); - - /** - * Retrieve the plugin that created this async packet listener. - * @return The plugin, or NULL if not available. - */ - public Plugin getPlugin(); -} diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java index 9bcc2e53..c4c87a9d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java @@ -1,5 +1,6 @@ package com.comphenix.protocol.async; +import java.io.IOException; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.util.Iterator; @@ -15,12 +16,7 @@ import com.google.common.primitives.Longs; * @author Kristian */ public class AsyncPacket implements Serializable, Comparable { - - /** - * Signal an end to the packet processing. - */ - static final AsyncPacket INTERUPT_PACKET = new AsyncPacket(); - + /** * Generated by Eclipse. */ @@ -31,11 +27,6 @@ public class AsyncPacket implements Serializable, Comparable { */ public static final int DEFAULT_TIMEOUT_DELTA = 60000; - /** - * The original synchronized packet. - */ - private PacketEvent packetEvent; - /** * The packet stream responsible for transmitting the packet when it's done processing. */ @@ -56,33 +47,16 @@ public class AsyncPacket implements Serializable, Comparable { // Whether or not the packet has been processed by the listeners private volatile boolean processed; - - private AsyncPacket() { - // Used by the poision pill pattern - } - - /** - * Determine whether or not this is a signal for the async listener to interrupt processing. - * @return Interrupt packet processing. - */ - boolean isInteruptPacket() { - // This is only possble if we're dealing with the poision pill packet - return packetEvent == null || packetStream == null; - } - + /** * Create a container for asyncronous packets. - * @param packetEvent - the synchronous packet event. * @param initialTime - the current time in milliseconds since 01.01.1970 00:00. */ - public AsyncPacket(PacketStream packetStream, PacketEvent packetEvent, long sendingIndex, long initialTime, long timeoutDelta) { - if (packetEvent == null) - throw new IllegalArgumentException("packetEvent cannot be NULL"); + AsyncPacket(PacketStream packetStream, long sendingIndex, long sendingDelta, long initialTime, long timeoutDelta) { if (packetStream == null) throw new IllegalArgumentException("packetStream cannot be NULL"); this.packetStream = packetStream; - this.packetEvent = packetEvent; // Timeout this.initialTime = initialTime; @@ -137,22 +111,6 @@ public class AsyncPacket implements Serializable, Comparable { this.newSendingIndex = newSendingIndex; } - /** - * Retrieve the original synchronous packet event. - * @return The original packet event. - */ - public PacketEvent getPacketEvent() { - return packetEvent; - } - - /** - * Retrieve the packet ID of the underlying packet. - * @return Packet ID. - */ - public int getPacketID() { - return packetEvent.getPacketID(); - } - /** * Retrieve the packet stream responsible for transmitting this packet. * @return The packet stream. @@ -173,7 +131,7 @@ public class AsyncPacket implements Serializable, Comparable { * Retrieve whether or not this packet has been processed by the async listeners. * @return TRUE if it has been processed, FALSE otherwise. */ - boolean isProcessed() { + public boolean isProcessed() { return processed; } @@ -189,21 +147,9 @@ public class AsyncPacket implements Serializable, Comparable { * Retrieve iterator for the next listener in line. * @return Next async packet listener iterator. */ - Iterator> getListenerTraversal() { + public Iterator> getListenerTraversal() { return listenerTraversal; } - - /** - * We're done processing. Send the packet. - */ - void sendPacket() { - try { - // We only support server packets at this stage - packetStream.sendServerPacket(packetEvent.getPlayer(), packetEvent.getPacket(), false); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } /** * Set the iterator for the next listener. @@ -212,7 +158,26 @@ public class AsyncPacket implements Serializable, Comparable { void setListenerTraversal(Iterator> listenerTraversal) { this.listenerTraversal = listenerTraversal; } - + + /** + * Transmit a given packet to the current packet stream. + * @param event - the packet to send. + * @throws IOException If the packet couldn't be sent. + */ + public void sendPacket(PacketEvent event) throws IOException { + try { + if (event.isServerPacket()) { + packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), false); + } else { + packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), false); + } + } catch (InvocationTargetException e) { + throw new IOException("Cannot send packet", e); + } catch (IllegalAccessException e) { + throw new IOException("Cannot send packet", e); + } + } + @Override public int compareTo(AsyncPacket o) { if (o == null) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java index b932b252..439902ce 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java @@ -5,8 +5,16 @@ import java.util.logging.Level; import org.bukkit.plugin.Plugin; -public class ListenerToken { +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; +class ListenerToken { + + /** + * Signal an end to the packet processing. + */ + private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object()); + // Default queue capacity private static int DEFAULT_CAPACITY = 1024; @@ -14,7 +22,7 @@ public class ListenerToken { private volatile boolean cancelled; // The packet listener - private AsyncListener listener; + private PacketListener listener; // The original plugin private Plugin plugin; @@ -23,12 +31,12 @@ public class ListenerToken { private AsyncFilterManager filterManager; // List of queued packets - private ArrayBlockingQueue queuedPackets = new ArrayBlockingQueue(DEFAULT_CAPACITY); + private ArrayBlockingQueue queuedPackets = new ArrayBlockingQueue(DEFAULT_CAPACITY); // Minecraft main thread private Thread mainThread; - public ListenerToken(Plugin plugin, Thread mainThread, AsyncFilterManager filterManager, AsyncListener listener) { + public ListenerToken(Plugin plugin, Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) { if (filterManager == null) throw new IllegalArgumentException("filterManager cannot be NULL"); if (listener == null) @@ -44,7 +52,7 @@ public class ListenerToken { return cancelled; } - public AsyncListener getAsyncListener() { + public PacketListener getAsyncListener() { return listener; } @@ -57,7 +65,7 @@ public class ListenerToken { // Poison Pill Shutdown queuedPackets.clear(); - queuedPackets.add(AsyncPacket.INTERUPT_PACKET); + queuedPackets.add(INTERUPT_PACKET); } /** @@ -65,7 +73,7 @@ public class ListenerToken { * @param packet - a packet for processing. * @throws IllegalStateException If the underlying packet queue is full. */ - public void enqueuePacket(AsyncPacket packet) { + public void enqueuePacket(PacketEvent packet) { if (packet == null) throw new IllegalArgumentException("packet is NULL"); @@ -86,16 +94,21 @@ public class ListenerToken { try { mainLoop: while (!cancelled) { - AsyncPacket packet = queuedPackets.take(); + PacketEvent packet = queuedPackets.take(); + AsyncPacket marker = packet.getAsyncMarker(); // Handle cancel requests - if (packet == null || packet.isInteruptPacket()) { + if (packet == null || marker == null || !packet.isAsynchronous()) { break; } // Here's the core of the asynchronous processing try { - listener.onAsyncPacket(packet); + if (packet.isServerPacket()) + listener.onPacketSending(packet); + else + listener.onPacketReceiving(packet); + } catch (Throwable e) { // Minecraft doesn't want your Exception. filterManager.getLogger().log(Level.SEVERE, @@ -103,8 +116,8 @@ public class ListenerToken { } // Now, get the next non-cancelled listener - for (; packet.getListenerTraversal().hasNext(); ) { - ListenerToken token = packet.getListenerTraversal().next().getListener(); + for (; marker.getListenerTraversal().hasNext(); ) { + ListenerToken token = marker.getListenerTraversal().next().getListener(); if (!token.isCancelled()) { token.enqueuePacket(packet); diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index 9061f96f..33d10389 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -6,6 +6,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Semaphore; import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap; +import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.PrioritizedListener; /** @@ -32,7 +33,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap processingQueue; + private ArrayBlockingQueue processingQueue; // Packets for sending private PacketSendingQueue sendingQueue; @@ -43,7 +44,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap(queueLimit); + this.processingQueue = new ArrayBlockingQueue(queueLimit); this.maximumConcurrency = maximumConcurrency; this.concurrentProcessing = new Semaphore(maximumConcurrency); this.sendingQueue = sendingQueue; @@ -54,7 +55,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap> list = getListener(packet.getPacketID()); + AsyncPacket marker = packet.getAsyncMarker(); if (list != null) { Iterator> iterator = list.iterator(); if (iterator.hasNext()) { - packet.setListenerTraversal(iterator); + marker.setListenerTraversal(iterator); iterator.next().getListener().enqueuePacket(packet); continue; } @@ -113,7 +115,15 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap token : ) + public void cleanupAll() { + // Cancel all the threads and every listener + for (PrioritizedListener token : values()) { + if (token != null) { + token.getListener().cancel(); + } + } + + // Remove the rest, just in case + clearListeners(); } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java index db2223f4..fca9de83 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java @@ -1,41 +1,85 @@ package com.comphenix.protocol.async; +import java.io.IOException; import java.util.concurrent.PriorityBlockingQueue; +import com.comphenix.protocol.events.PacketEvent; + /** * Represents packets ready to be transmitted to a client. * @author Kristian */ class PacketSendingQueue { - private PriorityBlockingQueue sendingQueue; + private PriorityBlockingQueue sendingQueue; /** * Enqueue a packet for sending. * @param packet */ - public void enqueue(AsyncPacket packet) { + public void enqueue(PacketEvent packet) { sendingQueue.add(packet); } /** * Invoked when one of the packets have finished processing. */ - public synchronized void signalPacketUpdate(AsyncPacket packetUpdated) { - + public synchronized void signalPacketUpdate(PacketEvent packetUpdated) { // Mark this packet as finished - packetUpdated.setProcessed(true); - - // Transmit as many packets as we can + packetUpdated.getAsyncMarker().setProcessed(true); + signalPacketUpdates(); + } + + /** + * Send every packet, regardless of the processing state. + */ + public synchronized void forceSend() { while (true) { - AsyncPacket current = sendingQueue.peek(); + PacketEvent current = sendingQueue.poll(); - if (current != null && current.isProcessed()) { - current.sendPacket(); - sendingQueue.poll(); + if (current != null) { + // Just print the error + try { + current.getAsyncMarker().sendPacket(current); + } catch (IOException e) { + e.printStackTrace(); + } } else { break; } } } + + /** + * Invoked when potentially every packet is finished. + */ + private void signalPacketUpdates() { + // Transmit as many packets as we can + while (true) { + PacketEvent current = sendingQueue.peek(); + + if (current != null && current.getAsyncMarker().isProcessed()) { + // Just print the error + try { + current.getAsyncMarker().sendPacket(current); + } catch (IOException e) { + e.printStackTrace(); + } + + sendingQueue.poll(); + + } else { + break; + } + } + + // And we're done + } + + /** + * Automatically transmits every delayed packet. + */ + public void cleanupAll() { + forceSend(); + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java b/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java index ae61cf9b..5154f261 100644 --- a/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java +++ b/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java @@ -8,6 +8,7 @@ import java.util.concurrent.ConcurrentMap; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.injector.PrioritizedListener; +import com.google.common.collect.Iterables; /** * A thread-safe implementation of a listener multimap. @@ -103,4 +104,19 @@ public abstract class AbstractConcurrentListenerMultimap { public Collection> getListener(int packetID) { return listeners.get(packetID); } + + /** + * Retrieve every listener. + * @return Every listener. + */ + protected Iterable> values() { + return Iterables.concat(listeners.values()); + } + + /** + * Remove all packet listeners. + */ + protected void clearListeners() { + listeners.clear(); + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java index f3fa4ae2..9eedc109 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java @@ -25,6 +25,8 @@ import java.util.EventObject; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; +import com.comphenix.protocol.async.AsyncPacket; + public class PacketEvent extends EventObject implements Cancellable { /** * Automatically generated by Eclipse. @@ -35,7 +37,10 @@ public class PacketEvent extends EventObject implements Cancellable { private PacketContainer packet; private boolean serverPacket; private boolean cancel; - + + private AsyncPacket asyncMarker; + private boolean asynchronous; + /** * Use the static constructors to create instances of this event. * @param source - the event source. @@ -50,6 +55,15 @@ public class PacketEvent extends EventObject implements Cancellable { this.player = player; this.serverPacket = serverPacket; } + + private PacketEvent(PacketEvent origial, AsyncPacket asyncMarker) { + super(origial.source); + this.packet = origial.packet; + this.player = origial.player; + this.serverPacket = origial.serverPacket; + this.asyncMarker = asyncMarker; + this.asynchronous = true; + } /** * Creates an event representing a client packet transmission. @@ -73,6 +87,16 @@ public class PacketEvent extends EventObject implements Cancellable { return new PacketEvent(source, packet, recipient, true); } + /** + * Create an asynchronous packet event from a synchronous event and a async marker. + * @param event - the original synchronous event. + * @param marker - the asynchronous marker. + * @return The new packet event. + */ + public static PacketEvent fromSynchronous(PacketEvent event, AsyncPacket marker) { + return new PacketEvent(event, marker); + } + /** * Retrieves the packet that will be sent to the player. * @return Packet to send to the player. @@ -129,6 +153,36 @@ public class PacketEvent extends EventObject implements Cancellable { return serverPacket; } + /** + * Retrieve the asynchronous marker. + *

+ * If the packet is synchronous, this marker will be used to schedule an asynchronous event. In this + * asynchronous event, the marker is used to correctly pass the packet around to the different threads. + * @return The current asynchronous marker. + */ + public AsyncPacket getAsyncMarker() { + return asyncMarker; + } + + /** + * Set the asynchronous marker. + *

+ * If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled + * to be processed asynchronously with the given settings. + * @param asyncMarker - the new asynchronous marker. + */ + public void setAsyncMarker(AsyncPacket asyncMarker) { + this.asyncMarker = asyncMarker; + } + + /** + * Determine if the packet event has been executed asynchronously or not. + * @return TRUE if this packet event is asynchronous, FALSE otherwise. + */ + public boolean isAsynchronous() { + return asynchronous; + } + private void writeObject(ObjectOutputStream output) throws IOException { // Default serialization output.defaultWriteObject(); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 85329a2e..10c256d9 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -668,10 +668,7 @@ public final class PacketFilterManager implements ProtocolManager { for (PlayerInjector injection : playerInjection.values()) { injection.cleanupAll(); } - - // Clean up async handlers - asyncFilterManager.cleanupAll(); - + // Remove packet handlers if (packetInjector != null) packetInjector.cleanupAll(); @@ -681,6 +678,9 @@ public final class PacketFilterManager implements ProtocolManager { playerInjection.clear(); connectionLookup.clear(); hasClosed = true; + + // Clean up async handlers. We have to do this last. + asyncFilterManager.cleanupAll(); } @Override From 81321383d5bd8209ac79e6dbf0c51ae31a78499c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 19:33:22 +0200 Subject: [PATCH 22/50] Renamed the async marker. Added handling of close. --- .../protocol/async/AsyncFilterManager.java | 8 +-- .../{AsyncPacket.java => AsyncMarker.java} | 21 +++++-- .../protocol/async/ListenerToken.java | 2 +- .../protocol/async/PacketProcessingQueue.java | 2 +- .../protocol/async/PacketSendingQueue.java | 56 +++++++++---------- .../protocol/events/PacketEvent.java | 12 ++-- 6 files changed, 57 insertions(+), 44 deletions(-) rename ProtocolLib/src/com/comphenix/protocol/async/{AsyncPacket.java => AsyncMarker.java} (86%) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index 36e6fc01..b6a75576 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -62,7 +62,7 @@ public class AsyncFilterManager { * @param syncPacket - synchronous packet event. * @param asyncMarker - the asynchronous marker to use. */ - public void enqueueSyncPacket(PacketEvent syncPacket, AsyncPacket asyncMarker) { + public void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) { PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker); // Start the process @@ -76,14 +76,14 @@ public class AsyncFilterManager { * @param timeoutDelta - how long (in ms) until the packet expire. * @return An async marker. */ - public AsyncPacket createAsyncMarker(long sendingDelta, long timeoutDelta) { + public AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta) { return createAsyncMarker(sendingDelta, timeoutDelta, currentSendingIndex.incrementAndGet(), System.currentTimeMillis()); } // Helper method - private AsyncPacket createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) { - return new AsyncPacket(packetStream, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta); + private AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) { + return new AsyncMarker(packetStream, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta); } public PacketStream getPacketStream() { diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java similarity index 86% rename from ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java rename to ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java index c4c87a9d..dbf5940b 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncPacket.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java @@ -11,11 +11,11 @@ import com.comphenix.protocol.injector.PrioritizedListener; import com.google.common.primitives.Longs; /** - * Represents a packet that is being processed by asynchronous listeners. + * Contains information about the packet that is being processed by asynchronous listeners. * * @author Kristian */ -public class AsyncPacket implements Serializable, Comparable { +public class AsyncMarker implements Serializable, Comparable { /** * Generated by Eclipse. @@ -47,12 +47,15 @@ public class AsyncPacket implements Serializable, Comparable { // Whether or not the packet has been processed by the listeners private volatile boolean processed; + + // Whethre or not the packet has been sent + private volatile boolean transmitted; /** * Create a container for asyncronous packets. * @param initialTime - the current time in milliseconds since 01.01.1970 00:00. */ - AsyncPacket(PacketStream packetStream, long sendingIndex, long sendingDelta, long initialTime, long timeoutDelta) { + AsyncMarker(PacketStream packetStream, long sendingIndex, long sendingDelta, long initialTime, long timeoutDelta) { if (packetStream == null) throw new IllegalArgumentException("packetStream cannot be NULL"); @@ -143,6 +146,14 @@ public class AsyncPacket implements Serializable, Comparable { this.processed = processed; } + /** + * Retrieve whether or not this packet has already been sent. + * @return TRUE if it has been sent before, FALSE otherwise. + */ + public boolean isTransmitted() { + return transmitted; + } + /** * Retrieve iterator for the next listener in line. * @return Next async packet listener iterator. @@ -171,6 +182,8 @@ public class AsyncPacket implements Serializable, Comparable { } else { packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), false); } + transmitted = true; + } catch (InvocationTargetException e) { throw new IOException("Cannot send packet", e); } catch (IllegalAccessException e) { @@ -179,7 +192,7 @@ public class AsyncPacket implements Serializable, Comparable { } @Override - public int compareTo(AsyncPacket o) { + public int compareTo(AsyncMarker o) { if (o == null) return 1; else diff --git a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java index 439902ce..472762af 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java @@ -95,7 +95,7 @@ class ListenerToken { mainLoop: while (!cancelled) { PacketEvent packet = queuedPackets.take(); - AsyncPacket marker = packet.getAsyncMarker(); + AsyncMarker marker = packet.getAsyncMarker(); // Handle cancel requests if (packet == null || marker == null || !packet.isAsynchronous()) { diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index 33d10389..f1d7c431 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -77,7 +77,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap> list = getListener(packet.getPacketID()); - AsyncPacket marker = packet.getAsyncMarker(); + AsyncMarker marker = packet.getAsyncMarker(); if (list != null) { Iterator> iterator = list.iterator(); diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java index fca9de83..867bdcbe 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java @@ -27,7 +27,18 @@ class PacketSendingQueue { public synchronized void signalPacketUpdate(PacketEvent packetUpdated) { // Mark this packet as finished packetUpdated.getAsyncMarker().setProcessed(true); - signalPacketUpdates(); + + // Transmit as many packets as we can + while (true) { + PacketEvent current = sendingQueue.peek(); + + if (current != null && current.getAsyncMarker().isProcessed()) { + sendPacket(current); + sendingQueue.poll(); + } else { + break; + } + } } /** @@ -38,12 +49,7 @@ class PacketSendingQueue { PacketEvent current = sendingQueue.poll(); if (current != null) { - // Just print the error - try { - current.getAsyncMarker().sendPacket(current); - } catch (IOException e) { - e.printStackTrace(); - } + sendPacket(current); } else { break; } @@ -51,29 +57,23 @@ class PacketSendingQueue { } /** - * Invoked when potentially every packet is finished. + * Transmit a packet, if it hasn't already. + * @param event - the packet to transmit. */ - private void signalPacketUpdates() { - // Transmit as many packets as we can - while (true) { - PacketEvent current = sendingQueue.peek(); - - if (current != null && current.getAsyncMarker().isProcessed()) { - // Just print the error - try { - current.getAsyncMarker().sendPacket(current); - } catch (IOException e) { - e.printStackTrace(); - } - - sendingQueue.poll(); - - } else { - break; - } - } + private void sendPacket(PacketEvent event) { - // And we're done + AsyncMarker marker = event.getAsyncMarker(); + + try { + // Don't send a packet twice + if (marker != null && !marker.isTransmitted()) { + marker.sendPacket(event); + } + + } catch (IOException e) { + // Just print the error + e.printStackTrace(); + } } /** diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java index 9eedc109..d393b20b 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java @@ -25,7 +25,7 @@ import java.util.EventObject; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; -import com.comphenix.protocol.async.AsyncPacket; +import com.comphenix.protocol.async.AsyncMarker; public class PacketEvent extends EventObject implements Cancellable { /** @@ -38,7 +38,7 @@ public class PacketEvent extends EventObject implements Cancellable { private boolean serverPacket; private boolean cancel; - private AsyncPacket asyncMarker; + private AsyncMarker asyncMarker; private boolean asynchronous; /** @@ -56,7 +56,7 @@ public class PacketEvent extends EventObject implements Cancellable { this.serverPacket = serverPacket; } - private PacketEvent(PacketEvent origial, AsyncPacket asyncMarker) { + private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) { super(origial.source); this.packet = origial.packet; this.player = origial.player; @@ -93,7 +93,7 @@ public class PacketEvent extends EventObject implements Cancellable { * @param marker - the asynchronous marker. * @return The new packet event. */ - public static PacketEvent fromSynchronous(PacketEvent event, AsyncPacket marker) { + public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) { return new PacketEvent(event, marker); } @@ -160,7 +160,7 @@ public class PacketEvent extends EventObject implements Cancellable { * asynchronous event, the marker is used to correctly pass the packet around to the different threads. * @return The current asynchronous marker. */ - public AsyncPacket getAsyncMarker() { + public AsyncMarker getAsyncMarker() { return asyncMarker; } @@ -171,7 +171,7 @@ public class PacketEvent extends EventObject implements Cancellable { * to be processed asynchronously with the given settings. * @param asyncMarker - the new asynchronous marker. */ - public void setAsyncMarker(AsyncPacket asyncMarker) { + public void setAsyncMarker(AsyncMarker asyncMarker) { this.asyncMarker = asyncMarker; } From fad6a0a99c1257baac7c5399be3cfac13a5477d5 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 20:30:22 +0200 Subject: [PATCH 23/50] Connect the asynchronous listeners to the system. --- .../protocol/async/AsyncFilterManager.java | 114 +++++++++++++++--- .../comphenix/protocol/async/AsyncMarker.java | 51 +++++++- .../protocol/async/ListenerToken.java | 4 +- .../protocol/async/PacketProcessingQueue.java | 2 +- .../protocol/async/PacketSendingQueue.java | 18 ++- .../protocol/events/PacketEvent.java | 14 ++- .../injector/PacketFilterManager.java | 34 +++++- 7 files changed, 202 insertions(+), 35 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index b6a75576..10a46f90 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -6,6 +6,7 @@ import java.util.logging.Logger; import org.bukkit.plugin.Plugin; import com.comphenix.protocol.PacketStream; +import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; @@ -16,8 +17,11 @@ import com.comphenix.protocol.events.PacketListener; */ public class AsyncFilterManager { - private PacketProcessingQueue processingQueue; - private PacketSendingQueue sendingQueue; + private PacketProcessingQueue serverProcessingQueue; + private PacketSendingQueue serverQueue; + + private PacketProcessingQueue clientProcessingQueue; + private PacketSendingQueue clientQueue; private PacketStream packetStream; private Logger logger; @@ -29,8 +33,10 @@ public class AsyncFilterManager { private AtomicInteger currentSendingIndex = new AtomicInteger(); public AsyncFilterManager(Logger logger, PacketStream packetStream) { - this.sendingQueue = new PacketSendingQueue(); - this.processingQueue = new PacketProcessingQueue(sendingQueue); + this.serverQueue = new PacketSendingQueue(); + this.clientQueue = new PacketSendingQueue(); + this.serverProcessingQueue = new PacketProcessingQueue(serverQueue); + this.clientProcessingQueue = new PacketProcessingQueue(clientQueue); this.packetStream = packetStream; this.logger = logger; @@ -40,10 +46,23 @@ public class AsyncFilterManager { public ListenerToken registerAsyncHandler(Plugin plugin, PacketListener listener) { ListenerToken token = new ListenerToken(plugin, mainThread, this, listener); - processingQueue.addListener(token, listener.getSendingWhitelist()); + // Add listener to either or both processing queue + if (hasValidWhitelist(listener.getSendingWhitelist())) + serverProcessingQueue.addListener(token, listener.getSendingWhitelist()); + if (hasValidWhitelist(listener.getReceivingWhitelist())) + clientProcessingQueue.addListener(token, listener.getReceivingWhitelist()); + return token; } + private boolean hasValidWhitelist(ListeningWhitelist whitelist) { + return whitelist != null && whitelist.getWhitelist().size() > 0; + } + + /** + * Unregisters and closes the given asynchronous handler. + * @param listenerToken - asynchronous handler. + */ public void unregisterAsyncHandler(ListenerToken listenerToken) { if (listenerToken == null) throw new IllegalArgumentException("listenerToken cannot be NULL"); @@ -53,8 +72,14 @@ public class AsyncFilterManager { // Called by ListenerToken void unregisterAsyncHandlerInternal(ListenerToken listenerToken) { - // Just remove it from the queue - processingQueue.removeListener(listenerToken, listenerToken.getAsyncListener().getSendingWhitelist()); + + PacketListener listener = listenerToken.getAsyncListener(); + + // Just remove it from the queue(s) + if (hasValidWhitelist(listener.getSendingWhitelist())) + serverProcessingQueue.removeListener(listenerToken, listener.getSendingWhitelist()); + if (hasValidWhitelist(listener.getReceivingWhitelist())) + clientProcessingQueue.removeListener(listenerToken, listener.getReceivingWhitelist()); } /** @@ -66,8 +91,25 @@ public class AsyncFilterManager { PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker); // Start the process - sendingQueue.enqueue(newEvent); - processingQueue.enqueuePacket(newEvent); + getSendingQueue(syncPacket).enqueue(newEvent); + getProcessingQueue(syncPacket).enqueue(newEvent); + } + + /** + * Determine if a given synchronous packet has asynchronous listeners. + * @param packet - packet to test. + * @return TRUE if it does, FALSE otherwise. + */ + public boolean hasAsynchronousListeners(PacketEvent packet) { + return getProcessingQueue(packet).getListener(packet.getPacketID()).size() > 0; + } + + /** + * Construct a asynchronous marker with all the default values. + * @return Asynchronous marker. + */ + public AsyncMarker createAsyncMarker() { + return createAsyncMarker(AsyncMarker.DEFAULT_SENDING_DELTA, AsyncMarker.DEFAULT_TIMEOUT_DELTA); } /** @@ -86,27 +128,61 @@ public class AsyncFilterManager { return new AsyncMarker(packetStream, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta); } + /** + * Retrieve the default packet stream. + * @return Default packet stream. + */ public PacketStream getPacketStream() { return packetStream; } + /** + * Retrieve the default error logger. + * @return Default logger. + */ public Logger getLogger() { return logger; } - PacketProcessingQueue getProcessingQueue() { - return processingQueue; - } - - PacketSendingQueue getSendingQueue() { - return sendingQueue; - } - /** * Remove listeners, close threads and transmit every delayed packet. */ public void cleanupAll() { - processingQueue.cleanupAll(); - sendingQueue.cleanupAll(); + serverProcessingQueue.cleanupAll(); + serverQueue.cleanupAll(); + } + + /** + * Signal that a packet is ready to be transmitted. + * @param packet - packet to signal. + */ + public void signalPacketUpdate(PacketEvent packet) { + getSendingQueue(packet).signalPacketUpdate(packet); + } + + /** + * Retrieve the sending queue this packet belongs to. + * @param packet - the packet. + * @return The server or client sending queue the packet belongs to. + */ + private PacketSendingQueue getSendingQueue(PacketEvent packet) { + return packet.isServerPacket() ? serverQueue : clientQueue; + } + + /** + * Signal that a packet has finished processing. + * @param packet - packet to signal. + */ + public void signalProcessingDone(PacketEvent packet) { + getProcessingQueue(packet).signalProcessingDone(); + } + + /** + * Retrieve the processing queue this packet belongs to. + * @param packet - the packet. + * @return The server or client sending processing the packet belongs to. + */ + private PacketProcessingQueue getProcessingQueue(PacketEvent packet) { + return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue; } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java index dbf5940b..378e9c18 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java @@ -27,6 +27,11 @@ public class AsyncMarker implements Serializable, Comparable { */ public static final int DEFAULT_TIMEOUT_DELTA = 60000; + /** + * Default number of packets to skip. + */ + public static final int DEFAULT_SENDING_DELTA = 0; + /** * The packet stream responsible for transmitting the packet when it's done processing. */ @@ -48,8 +53,11 @@ public class AsyncMarker implements Serializable, Comparable { // Whether or not the packet has been processed by the listeners private volatile boolean processed; - // Whethre or not the packet has been sent + // Whether or not the packet has been sent private volatile boolean transmitted; + + // Whether or not the asynchronous processing itself should be cancelled + private volatile boolean asyncCancelled; /** * Create a container for asyncronous packets. @@ -86,6 +94,14 @@ public class AsyncMarker implements Serializable, Comparable { return timeout; } + /** + * Set the time the packet will be forcefully rejected. + * @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00. + */ + public void setTimeout(long timeout) { + this.timeout = timeout; + } + /** * Retrieve the order the packet was originally transmitted. * @return The original packet index. @@ -154,6 +170,39 @@ public class AsyncMarker implements Serializable, Comparable { return transmitted; } + /** + * Determine if this packet has expired. + * @return TRUE if it has, FALSE otherwise. + */ + public boolean hasExpired() { + return hasExpired(System.currentTimeMillis()); + } + + /** + * Determine if this packet has expired given this time. + * @param currentTime - the current time in milliseconds since 01.01.1970 00:00. + * @return TRUE if it has, FALSE otherwise. + */ + public boolean hasExpired(long currentTime) { + return timeout < currentTime; + } + + /** + * Determine if the asynchronous handling should be cancelled. + * @return TRUE if it should, FALSE otherwise. + */ + public boolean isAsyncCancelled() { + return asyncCancelled; + } + + /** + * Set whether or not the asynchronous handling should be cancelled. + * @param asyncCancelled - TRUE to cancel it, FALSE otherwise. + */ + public void setAsyncCancelled(boolean asyncCancelled) { + this.asyncCancelled = asyncCancelled; + } + /** * Retrieve iterator for the next listener in line. * @return Next async packet listener iterator. diff --git a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java index 472762af..7ada78ae 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java @@ -126,8 +126,8 @@ class ListenerToken { } // There are no more listeners - queue the packet for transmission - filterManager.getSendingQueue().signalPacketUpdate(packet); - filterManager.getProcessingQueue().signalProcessingDone(); + filterManager.signalPacketUpdate(packet); + filterManager.signalProcessingDone(packet); } } catch (InterruptedException e) { diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index f1d7c431..f703889e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -55,7 +55,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap * If the packet is synchronous, this marker will be used to schedule an asynchronous event. In this * asynchronous event, the marker is used to correctly pass the packet around to the different threads. - * @return The current asynchronous marker. + *

+ * Note that if there are no asynchronous events that can receive this packet, the marker is NULL. + * @return The current asynchronous marker, or NULL. */ public AsyncMarker getAsyncMarker() { return asyncMarker; } - + /** * Set the asynchronous marker. *

* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled - * to be processed asynchronously with the given settings. - * @param asyncMarker - the new asynchronous marker. + * to be processed asynchronously with the given settings. + *

+ * Note that if there are no asynchronous events that can receive this packet, the marker should be NULL. + * @param asyncMarker - the new asynchronous marker, or NULL. */ public void setAsyncMarker(AsyncMarker asyncMarker) { this.asyncMarker = asyncMarker; @@ -182,7 +186,7 @@ public class PacketEvent extends EventObject implements Cancellable { public boolean isAsynchronous() { return asynchronous; } - + private void writeObject(ObjectOutputStream output) throws IOException { // Default serialization output.defaultWriteObject(); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 10c256d9..75016015 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -47,6 +47,7 @@ import org.bukkit.plugin.PluginManager; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.async.AsyncFilterManager; +import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.events.*; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -284,7 +285,7 @@ public final class PacketFilterManager implements ProtocolManager { * @param event - the packet event to invoke. */ public void invokePacketRecieving(PacketEvent event) { - recievedListeners.invokePacketRecieving(logger, event); + handlePacket(recievedListeners, event); } /** @@ -292,7 +293,36 @@ public final class PacketFilterManager implements ProtocolManager { * @param event - the packet event to invoke. */ public void invokePacketSending(PacketEvent event) { - sendingListeners.invokePacketSending(logger, event); + handlePacket(sendingListeners, event); + } + + /** + * Handle a packet sending or receiving event. + *

+ * Note that we also handle asynchronous events. + * @param packetListeners - packet listeners that will receive this event. + * @param event - the evnet to broadcast. + */ + private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event) { + + // By default, asynchronous packets are queued for processing + if (asyncFilterManager.hasAsynchronousListeners(event)) { + event.setAsyncMarker(asyncFilterManager.createAsyncMarker()); + } + + // Process synchronous events + packetListeners.invokePacketRecieving(logger, event); + + // To cancel the asynchronous processing, use the async marker + if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) { + asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker()); + event.setCancelled(true); + } + } + + // NULL marker mean we're dealing with no asynchronous listeners + private boolean hasAsyncCancelled(AsyncMarker marker) { + return marker == null || marker.isAsyncCancelled(); } /** From 880520cd940602adbe0f5ff20f47ea720055c80d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 20:32:18 +0200 Subject: [PATCH 24/50] Renamed the ListenerToken into a AsyncListenerHandler. --- .../comphenix/protocol/async/AsyncFilterManager.java | 8 ++++---- .../{ListenerToken.java => AsyncListenerHandler.java} | 11 ++++++++--- .../src/com/comphenix/protocol/async/AsyncMarker.java | 6 +++--- .../protocol/async/PacketProcessingQueue.java | 8 ++++---- 4 files changed, 19 insertions(+), 14 deletions(-) rename ProtocolLib/src/com/comphenix/protocol/async/{ListenerToken.java => AsyncListenerHandler.java} (88%) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index 10a46f90..c11a7b04 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -43,8 +43,8 @@ public class AsyncFilterManager { this.mainThread = Thread.currentThread(); } - public ListenerToken registerAsyncHandler(Plugin plugin, PacketListener listener) { - ListenerToken token = new ListenerToken(plugin, mainThread, this, listener); + public AsyncListenerHandler registerAsyncHandler(Plugin plugin, PacketListener listener) { + AsyncListenerHandler token = new AsyncListenerHandler(plugin, mainThread, this, listener); // Add listener to either or both processing queue if (hasValidWhitelist(listener.getSendingWhitelist())) @@ -63,7 +63,7 @@ public class AsyncFilterManager { * Unregisters and closes the given asynchronous handler. * @param listenerToken - asynchronous handler. */ - public void unregisterAsyncHandler(ListenerToken listenerToken) { + public void unregisterAsyncHandler(AsyncListenerHandler listenerToken) { if (listenerToken == null) throw new IllegalArgumentException("listenerToken cannot be NULL"); @@ -71,7 +71,7 @@ public class AsyncFilterManager { } // Called by ListenerToken - void unregisterAsyncHandlerInternal(ListenerToken listenerToken) { + void unregisterAsyncHandlerInternal(AsyncListenerHandler listenerToken) { PacketListener listener = listenerToken.getAsyncListener(); diff --git a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java similarity index 88% rename from ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java rename to ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java index 7ada78ae..b400b8e1 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/ListenerToken.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -8,7 +8,12 @@ import org.bukkit.plugin.Plugin; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; -class ListenerToken { +/** + * Represents a handler for an asynchronous event. + * + * @author Kristian + */ +public class AsyncListenerHandler { /** * Signal an end to the packet processing. @@ -36,7 +41,7 @@ class ListenerToken { // Minecraft main thread private Thread mainThread; - public ListenerToken(Plugin plugin, Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) { + public AsyncListenerHandler(Plugin plugin, Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) { if (filterManager == null) throw new IllegalArgumentException("filterManager cannot be NULL"); if (listener == null) @@ -117,7 +122,7 @@ class ListenerToken { // Now, get the next non-cancelled listener for (; marker.getListenerTraversal().hasNext(); ) { - ListenerToken token = marker.getListenerTraversal().next().getListener(); + AsyncListenerHandler token = marker.getListenerTraversal().next().getListener(); if (!token.isCancelled()) { token.enqueuePacket(packet); diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java index 378e9c18..51ac432d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java @@ -40,7 +40,7 @@ public class AsyncMarker implements Serializable, Comparable { /** * Current list of async packet listeners. */ - private transient Iterator> listenerTraversal; + private transient Iterator> listenerTraversal; // Timeout handling private long initialTime; @@ -207,7 +207,7 @@ public class AsyncMarker implements Serializable, Comparable { * Retrieve iterator for the next listener in line. * @return Next async packet listener iterator. */ - public Iterator> getListenerTraversal() { + public Iterator> getListenerTraversal() { return listenerTraversal; } @@ -215,7 +215,7 @@ public class AsyncMarker implements Serializable, Comparable { * Set the iterator for the next listener. * @param listenerTraversal - the new async packet listener iterator. */ - void setListenerTraversal(Iterator> listenerTraversal) { + void setListenerTraversal(Iterator> listenerTraversal) { this.listenerTraversal = listenerTraversal; } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index f703889e..c67d9d20 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -14,7 +14,7 @@ import com.comphenix.protocol.injector.PrioritizedListener; * * @author Kristian */ -class PacketProcessingQueue extends AbstractConcurrentListenerMultimap { +class PacketProcessingQueue extends AbstractConcurrentListenerMultimap { /** * Default maximum number of packets to process concurrently. @@ -76,11 +76,11 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap> list = getListener(packet.getPacketID()); + Collection> list = getListener(packet.getPacketID()); AsyncMarker marker = packet.getAsyncMarker(); if (list != null) { - Iterator> iterator = list.iterator(); + Iterator> iterator = list.iterator(); if (iterator.hasNext()) { marker.setListenerTraversal(iterator); @@ -117,7 +117,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap token : values()) { + for (PrioritizedListener token : values()) { if (token != null) { token.getListener().cancel(); } From 025e97ca957b1dbb74c2caefea63e0daa454a93e Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 20:34:29 +0200 Subject: [PATCH 25/50] Renamed the ListenerToken in the code as well. --- .../protocol/async/AsyncFilterManager.java | 26 +++++++++---------- .../protocol/async/AsyncListenerHandler.java | 6 ++--- .../protocol/async/PacketProcessingQueue.java | 6 ++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index c11a7b04..6ad4f219 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -44,15 +44,15 @@ public class AsyncFilterManager { } public AsyncListenerHandler registerAsyncHandler(Plugin plugin, PacketListener listener) { - AsyncListenerHandler token = new AsyncListenerHandler(plugin, mainThread, this, listener); + AsyncListenerHandler handler = new AsyncListenerHandler(plugin, mainThread, this, listener); // Add listener to either or both processing queue if (hasValidWhitelist(listener.getSendingWhitelist())) - serverProcessingQueue.addListener(token, listener.getSendingWhitelist()); + serverProcessingQueue.addListener(handler, listener.getSendingWhitelist()); if (hasValidWhitelist(listener.getReceivingWhitelist())) - clientProcessingQueue.addListener(token, listener.getReceivingWhitelist()); + clientProcessingQueue.addListener(handler, listener.getReceivingWhitelist()); - return token; + return handler; } private boolean hasValidWhitelist(ListeningWhitelist whitelist) { @@ -61,25 +61,25 @@ public class AsyncFilterManager { /** * Unregisters and closes the given asynchronous handler. - * @param listenerToken - asynchronous handler. + * @param handler - asynchronous handler. */ - public void unregisterAsyncHandler(AsyncListenerHandler listenerToken) { - if (listenerToken == null) + public void unregisterAsyncHandler(AsyncListenerHandler handler) { + if (handler == null) throw new IllegalArgumentException("listenerToken cannot be NULL"); - listenerToken.cancel(); + handler.cancel(); } - // Called by ListenerToken - void unregisterAsyncHandlerInternal(AsyncListenerHandler listenerToken) { + // Called by AsyncListenerHandler + void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) { - PacketListener listener = listenerToken.getAsyncListener(); + PacketListener listener = handler.getAsyncListener(); // Just remove it from the queue(s) if (hasValidWhitelist(listener.getSendingWhitelist())) - serverProcessingQueue.removeListener(listenerToken, listener.getSendingWhitelist()); + serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist()); if (hasValidWhitelist(listener.getReceivingWhitelist())) - clientProcessingQueue.removeListener(listenerToken, listener.getReceivingWhitelist()); + clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist()); } /** diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java index b400b8e1..4521831d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -122,10 +122,10 @@ public class AsyncListenerHandler { // Now, get the next non-cancelled listener for (; marker.getListenerTraversal().hasNext(); ) { - AsyncListenerHandler token = marker.getListenerTraversal().next().getListener(); + AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener(); - if (!token.isCancelled()) { - token.enqueuePacket(packet); + if (!handler.isCancelled()) { + handler.enqueuePacket(packet); continue mainLoop; } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index c67d9d20..80554803 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -117,9 +117,9 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap token : values()) { - if (token != null) { - token.getListener().cancel(); + for (PrioritizedListener handler : values()) { + if (handler != null) { + handler.getListener().cancel(); } } From 4f4202185c4cac661ca18fa6cf9afc14f87eda47 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 21:18:21 +0200 Subject: [PATCH 26/50] Make it even clearer that the listener loop method should be called from a separate thread. --- .../protocol/async/AsyncListenerHandler.java | 18 ++++++++++++++---- .../protocol/async/PacketSendingQueue.java | 6 ++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java index 4521831d..72c04470 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -86,12 +86,22 @@ public class AsyncListenerHandler { } /** - * Entry point for the background thread that will be processing the packet asynchronously. + * Create a runnable that will initiate the listener loop. *

- * WARNING: - * Never call this method from the main thread. Doing so will block Minecraft. + * Warning: Never call the run() method in the main thread. */ - public void listenerLoop() { + public Runnable getListenerLoop() { + return new Runnable() { + @Override + public void run() { + listenerLoop(); + } + }; + } + + // DO NOT call this method from the main thread + private void listenerLoop() { + // Danger, danger! if (Thread.currentThread().getId() == mainThread.getId()) throw new IllegalStateException("Do not call this method from the main thread."); diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java index 2c894dbd..9ab9fb7e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java @@ -27,7 +27,13 @@ class PacketSendingQueue { public synchronized void signalPacketUpdate(PacketEvent packetUpdated) { // Mark this packet as finished packetUpdated.getAsyncMarker().setProcessed(true); + trySendPackets(); + } + /** + * Attempt to send any remaining packets. + */ + public synchronized void trySendPackets() { // Transmit as many packets as we can while (true) { PacketEvent current = sendingQueue.peek(); From a6db5419c095fae516dbbc64cf9cf9327728e261 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 21:29:12 +0200 Subject: [PATCH 27/50] Detect packet timeout. --- .../comphenix/protocol/ProtocolLibrary.java | 36 +++++++++++++++++++ .../protocol/async/AsyncFilterManager.java | 8 +++++ .../protocol/async/AsyncListenerHandler.java | 11 ++++++ 3 files changed, 55 insertions(+) diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index f41491ca..d09d901b 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -43,6 +43,13 @@ public class ProtocolLibrary extends JavaPlugin { // Structure compiler private BackgroundCompiler backgroundCompiler; + // Used to (mostly) clean up packets that have expired + private int asyncPacketTask = -1; + + // Number of ticks between each cleanup. We don't need to do this often, + // as it's only indeeded to detected timeouts. + private static final int ASYNC_PACKET_DELAY = 10; + @Override public void onLoad() { logger = getLoggerSafely(); @@ -69,6 +76,9 @@ public class ProtocolLibrary extends JavaPlugin { // Inject our hook into already existing players protocolManager.initializePlayers(server.getOnlinePlayers()); + // Timeout + createAsyncTask(server); + // Try to enable statistics try { statistisc = new Statistics(this); @@ -79,6 +89,26 @@ public class ProtocolLibrary extends JavaPlugin { } } + private void createAsyncTask(Server server) { + try { + if (asyncPacketTask < 0) + throw new IllegalStateException("Async task has already been created"); + + // Attempt to create task + asyncPacketTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() { + @Override + public void run() { + protocolManager.getAsyncFilterManager().sendProcessedPackets(); + } + }, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY); + + } catch (Throwable e) { + if (asyncPacketTask == -1) { + logger.log(Level.SEVERE, "Unable to create packet timeout task.", e); + } + } + } + private void checkForIncompatibility(PluginManager manager) { // Plugin authors: Notify me to remove these String[] incompatiblePlugins = {}; @@ -100,6 +130,12 @@ public class ProtocolLibrary extends JavaPlugin { BackgroundCompiler.setInstance(null); } + // Clean up + if (asyncPacketTask >= 0) { + getServer().getScheduler().cancelTask(asyncPacketTask); + asyncPacketTask = -1; + } + protocolManager.close(); protocolManager = null; statistisc = null; diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index 6ad4f219..cefdd30d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -185,4 +185,12 @@ public class AsyncFilterManager { private PacketProcessingQueue getProcessingQueue(PacketEvent packet) { return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue; } + + /** + * Send any due packets, or clean up packets that have expired. + */ + public void sendProcessedPackets() { + clientQueue.trySendPackets(); + serverQueue.trySendPackets(); + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java index 72c04470..90461670 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -26,6 +26,9 @@ public class AsyncListenerHandler { // Cancel the async handler private volatile boolean cancelled; + // If we've started the listener loop before + private volatile boolean started; + // The packet listener private PacketListener listener; @@ -105,6 +108,13 @@ public class AsyncListenerHandler { // Danger, danger! if (Thread.currentThread().getId() == mainThread.getId()) throw new IllegalStateException("Do not call this method from the main thread."); + if (started) + throw new IllegalStateException("A listener cannot be run by multiple threads. Create a new listener instead."); + if (cancelled) + throw new IllegalStateException("Listener has been cancelled. Create a new listener instead."); + + // Proceed + started = true; try { mainLoop: @@ -158,6 +168,7 @@ public class AsyncListenerHandler { if (!cancelled) { filterManager.unregisterAsyncHandlerInternal(this); cancelled = true; + started = false; } } From 65b5a0e8ec2be4fcf8ce55a07cf2e7e0f9fd6195 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 21:42:16 +0200 Subject: [PATCH 28/50] We don't expect consumers to create asynchronous markers, so don't allow them to set it in the packet event. --- .../com/comphenix/protocol/ProtocolManager.java | 7 +++++++ .../protocol/async/PacketProcessingQueue.java | 3 ++- .../protocol/async/PacketSendingQueue.java | 2 +- .../comphenix/protocol/events/PacketEvent.java | 16 ++-------------- .../protocol/injector/PacketFilterManager.java | 9 ++++----- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java index 2fa911e3..f3f25d63 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java @@ -24,6 +24,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; +import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.PacketConstructor; @@ -122,4 +123,10 @@ public interface ProtocolManager extends PacketStream { * @return TRUE if it has, FALSE otherwise. */ public boolean isClosed(); + + /** + * Retrieve the current asyncronous packet manager. + * @return Asyncronous packet manager. + */ + public AsyncFilterManager getAsyncFilterManager(); } \ No newline at end of file diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index 80554803..d2ffe29e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -79,6 +79,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap> list = getListener(packet.getPacketID()); AsyncMarker marker = packet.getAsyncMarker(); + // Yes, removing the marker will cause the chain to stop if (list != null) { Iterator> iterator = list.iterator(); @@ -89,7 +90,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap - * If the packet is synchronous, this marker will be used to schedule an asynchronous event. In this + * If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following * asynchronous event, the marker is used to correctly pass the packet around to the different threads. *

* Note that if there are no asynchronous events that can receive this packet, the marker is NULL. @@ -165,19 +166,6 @@ public class PacketEvent extends EventObject implements Cancellable { public AsyncMarker getAsyncMarker() { return asyncMarker; } - - /** - * Set the asynchronous marker. - *

- * If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled - * to be processed asynchronously with the given settings. - *

- * Note that if there are no asynchronous events that can receive this packet, the marker should be NULL. - * @param asyncMarker - the new asynchronous marker, or NULL. - */ - public void setAsyncMarker(AsyncMarker asyncMarker) { - this.asyncMarker = asyncMarker; - } /** * Determine if the packet event has been executed asynchronously or not. diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 75016015..5b138624 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -137,10 +137,7 @@ public final class PacketFilterManager implements ProtocolManager { } } - /** - * Retrieve the current async packet filter manager. - * @return Async filter manager. - */ + @Override public AsyncFilterManager getAsyncFilterManager() { return asyncFilterManager; } @@ -313,9 +310,11 @@ public final class PacketFilterManager implements ProtocolManager { // Process synchronous events packetListeners.invokePacketRecieving(logger, event); - // To cancel the asynchronous processing, use the async marker + // To cancel asynchronous processing, use the async marker if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) { asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker()); + + // The above makes a copy of the event, so it's safe to cancel it event.setCancelled(true); } } From e86c3d3a6eb9c26a77c74364c362c8cab6821041 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 22:12:30 +0200 Subject: [PATCH 29/50] We have to keep the setAsyncMarker method. --- .../protocol/async/AsyncFilterManager.java | 19 +++++++++++++++++++ .../protocol/async/PacketSendingQueue.java | 16 ++++++++++++++++ .../AbstractConcurrentListenerMultimap.java | 11 ++++++++++- .../protocol/events/PacketEvent.java | 15 +++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index cefdd30d..a5a7c21a 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -1,5 +1,6 @@ package com.comphenix.protocol.async; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -95,6 +96,24 @@ public class AsyncFilterManager { getProcessingQueue(syncPacket).enqueue(newEvent); } + /** + * Retrieves a immutable set containing the ID of the sent server packets that will be + * observed by the asynchronous listeners. + * @return Every filtered server packet. + */ + public Set getSendingFilters() { + return serverProcessingQueue.keySet(); + } + + /** + * Retrieves a immutable set containing the ID of the recieved client packets that will be + * observed by the asynchronous listeners. + * @return Every filtered client packet. + */ + public Set getReceivingFilters() { + return clientProcessingQueue.keySet(); + } + /** * Determine if a given synchronous packet has asynchronous listeners. * @param packet - packet to test. diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java index 97452f7b..5028738f 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java @@ -1,9 +1,11 @@ package com.comphenix.protocol.async; import java.io.IOException; +import java.util.Comparator; import java.util.concurrent.PriorityBlockingQueue; import com.comphenix.protocol.events.PacketEvent; +import com.google.common.collect.ComparisonChain; /** * Represents packets ready to be transmitted to a client. @@ -11,8 +13,22 @@ import com.comphenix.protocol.events.PacketEvent; */ class PacketSendingQueue { + private static final int INITIAL_CAPACITY = 64; + private PriorityBlockingQueue sendingQueue; + public PacketSendingQueue() { + sendingQueue = new PriorityBlockingQueue(INITIAL_CAPACITY, new Comparator() { + // Compare using the async marker + @Override + public int compare(PacketEvent o1, PacketEvent o2) { + return ComparisonChain.start(). + compare(o1.getAsyncMarker(), o2.getAsyncMarker()). + result(); + } + }); + } + /** * Enqueue a packet for sending. * @param packet diff --git a/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java b/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java index 5154f261..00741fe2 100644 --- a/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java +++ b/ProtocolLib/src/com/comphenix/protocol/concurrency/AbstractConcurrentListenerMultimap.java @@ -3,6 +3,7 @@ package com.comphenix.protocol.concurrency; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -109,10 +110,18 @@ public abstract class AbstractConcurrentListenerMultimap { * Retrieve every listener. * @return Every listener. */ - protected Iterable> values() { + public Iterable> values() { return Iterables.concat(listeners.values()); } + /** + * Retrieve every registered packet ID: + * @return Registered packet ID. + */ + public Set keySet() { + return listeners.keySet(); + } + /** * Remove all packet listeners. */ diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java index aba9a2ff..799154f3 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java @@ -166,6 +166,21 @@ public class PacketEvent extends EventObject implements Cancellable { public AsyncMarker getAsyncMarker() { return asyncMarker; } + /** + * Set the asynchronous marker. + *

+ * If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled + * to be processed asynchronously with the given settings. + *

+ * Note that if there are no asynchronous events that can receive this packet, the marker should be NULL. + * @param asyncMarker - the new asynchronous marker, or NULL. + * @throws IllegalStateException If the current event is asynchronous. + */ + public void setAsyncMarker(AsyncMarker asyncMarker) { + if (isAsynchronous()) + throw new IllegalStateException("The marker is immutable for asynchronous events"); + this.asyncMarker = asyncMarker; + } /** * Determine if the packet event has been executed asynchronously or not. From b3098bc6adfd6f61455011c6d2932900505ea7f0 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 22:18:19 +0200 Subject: [PATCH 30/50] Pull up the important methods in the asynchronous packet handler. --- .../protocol/AsynchronousManager.java | 72 +++++++++++++++++++ .../comphenix/protocol/ProtocolManager.java | 3 +- .../protocol/async/AsyncFilterManager.java | 41 +++-------- .../injector/PacketFilterManager.java | 3 +- 4 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java diff --git a/ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java b/ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java new file mode 100644 index 00000000..e12e73a3 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java @@ -0,0 +1,72 @@ +package com.comphenix.protocol; + +import java.util.Set; +import java.util.logging.Logger; + +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.async.AsyncListenerHandler; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; + +/** + * Represents a asynchronous packet handler. + * + * @author Kristian + */ +public interface AsynchronousManager { + + /** + * Registers an asynchronous packet handler. + *

+ * To start listening asynchronously, pass the getListenerLoop() runnable to a different thread. + * @param plugin - the plugin that is registering the handler. + * @param listener - the packet listener that will recieve these asynchronous events. + * @return An asynchrouns handler. + */ + public abstract AsyncListenerHandler registerAsyncHandler(Plugin plugin, PacketListener listener); + + /** + * Unregisters and closes the given asynchronous handler. + * @param handler - asynchronous handler. + */ + public abstract void unregisterAsyncHandler(AsyncListenerHandler handler); + + /** + * Retrieves a immutable set containing the ID of the sent server packets that will be + * observed by the asynchronous listeners. + * @return Every filtered server packet. + */ + public abstract Set getSendingFilters(); + + /** + * Retrieves a immutable set containing the ID of the recieved client packets that will be + * observed by the asynchronous listeners. + * @return Every filtered client packet. + */ + public abstract Set getReceivingFilters(); + + /** + * Determine if a given synchronous packet has asynchronous listeners. + * @param packet - packet to test. + * @return TRUE if it does, FALSE otherwise. + */ + public abstract boolean hasAsynchronousListeners(PacketEvent packet); + + /** + * Retrieve the default packet stream. + * @return Default packet stream. + */ + public abstract PacketStream getPacketStream(); + + /** + * Retrieve the default error logger. + * @return Default logger. + */ + public abstract Logger getLogger(); + + /** + * Remove listeners, close threads and transmit every delayed packet. + */ + public abstract void cleanupAll(); +} \ No newline at end of file diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java index f3f25d63..a820ff9a 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java @@ -24,7 +24,6 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.PacketConstructor; @@ -128,5 +127,5 @@ public interface ProtocolManager extends PacketStream { * Retrieve the current asyncronous packet manager. * @return Asyncronous packet manager. */ - public AsyncFilterManager getAsyncFilterManager(); + public AsynchronousManager getAsyncFilterManager(); } \ No newline at end of file diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index a5a7c21a..8fd8319d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -6,6 +6,7 @@ import java.util.logging.Logger; import org.bukkit.plugin.Plugin; +import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketEvent; @@ -16,7 +17,7 @@ import com.comphenix.protocol.events.PacketListener; * * @author Kristian */ -public class AsyncFilterManager { +public class AsyncFilterManager implements AsynchronousManager { private PacketProcessingQueue serverProcessingQueue; private PacketSendingQueue serverQueue; @@ -44,6 +45,7 @@ public class AsyncFilterManager { this.mainThread = Thread.currentThread(); } + @Override public AsyncListenerHandler registerAsyncHandler(Plugin plugin, PacketListener listener) { AsyncListenerHandler handler = new AsyncListenerHandler(plugin, mainThread, this, listener); @@ -60,10 +62,7 @@ public class AsyncFilterManager { return whitelist != null && whitelist.getWhitelist().size() > 0; } - /** - * Unregisters and closes the given asynchronous handler. - * @param handler - asynchronous handler. - */ + @Override public void unregisterAsyncHandler(AsyncListenerHandler handler) { if (handler == null) throw new IllegalArgumentException("listenerToken cannot be NULL"); @@ -96,29 +95,17 @@ public class AsyncFilterManager { getProcessingQueue(syncPacket).enqueue(newEvent); } - /** - * Retrieves a immutable set containing the ID of the sent server packets that will be - * observed by the asynchronous listeners. - * @return Every filtered server packet. - */ + @Override public Set getSendingFilters() { return serverProcessingQueue.keySet(); } - /** - * Retrieves a immutable set containing the ID of the recieved client packets that will be - * observed by the asynchronous listeners. - * @return Every filtered client packet. - */ + @Override public Set getReceivingFilters() { return clientProcessingQueue.keySet(); } - /** - * Determine if a given synchronous packet has asynchronous listeners. - * @param packet - packet to test. - * @return TRUE if it does, FALSE otherwise. - */ + @Override public boolean hasAsynchronousListeners(PacketEvent packet) { return getProcessingQueue(packet).getListener(packet.getPacketID()).size() > 0; } @@ -147,25 +134,17 @@ public class AsyncFilterManager { return new AsyncMarker(packetStream, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta); } - /** - * Retrieve the default packet stream. - * @return Default packet stream. - */ + @Override public PacketStream getPacketStream() { return packetStream; } - /** - * Retrieve the default error logger. - * @return Default logger. - */ + @Override public Logger getLogger() { return logger; } - /** - * Remove listeners, close threads and transmit every delayed packet. - */ + @Override public void cleanupAll() { serverProcessingQueue.cleanupAll(); serverQueue.cleanupAll(); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 5b138624..5db7c124 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -45,6 +45,7 @@ import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; +import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncMarker; @@ -138,7 +139,7 @@ public final class PacketFilterManager implements ProtocolManager { } @Override - public AsyncFilterManager getAsyncFilterManager() { + public AsynchronousManager getAsyncFilterManager() { return asyncFilterManager; } From 721d92bd4fee2f78b811491d92788c65021a4382 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 22:20:21 +0200 Subject: [PATCH 31/50] Fixed a minor bug. --- ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java | 4 +++- ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java | 2 +- .../com/comphenix/protocol/injector/PacketFilterManager.java | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index d09d901b..f989b1ad 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -25,6 +25,7 @@ import org.bukkit.Server; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.metrics.Statistics; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; @@ -98,7 +99,8 @@ public class ProtocolLibrary extends JavaPlugin { asyncPacketTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() { @Override public void run() { - protocolManager.getAsyncFilterManager().sendProcessedPackets(); + AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager(); + manager.sendProcessedPackets(); } }, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY); diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java index a820ff9a..eaaf3702 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolManager.java @@ -127,5 +127,5 @@ public interface ProtocolManager extends PacketStream { * Retrieve the current asyncronous packet manager. * @return Asyncronous packet manager. */ - public AsynchronousManager getAsyncFilterManager(); + public AsynchronousManager getAsynchronousManager(); } \ No newline at end of file diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 5db7c124..84dc69de 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -139,7 +139,7 @@ public final class PacketFilterManager implements ProtocolManager { } @Override - public AsynchronousManager getAsyncFilterManager() { + public AsynchronousManager getAsynchronousManager() { return asyncFilterManager; } From 15b33925c09c092d1e2ecc5e7ec6a5d53f228990 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 22:25:09 +0200 Subject: [PATCH 32/50] Fixed a couple of minor bugs. --- .../com/comphenix/protocol/injector/PrioritizedListener.java | 5 +++++ .../protocol/reflect/compiler/StructureCompiler.java | 5 +++-- .../protocol/reflect/instances/DefaultInstances.java | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PrioritizedListener.java b/ProtocolLib/src/com/comphenix/protocol/injector/PrioritizedListener.java index 017aadea..2d971206 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PrioritizedListener.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PrioritizedListener.java @@ -41,6 +41,11 @@ public class PrioritizedListener implements Comparable[] { String.class, byte[].class, int.class, int.class }); // Awesome. Now, create and return it. - defineMethod.setAccessible(true); + defined.setAccessible(true); + defineMethod = defined; } @SuppressWarnings("rawtypes") diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java index 0e548ff0..7b846077 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/instances/DefaultInstances.java @@ -172,11 +172,11 @@ public class DefaultInstances { } Constructor minimum = getMinimumConstructor(type); - int parameterCount = minimum.getParameterTypes().length; // Create the type with this constructor using default values. This might fail, though. try { if (minimum != null) { + int parameterCount = minimum.getParameterTypes().length; Object[] params = new Object[parameterCount]; Class[] types = minimum.getParameterTypes(); From d58ff1c4c1d966b17e55fbec8ff39fbafc1ac608 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 23:21:09 +0200 Subject: [PATCH 33/50] Properly clean up after async listeners. --- .../protocol/AsynchronousManager.java | 8 ++- .../protocol/async/AsyncFilterManager.java | 61 ++++++++++++++++--- .../protocol/async/AsyncListenerHandler.java | 21 ++++--- .../protocol/async/PacketSendingQueue.java | 18 ++++++ .../injector/PacketFilterManager.java | 7 ++- .../reflect/compiler/StructureCompiler.java | 2 +- 6 files changed, 96 insertions(+), 21 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java b/ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java index e12e73a3..eca8466a 100644 --- a/ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java @@ -24,7 +24,7 @@ public interface AsynchronousManager { * @param listener - the packet listener that will recieve these asynchronous events. * @return An asynchrouns handler. */ - public abstract AsyncListenerHandler registerAsyncHandler(Plugin plugin, PacketListener listener); + public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener); /** * Unregisters and closes the given asynchronous handler. @@ -32,6 +32,12 @@ public interface AsynchronousManager { */ public abstract void unregisterAsyncHandler(AsyncListenerHandler handler); + /** + * Unregisters every asynchronous handler associated with this plugin. + * @param plugin - the original plugin. + */ + public void unregisterAsyncHandlers(Plugin plugin); + /** * Retrieves a immutable set containing the ID of the sent server packets that will be * observed by the asynchronous listeners. diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index 8fd8319d..d42c5d43 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -1,5 +1,6 @@ package com.comphenix.protocol.async; +import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; @@ -11,6 +12,9 @@ import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.PacketFilterManager; +import com.comphenix.protocol.injector.PrioritizedListener; +import com.google.common.base.Objects; /** * Represents a filter manager for asynchronous packets. @@ -34,6 +38,9 @@ public class AsyncFilterManager implements AsynchronousManager { // Current packet index private AtomicInteger currentSendingIndex = new AtomicInteger(); + // Whether or not we're currently cleaning up + private volatile boolean cleaningUp; + public AsyncFilterManager(Logger logger, PacketStream packetStream) { this.serverQueue = new PacketSendingQueue(); this.clientQueue = new PacketSendingQueue(); @@ -46,14 +53,21 @@ public class AsyncFilterManager implements AsynchronousManager { } @Override - public AsyncListenerHandler registerAsyncHandler(Plugin plugin, PacketListener listener) { - AsyncListenerHandler handler = new AsyncListenerHandler(plugin, mainThread, this, listener); + public AsyncListenerHandler registerAsyncHandler(PacketListener listener) { + AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener); + + ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist(); + ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist(); // Add listener to either or both processing queue - if (hasValidWhitelist(listener.getSendingWhitelist())) - serverProcessingQueue.addListener(handler, listener.getSendingWhitelist()); - if (hasValidWhitelist(listener.getReceivingWhitelist())) - clientProcessingQueue.addListener(handler, listener.getReceivingWhitelist()); + if (hasValidWhitelist(sendingWhitelist)) { + PacketFilterManager.verifyWhitelist(listener, sendingWhitelist); + serverProcessingQueue.addListener(handler, sendingWhitelist); + } + if (hasValidWhitelist(receivingWhitelist)) { + PacketFilterManager.verifyWhitelist(listener, receivingWhitelist); + clientProcessingQueue.addListener(handler, receivingWhitelist); + } return handler; } @@ -76,10 +90,36 @@ public class AsyncFilterManager implements AsynchronousManager { PacketListener listener = handler.getAsyncListener(); // Just remove it from the queue(s) - if (hasValidWhitelist(listener.getSendingWhitelist())) - serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist()); - if (hasValidWhitelist(listener.getReceivingWhitelist())) - clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist()); + if (hasValidWhitelist(listener.getSendingWhitelist())) { + List removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist()); + + // We're already taking care of this, so don't do anything + if (!cleaningUp) + serverQueue.signalPacketUpdate(removed); + } + if (hasValidWhitelist(listener.getReceivingWhitelist())) { + List removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist()); + + if (!cleaningUp) + clientQueue.signalPacketUpdate(removed); + } + } + + @Override + public void unregisterAsyncHandlers(Plugin plugin) { + unregisterAsyncHandlers(serverProcessingQueue, plugin); + unregisterAsyncHandlers(clientProcessingQueue, plugin); + } + + private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) { + + // Iterate through every packet listener + for (PrioritizedListener listener : processingQueue.values()) { + // Remove the listener + if (Objects.equal(listener.getListener().getPlugin(), plugin)) { + unregisterAsyncHandler(listener.getListener()); + } + } } /** @@ -146,6 +186,7 @@ public class AsyncFilterManager implements AsynchronousManager { @Override public void cleanupAll() { + cleaningUp = true; serverProcessingQueue.cleanupAll(); serverQueue.cleanupAll(); } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java index 90461670..4ad4e3de 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -5,6 +5,7 @@ import java.util.logging.Level; import org.bukkit.plugin.Plugin; +import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; @@ -31,10 +32,7 @@ public class AsyncListenerHandler { // The packet listener private PacketListener listener; - - // The original plugin - private Plugin plugin; - + // The filter manager private AsyncFilterManager filterManager; @@ -44,13 +42,12 @@ public class AsyncListenerHandler { // Minecraft main thread private Thread mainThread; - public AsyncListenerHandler(Plugin plugin, Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) { + public AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) { if (filterManager == null) throw new IllegalArgumentException("filterManager cannot be NULL"); if (listener == null) throw new IllegalArgumentException("listener cannot be NULL"); - - this.plugin = plugin; + this.mainThread = mainThread; this.filterManager = filterManager; this.listener = listener; @@ -173,6 +170,14 @@ public class AsyncListenerHandler { } private String getPluginName() { - return plugin != null ? plugin.getName() : "UNKNOWN"; + return PacketAdapter.getPluginName(listener); + } + + /** + * Retrieve the plugin associated with this async listener. + * @return The plugin. + */ + public Plugin getPlugin() { + return listener != null ? listener.getPlugin() : null; } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java index 5028738f..911fc342 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java @@ -2,6 +2,9 @@ package com.comphenix.protocol.async; import java.io.IOException; import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.PriorityBlockingQueue; import com.comphenix.protocol.events.PacketEvent; @@ -46,6 +49,21 @@ class PacketSendingQueue { trySendPackets(); } + public synchronized void signalPacketUpdate(List packetsRemoved) { + + Set lookup = new HashSet(packetsRemoved); + + // Note that this is O(n), so it might be expensive + for (PacketEvent event : sendingQueue) { + if (lookup.contains(event.getPacketID())) { + event.getAsyncMarker().setProcessed(true); + } + } + + // This is likely to have changed the situation a bit + trySendPackets(); + } + /** * Attempt to send any remaining packets. */ diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 84dc69de..cdcd0647 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -212,9 +212,11 @@ public final class PacketFilterManager implements ProtocolManager { /** * Determine if the packet IDs in a whitelist is valid. + * @param listener - the listener that will be mentioned in the error. * @param whitelist - whitelist of packet IDs. + * @throws IllegalArgumentException If the whitelist is illegal. */ - private void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) { + public static void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) { for (Integer id : whitelist.getWhitelist()) { if (id >= 256 || id < 0) { throw new IllegalArgumentException(String.format("Invalid packet id %s in listener %s.", @@ -276,6 +278,9 @@ public final class PacketFilterManager implements ProtocolManager { removePacketListener(listener); } } + + // Do the same for the asynchronous events + asyncFilterManager.unregisterAsyncHandlers(plugin); } /** diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index 6940beea..837aa778 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -104,7 +104,7 @@ public final class StructureCompiler { } // Used to load classes - private static Method defineMethod; + private volatile static Method defineMethod; @SuppressWarnings("rawtypes") private Map compiledCache = new HashMap(); From 8a26d047b227d548a2c8380eeea25064c3d8b12a Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 23:48:09 +0200 Subject: [PATCH 34/50] Client packets are processed on the server, so they must be synchronized with the main thread. --- .../comphenix/protocol/ProtocolLibrary.java | 13 +++---- .../protocol/async/AsyncFilterManager.java | 32 ++++++++++++----- .../protocol/async/PacketSendingQueue.java | 36 ++++++++++++++----- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index f989b1ad..56668637 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -44,12 +44,11 @@ public class ProtocolLibrary extends JavaPlugin { // Structure compiler private BackgroundCompiler backgroundCompiler; - // Used to (mostly) clean up packets that have expired + // Used to clean up server packets that have expired. + // But mostly required to simulate recieving client packets. private int asyncPacketTask = -1; - - // Number of ticks between each cleanup. We don't need to do this often, - // as it's only indeeded to detected timeouts. - private static final int ASYNC_PACKET_DELAY = 10; + private int tickCounter = 0; + private static final int ASYNC_PACKET_DELAY = 1; @Override public void onLoad() { @@ -100,7 +99,9 @@ public class ProtocolLibrary extends JavaPlugin { @Override public void run() { AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager(); - manager.sendProcessedPackets(); + + // We KNOW we're on the main thread at the moment + manager.sendProcessedPackets(tickCounter++, true); } }, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY); diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index d42c5d43..f95a9257 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -42,8 +42,10 @@ public class AsyncFilterManager implements AsynchronousManager { private volatile boolean cleaningUp; public AsyncFilterManager(Logger logger, PacketStream packetStream) { - this.serverQueue = new PacketSendingQueue(); - this.clientQueue = new PacketSendingQueue(); + this.serverQueue = new PacketSendingQueue(false); + // Client packets must be synchronized + this.clientQueue = new PacketSendingQueue(true); + this.serverProcessingQueue = new PacketProcessingQueue(serverQueue); this.clientProcessingQueue = new PacketProcessingQueue(clientQueue); this.packetStream = packetStream; @@ -88,6 +90,7 @@ public class AsyncFilterManager implements AsynchronousManager { void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) { PacketListener listener = handler.getAsyncListener(); + boolean synchronusOK = onMainThread(); // Just remove it from the queue(s) if (hasValidWhitelist(listener.getSendingWhitelist())) { @@ -95,16 +98,24 @@ public class AsyncFilterManager implements AsynchronousManager { // We're already taking care of this, so don't do anything if (!cleaningUp) - serverQueue.signalPacketUpdate(removed); + serverQueue.signalPacketUpdate(removed, synchronusOK); } if (hasValidWhitelist(listener.getReceivingWhitelist())) { List removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist()); if (!cleaningUp) - clientQueue.signalPacketUpdate(removed); + clientQueue.signalPacketUpdate(removed, synchronusOK); } } + /** + * Determine if we're running on the main thread. + * @return TRUE if we are, FALSE otherwise. + */ + private boolean onMainThread() { + return Thread.currentThread().getId() == mainThread.getId(); + } + @Override public void unregisterAsyncHandlers(Plugin plugin) { unregisterAsyncHandlers(serverProcessingQueue, plugin); @@ -196,7 +207,7 @@ public class AsyncFilterManager implements AsynchronousManager { * @param packet - packet to signal. */ public void signalPacketUpdate(PacketEvent packet) { - getSendingQueue(packet).signalPacketUpdate(packet); + getSendingQueue(packet).signalPacketUpdate(packet, onMainThread()); } /** @@ -228,8 +239,13 @@ public class AsyncFilterManager implements AsynchronousManager { /** * Send any due packets, or clean up packets that have expired. */ - public void sendProcessedPackets() { - clientQueue.trySendPackets(); - serverQueue.trySendPackets(); + public void sendProcessedPackets(int tickCounter, boolean onMainThread) { + + // The server queue is unlikely to need checking that often + if (tickCounter % 10 == 0) { + serverQueue.trySendPackets(onMainThread); + } + + clientQueue.trySendPackets(onMainThread); } } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java index 911fc342..5c8ddfb9 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java @@ -20,8 +20,12 @@ class PacketSendingQueue { private PriorityBlockingQueue sendingQueue; - public PacketSendingQueue() { - sendingQueue = new PriorityBlockingQueue(INITIAL_CAPACITY, new Comparator() { + // Whether or not packet transmission can only occur on the main thread + private final boolean synchronizeMain; + + public PacketSendingQueue(boolean synchronizeMain) { + this.synchronizeMain = synchronizeMain; + this.sendingQueue = new PriorityBlockingQueue(INITIAL_CAPACITY, new Comparator() { // Compare using the async marker @Override public int compare(PacketEvent o1, PacketEvent o2) { @@ -43,13 +47,13 @@ class PacketSendingQueue { /** * Invoked when one of the packets have finished processing. */ - public synchronized void signalPacketUpdate(PacketEvent packetUpdated) { + public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) { // Mark this packet as finished packetUpdated.getAsyncMarker().setProcessed(true); - trySendPackets(); + trySendPackets(onMainThread); } - public synchronized void signalPacketUpdate(List packetsRemoved) { + public synchronized void signalPacketUpdate(List packetsRemoved, boolean onMainThread) { Set lookup = new HashSet(packetsRemoved); @@ -60,14 +64,19 @@ class PacketSendingQueue { } } - // This is likely to have changed the situation a bit - trySendPackets(); + // This is likely to have changed the situation a bit + trySendPackets(onMainThread); } /** * Attempt to send any remaining packets. */ - public synchronized void trySendPackets() { + public void trySendPackets(boolean onMainThread) { + + // Abort if we're not on the main thread + if (synchronizeMain && !onMainThread) + return; + // Transmit as many packets as we can while (true) { PacketEvent current = sendingQueue.peek(); @@ -92,7 +101,7 @@ class PacketSendingQueue { /** * Send every packet, regardless of the processing state. */ - public synchronized void forceSend() { + private void forceSend() { while (true) { PacketEvent current = sendingQueue.poll(); @@ -104,6 +113,14 @@ class PacketSendingQueue { } } + /** + * Whether or not the packet transmission must synchronize with the main thread. + * @return TRUE if it must, FALSE otherwise. + */ + public boolean isSynchronizeMain() { + return synchronizeMain; + } + /** * Transmit a packet, if it hasn't already. * @param event - the packet to transmit. @@ -128,6 +145,7 @@ class PacketSendingQueue { * Automatically transmits every delayed packet. */ public void cleanupAll() { + // Note that the cleanup itself will always occur on the main thread forceSend(); } } From 63197bbbd5fd9666251b782ae07cf7c5c2d1ee40 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 29 Sep 2012 23:52:28 +0200 Subject: [PATCH 35/50] Add the other cases where we need to know if we're on the main thread or not. --- .../comphenix/protocol/async/AsyncFilterManager.java | 4 +++- .../protocol/async/PacketProcessingQueue.java | 10 ++++++---- .../comphenix/protocol/async/PacketSendingQueue.java | 12 ++++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index f95a9257..569e95d8 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -143,7 +143,9 @@ public class AsyncFilterManager implements AsynchronousManager { // Start the process getSendingQueue(syncPacket).enqueue(newEvent); - getProcessingQueue(syncPacket).enqueue(newEvent); + + // We know this is occuring on the main thread, to pass TRUE + getProcessingQueue(syncPacket).enqueue(newEvent, true); } @Override diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index d2ffe29e..af25eaa1 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -53,14 +53,15 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap(INITIAL_CAPACITY, new Comparator() { @@ -46,6 +50,8 @@ class PacketSendingQueue { /** * Invoked when one of the packets have finished processing. + * @param packetUpdated - the packet that has now been updated. + * @param onMainThread - whether or not this is occuring on the main thread. */ public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) { // Mark this packet as finished @@ -53,6 +59,11 @@ class PacketSendingQueue { trySendPackets(onMainThread); } + /*** + * Invoked when a list of packet IDs are no longer associated with any listeners. + * @param packetsRemoved - packets that no longer have any listeners. + * @param onMainThread - whether or not this is occuring on the main thread. + */ public synchronized void signalPacketUpdate(List packetsRemoved, boolean onMainThread) { Set lookup = new HashSet(packetsRemoved); @@ -70,6 +81,7 @@ class PacketSendingQueue { /** * Attempt to send any remaining packets. + * @param onMainThread - whether or not this is occuring on the main thread. */ public void trySendPackets(boolean onMainThread) { From 711990fa15c66d18301b91c6a502ec5e579e49b0 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 30 Sep 2012 00:13:36 +0200 Subject: [PATCH 36/50] Simplify the act of creating an asynchronous listener. --- .../comphenix/protocol/ProtocolLibrary.java | 3 ++- .../protocol/async/AsyncFilterManager.java | 19 ++++++++++++++++++- .../protocol/async/AsyncListenerHandler.java | 11 +++++++++++ .../injector/PacketFilterManager.java | 5 +++-- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index 56668637..a3a7e31d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -53,7 +53,8 @@ public class ProtocolLibrary extends JavaPlugin { @Override public void onLoad() { logger = getLoggerSafely(); - protocolManager = new PacketFilterManager(getClassLoader(), logger); + protocolManager = new PacketFilterManager( + getClassLoader(), getServer().getScheduler(), logger); } @Override diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index 569e95d8..c10770b5 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -6,6 +6,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.PacketStream; @@ -35,20 +36,27 @@ public class AsyncFilterManager implements AsynchronousManager { // The likely main thread private Thread mainThread; + // Default scheduler + private BukkitScheduler scheduler; + // Current packet index private AtomicInteger currentSendingIndex = new AtomicInteger(); // Whether or not we're currently cleaning up private volatile boolean cleaningUp; - public AsyncFilterManager(Logger logger, PacketStream packetStream) { + public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, PacketStream packetStream) { + + // Server packets are synchronized already this.serverQueue = new PacketSendingQueue(false); // Client packets must be synchronized this.clientQueue = new PacketSendingQueue(true); this.serverProcessingQueue = new PacketProcessingQueue(serverQueue); this.clientProcessingQueue = new PacketProcessingQueue(clientQueue); + this.packetStream = packetStream; + this.scheduler = scheduler; this.logger = logger; this.mainThread = Thread.currentThread(); @@ -158,6 +166,15 @@ public class AsyncFilterManager implements AsynchronousManager { return clientProcessingQueue.keySet(); } + /** + * Used to create a default asynchronous task. + * @param plugin - the calling plugin. + * @param runnable - the runnable. + */ + public void scheduleAsyncTask(Plugin plugin, Runnable runnable) { + scheduler.scheduleAsyncDelayedTask(plugin, runnable); + } + @Override public boolean hasAsynchronousListeners(PacketEvent packet) { return getProcessingQueue(packet).getListener(packet.getPacketID()).size() > 0; diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java index 4ad4e3de..5687e8ca 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -8,6 +8,7 @@ import org.bukkit.plugin.Plugin; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; +import com.sun.org.apache.bcel.internal.generic.GETSTATIC; /** * Represents a handler for an asynchronous event. @@ -180,4 +181,14 @@ public class AsyncListenerHandler { public Plugin getPlugin() { return listener != null ? listener.getPlugin() : null; } + + /** + * Start the asynchronous listener using the Bukkit scheduler. + */ + public void start() { + if (listener.getPlugin() == null) + throw new IllegalArgumentException("Cannot start task without a valid plugin."); + + filterManager.scheduleAsyncTask(listener.getPlugin(), getListenerLoop()); + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index cdcd0647..22a5b232 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -44,6 +44,7 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; +import org.bukkit.scheduler.BukkitScheduler; import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.ProtocolManager; @@ -121,7 +122,7 @@ public final class PacketFilterManager implements ProtocolManager { /** * Only create instances of this class if protocol lib is disabled. */ - public PacketFilterManager(ClassLoader classLoader, Logger logger) { + public PacketFilterManager(ClassLoader classLoader, BukkitScheduler scheduler, Logger logger) { if (logger == null) throw new IllegalArgumentException("logger cannot be NULL."); if (classLoader == null) @@ -132,7 +133,7 @@ public final class PacketFilterManager implements ProtocolManager { this.classLoader = classLoader; this.logger = logger; this.packetInjector = new PacketInjector(classLoader, this, connectionLookup); - this.asyncFilterManager = new AsyncFilterManager(logger, this); + this.asyncFilterManager = new AsyncFilterManager(logger, scheduler, this); } catch (IllegalAccessException e) { logger.log(Level.SEVERE, "Unable to initialize packet injector.", e); } From c77103246e4ebe48f2fba6f7224d019fef9cefb4 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 30 Sep 2012 00:25:04 +0200 Subject: [PATCH 37/50] Small bug fixes. --- ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java | 2 +- .../src/com/comphenix/protocol/async/AsyncFilterManager.java | 4 +++- .../com/comphenix/protocol/async/AsyncListenerHandler.java | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index a3a7e31d..2305cc7f 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -92,7 +92,7 @@ public class ProtocolLibrary extends JavaPlugin { private void createAsyncTask(Server server) { try { - if (asyncPacketTask < 0) + if (asyncPacketTask >= 0) throw new IllegalStateException("Async task has already been created"); // Attempt to create task diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index c10770b5..974125b6 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -1,5 +1,6 @@ package com.comphenix.protocol.async; +import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -177,7 +178,8 @@ public class AsyncFilterManager implements AsynchronousManager { @Override public boolean hasAsynchronousListeners(PacketEvent packet) { - return getProcessingQueue(packet).getListener(packet.getPacketID()).size() > 0; + Collection list = getProcessingQueue(packet).getListener(packet.getPacketID()); + return list != null && list.size() > 0; } /** diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java index 5687e8ca..6a0e6c0b 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -8,7 +8,6 @@ import org.bukkit.plugin.Plugin; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; -import com.sun.org.apache.bcel.internal.generic.GETSTATIC; /** * Represents a handler for an asynchronous event. From 0cff9d12432a9d110a608e29b006a49e64fc42f9 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 30 Sep 2012 00:51:59 +0200 Subject: [PATCH 38/50] Ensure that asynchronous listeners are run, even if they have no accompanying synchronous listener. --- .../protocol/async/AsyncFilterManager.java | 39 ++++++++++-- .../protocol/async/AsyncListenerHandler.java | 17 +++++ .../protocol/async/NullPacketListener.java | 62 +++++++++++++++++++ 3 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/async/NullPacketListener.java diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index 974125b6..7d7ee841 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -11,6 +11,7 @@ import org.bukkit.scheduler.BukkitScheduler; import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.PacketStream; +import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; @@ -31,7 +32,6 @@ public class AsyncFilterManager implements AsynchronousManager { private PacketProcessingQueue clientProcessingQueue; private PacketSendingQueue clientQueue; - private PacketStream packetStream; private Logger logger; // The likely main thread @@ -40,13 +40,16 @@ public class AsyncFilterManager implements AsynchronousManager { // Default scheduler private BukkitScheduler scheduler; + // Our protocol manager + private ProtocolManager manager; + // Current packet index private AtomicInteger currentSendingIndex = new AtomicInteger(); // Whether or not we're currently cleaning up private volatile boolean cleaningUp; - public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, PacketStream packetStream) { + public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, ProtocolManager manager) { // Server packets are synchronized already this.serverQueue = new PacketSendingQueue(false); @@ -56,8 +59,8 @@ public class AsyncFilterManager implements AsynchronousManager { this.serverProcessingQueue = new PacketProcessingQueue(serverQueue); this.clientProcessingQueue = new PacketProcessingQueue(clientQueue); - this.packetStream = packetStream; this.scheduler = scheduler; + this.manager = manager; this.logger = logger; this.mainThread = Thread.currentThread(); @@ -70,19 +73,39 @@ public class AsyncFilterManager implements AsynchronousManager { ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist(); ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist(); + // We need a synchronized listener to get the ball rolling + boolean needNoOp = true; + // Add listener to either or both processing queue if (hasValidWhitelist(sendingWhitelist)) { PacketFilterManager.verifyWhitelist(listener, sendingWhitelist); serverProcessingQueue.addListener(handler, sendingWhitelist); + needNoOp &= hasPacketListener(sendingWhitelist); } + if (hasValidWhitelist(receivingWhitelist)) { PacketFilterManager.verifyWhitelist(listener, receivingWhitelist); clientProcessingQueue.addListener(handler, receivingWhitelist); + needNoOp &= hasPacketListener(receivingWhitelist); + } + + if (needNoOp) { + handler.setNullPacketListener(new NullPacketListener(listener)); + manager.addPacketListener(handler.getNullPacketListener()); } return handler; } + /** + * Determine if the given packets are represented. + * @param whitelist - list of packets. + * @return TRUE if they are all registered, FALSE otherwise. + */ + private boolean hasPacketListener(ListeningWhitelist whitelist) { + return manager.getSendingFilters().containsAll(whitelist.getWhitelist()); + } + private boolean hasValidWhitelist(ListeningWhitelist whitelist) { return whitelist != null && whitelist.getWhitelist().size() > 0; } @@ -101,6 +124,11 @@ public class AsyncFilterManager implements AsynchronousManager { PacketListener listener = handler.getAsyncListener(); boolean synchronusOK = onMainThread(); + // Unregister null packet listeners + if (handler.getNullPacketListener() != null) { + manager.removePacketListener(handler.getNullPacketListener()); + } + // Just remove it from the queue(s) if (hasValidWhitelist(listener.getSendingWhitelist())) { List removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist()); @@ -109,6 +137,7 @@ public class AsyncFilterManager implements AsynchronousManager { if (!cleaningUp) serverQueue.signalPacketUpdate(removed, synchronusOK); } + if (hasValidWhitelist(listener.getReceivingWhitelist())) { List removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist()); @@ -203,12 +232,12 @@ public class AsyncFilterManager implements AsynchronousManager { // Helper method private AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) { - return new AsyncMarker(packetStream, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta); + return new AsyncMarker(manager, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta); } @Override public PacketStream getPacketStream() { - return packetStream; + return manager; } @Override diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java index 6a0e6c0b..e4cd15fc 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -35,6 +35,7 @@ public class AsyncListenerHandler { // The filter manager private AsyncFilterManager filterManager; + private NullPacketListener nullPacketListener; // List of queued packets private ArrayBlockingQueue queuedPackets = new ArrayBlockingQueue(DEFAULT_CAPACITY); @@ -61,6 +62,22 @@ public class AsyncListenerHandler { return listener; } + /** + * Set the synchronized listener that has been automatically created. + * @param nullPacketListener - automatically created listener. + */ + void setNullPacketListener(NullPacketListener nullPacketListener) { + this.nullPacketListener = nullPacketListener; + } + + /** + * Retrieve the synchronized listener that was automatically created. + * @return Automatically created listener. + */ + PacketListener getNullPacketListener() { + return nullPacketListener; + } + /** * Cancel the handler. */ diff --git a/ProtocolLib/src/com/comphenix/protocol/async/NullPacketListener.java b/ProtocolLib/src/com/comphenix/protocol/async/NullPacketListener.java new file mode 100644 index 00000000..d02559d3 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/async/NullPacketListener.java @@ -0,0 +1,62 @@ +package com.comphenix.protocol.async; + +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; + +/** + * Represents a NO OPERATION listener. + * + * @author Kristian + */ +class NullPacketListener implements PacketListener { + + private ListeningWhitelist sendingWhitelist; + private ListeningWhitelist receivingWhitelist; + private Plugin plugin; + + /** + * Create a no-op listener with the same whitelist and plugin as the given listener. + * @param original - the packet listener to copy. + */ + public NullPacketListener(PacketListener original) { + this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist()); + this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist()); + this.plugin = original.getPlugin(); + } + + @Override + public void onPacketSending(PacketEvent event) { + // NULL + } + + @Override + public void onPacketReceiving(PacketEvent event) { + // NULL + } + + @Override + public ListeningWhitelist getSendingWhitelist() { + return sendingWhitelist; + } + + @Override + public ListeningWhitelist getReceivingWhitelist() { + return receivingWhitelist; + } + + private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) { + if (whitelist != null) + return new ListeningWhitelist(priority, whitelist.getWhitelist()); + else + return null; + } + + @Override + public Plugin getPlugin() { + return plugin; + } +} From 801bf81f153044b5621c916cb33dc5234173ab80 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 30 Sep 2012 03:24:13 +0200 Subject: [PATCH 39/50] Incredibly difficult bug to track down. Forgot to remove the "override" flag on cancelled packets when they're sent again. --- .../protocol/async/AsyncFilterManager.java | 8 +++--- .../protocol/async/PacketSendingQueue.java | 3 +- .../injector/PacketFilterManager.java | 3 ++ .../protocol/injector/PacketInjector.java | 28 +++++++++++++++++-- .../protocol/injector/ReadPacketModifier.java | 17 +++++++++++ 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index 7d7ee841..a09bfd13 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -74,22 +74,22 @@ public class AsyncFilterManager implements AsynchronousManager { ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist(); // We need a synchronized listener to get the ball rolling - boolean needNoOp = true; + boolean hasListener = true; // Add listener to either or both processing queue if (hasValidWhitelist(sendingWhitelist)) { PacketFilterManager.verifyWhitelist(listener, sendingWhitelist); serverProcessingQueue.addListener(handler, sendingWhitelist); - needNoOp &= hasPacketListener(sendingWhitelist); + hasListener &= hasPacketListener(sendingWhitelist); } if (hasValidWhitelist(receivingWhitelist)) { PacketFilterManager.verifyWhitelist(listener, receivingWhitelist); clientProcessingQueue.addListener(handler, receivingWhitelist); - needNoOp &= hasPacketListener(receivingWhitelist); + hasListener &= hasPacketListener(receivingWhitelist); } - if (needNoOp) { + if (!hasListener) { handler.setNullPacketListener(new NullPacketListener(listener)); manager.addPacketListener(handler.getNullPacketListener()); } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java index be20847c..4d25a667 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java @@ -97,8 +97,9 @@ class PacketSendingQueue { AsyncMarker marker = current.getAsyncMarker(); if (marker.isProcessed() || marker.hasExpired()) { - if (marker.isProcessed() && !current.isCancelled()) + if (marker.isProcessed() && !current.isCancelled()) { sendPacket(current); + } sendingQueue.poll(); continue; diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 22a5b232..1dfb5bf9 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -399,6 +399,9 @@ public final class PacketFilterManager implements ProtocolManager { PlayerInjector injector = getInjector(sender); Packet mcPacket = packet.getHandle(); + // Make sure the packet isn't cancelled + packetInjector.undoCancel(packet.getID(), mcPacket); + if (filters) { mcPacket = injector.handlePacketRecieved(mcPacket); } diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java index ebdfd578..998eb5a7 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java @@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.entity.Player; @@ -52,6 +53,9 @@ class PacketInjector { // Allows us to determine the sender private Map playerLookup; + // Allows us to look up read packet injectors + private Map readModifier; + // Class loader private ClassLoader classLoader; @@ -61,9 +65,24 @@ class PacketInjector { this.classLoader = classLoader; this.manager = manager; this.playerLookup = playerLookup; + this.readModifier = new ConcurrentHashMap(); initialize(); } + /** + * Undo a packet cancel. + * @param id - the id of the packet. + * @param packet - packet to uncancel. + */ + public void undoCancel(Integer id, Packet packet) { + ReadPacketModifier modifier = readModifier.get(id); + + // Cancelled packets are represented with NULL + if (modifier != null && modifier.getOverride(packet) == null) { + modifier.removeOverride(packet); + } + } + private void initialize() throws IllegalAccessException { if (intHashMap == null) { // We're looking for the first static field with a Minecraft-object. This should be a IntHashMap. @@ -109,10 +128,12 @@ class PacketInjector { ex.setClassLoader(classLoader); Class proxy = ex.createClass(); + // Create the proxy handler + ReadPacketModifier modifier = new ReadPacketModifier(packetID, this); + readModifier.put(packetID, modifier); + // Add a static reference - Enhancer.registerStaticCallbacks(proxy, new Callback[] { - new ReadPacketModifier(packetID, this) - }); + Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier }); try { // Override values @@ -147,6 +168,7 @@ class PacketInjector { putMethod.invoke(intHashMap, packetID, old); previous.remove(packetID); + readModifier.remove(packetID); registry.remove(proxy); overwritten.remove(packetID); return true; diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java b/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java index d8400125..ba489b96 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java @@ -45,6 +45,23 @@ class ReadPacketModifier implements MethodInterceptor { this.packetID = packetID; this.packetInjector = packetInjector; } + + /** + * Remove any packet overrides. + * @param packet - the packet to rever + */ + public void removeOverride(Packet packet) { + override.remove(packet); + } + + /** + * Retrieve the packet that overrides the methods of the given packet. + * @param packet - the given packet. + * @return Overriden object. + */ + public Object getOverride(Packet packet) { + return override.get(packet); + } @Override public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { From 1c41f83305817e14c9a9d2af2458ab24aaecc90c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 30 Sep 2012 03:39:26 +0200 Subject: [PATCH 40/50] Fixed (probably) a problem reported by Milkywayz. Relevant crash dump: --- .../src/com/comphenix/protocol/injector/PlayerInjector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index 9bacc311..b92fd3c0 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -223,7 +223,7 @@ abstract class PlayerInjector { Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass()); // Make sure we're listening - if (sendingFilters.contains(id)) { + if (id != null && sendingFilters.contains(id)) { // A packet has been sent guys! PacketContainer container = new PacketContainer(id, packet); PacketEvent event = PacketEvent.fromServer(manager, container, player); From 6063f437fd459afd81b797c9b789bc8f266cb9fd Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 30 Sep 2012 04:24:27 +0200 Subject: [PATCH 41/50] Another diffcult to track down bug. --- .../comphenix/protocol/async/AsyncMarker.java | 53 +++++++++++++++++++ .../protocol/async/PacketSendingQueue.java | 25 ++++++--- .../protocol/injector/ReadPacketModifier.java | 11 ++-- .../protocol/reflect/FuzzyReflection.java | 23 ++++++++ 4 files changed, 103 insertions(+), 9 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java index 51ac432d..be543e9d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java @@ -3,11 +3,17 @@ package com.comphenix.protocol.async; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Iterator; +import java.util.List; + +import net.minecraft.server.Packet; import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.PrioritizedListener; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FuzzyReflection; import com.google.common.primitives.Longs; /** @@ -58,6 +64,10 @@ public class AsyncMarker implements Serializable, Comparable { // Whether or not the asynchronous processing itself should be cancelled private volatile boolean asyncCancelled; + + // Determine if Minecraft processes this packet asynchronously + private static Method isMinecraftAsync; + private static boolean alwaysSync; /** * Create a container for asyncronous packets. @@ -240,6 +250,49 @@ public class AsyncMarker implements Serializable, Comparable { } } + /** + * Determine if Minecraft allows asynchronous processing of this packet. + * @return TRUE if it does, FALSE otherwise. + */ + public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException { + + if (isMinecraftAsync == null) { + try { + isMinecraftAsync = FuzzyReflection.fromClass(Packet.class).getMethodByName("a_.*"); + } catch (RuntimeException e) { + // This will occur in 1.2.5 (or possibly in later versions) + List methods = FuzzyReflection.fromClass(Packet.class). + getMethodListByParameters(boolean.class, new Class[] {}); + + // Try to look for boolean methods + if (methods.size() == 2) { + isMinecraftAsync = methods.get(1); + } else if (methods.size() == 1) { + // We're in 1.2.5 + alwaysSync = true; + } else { + System.err.println("Cannot determine asynchronous state of packets!"); + alwaysSync = true; + } + } + } + + if (alwaysSync) { + return false; + } else { + try { + // Wrap exceptions + return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle()); + } catch (IllegalArgumentException e) { + throw new FieldAccessException("Illegal argument", e); + } catch (IllegalAccessException e) { + throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e); + } catch (InvocationTargetException e) { + throw new FieldAccessException("Minecraft error", e); + } + } + } + @Override public int compareTo(AsyncMarker o) { if (o == null) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java index 4d25a667..500cf2c3 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java @@ -8,6 +8,7 @@ import java.util.Set; import java.util.concurrent.PriorityBlockingQueue; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.reflect.FieldAccessException; import com.google.common.collect.ComparisonChain; /** @@ -84,18 +85,30 @@ class PacketSendingQueue { * @param onMainThread - whether or not this is occuring on the main thread. */ public void trySendPackets(boolean onMainThread) { - - // Abort if we're not on the main thread - if (synchronizeMain && !onMainThread) - return; - + // Transmit as many packets as we can while (true) { PacketEvent current = sendingQueue.peek(); - + if (current != null) { AsyncMarker marker = current.getAsyncMarker(); + // Abort if we're not on the main thread + if (synchronizeMain) { + try { + boolean wantAsync = marker.isMinecraftAsync(current); + boolean wantSync = !wantAsync; + + // Quit if we haven't fulfilled our promise + if ((onMainThread && wantAsync) || (!onMainThread && wantSync)) + return; + + } catch (FieldAccessException e) { + e.printStackTrace(); + return; + } + } + if (marker.isProcessed() || marker.hasExpired()) { if (marker.isProcessed() && !current.isCancelled()) { sendPacket(current); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java b/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java index ba489b96..d2e71c60 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java @@ -77,9 +77,14 @@ class ReadPacketModifier implements MethodInterceptor { if (override.containsKey(thisObj)) { Object overridenObject = override.get(thisObj); - // Cancel EVERYTHING, including "processPacket" - if (overridenObject == null) - return null; + // This packet has been cancelled + if (overridenObject == null) { + // So, cancel all void methods + if (method.getReturnType().equals(Void.TYPE)) + return null; + else // Revert to normal for everything else + overridenObject = thisObj; + } returnValue = proxy.invokeSuper(overridenObject, args); } else { diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/FuzzyReflection.java b/ProtocolLib/src/com/comphenix/protocol/reflect/FuzzyReflection.java index a8e3eb6b..ae30b604 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/FuzzyReflection.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/FuzzyReflection.java @@ -19,8 +19,10 @@ package com.comphenix.protocol.reflect; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import java.util.regex.Pattern; @@ -96,6 +98,7 @@ public class FuzzyReflection { * Retrieves a method by looking at its name. * @param nameRegex - regular expression that will match method names. * @return The first method that satisfies the regular expression. + * @throws RuntimeException If the method cannot be found. */ public Method getMethodByName(String nameRegex) { @@ -151,6 +154,26 @@ public class FuzzyReflection { throw new RuntimeException("Unable to find " + name + " in " + source.getName()); } + /** + * Retrieves every method that has the given parameter types and return type. + * @param returnType - return type of the method to find. + * @param args - parameter types of the method to find. + * @return Every method that satisfies the given constraints. + */ + public List getMethodListByParameters(Class returnType, Class[] args) { + + List methods = new ArrayList(); + + // Find the correct method to call + for (Method method : getMethods()) { + if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) { + methods.add(method); + } + } + + return methods; + } + /** * Retrieves a field by name. * @param nameRegex - regular expression that will match a field name. From e666d17dc2aa301a26d553436e0fff98db598879 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 30 Sep 2012 04:25:56 +0200 Subject: [PATCH 42/50] Don't try to find the isAsyncPacket method over and over again. --- ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java index be543e9d..3f063eb8 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java @@ -256,7 +256,7 @@ public class AsyncMarker implements Serializable, Comparable { */ public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException { - if (isMinecraftAsync == null) { + if (isMinecraftAsync == null && !alwaysSync) { try { isMinecraftAsync = FuzzyReflection.fromClass(Packet.class).getMethodByName("a_.*"); } catch (RuntimeException e) { From 73005e032bd4566af002e82f4e9c7db8d38798a3 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 30 Sep 2012 04:42:58 +0200 Subject: [PATCH 43/50] Forgot to release a semaphore lock. --- .../protocol/async/PacketProcessingQueue.java | 3 ++- .../protocol/injector/PacketFilterManager.java | 13 ++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index af25eaa1..a28788ce 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -97,7 +97,8 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap Date: Sun, 30 Sep 2012 04:44:57 +0200 Subject: [PATCH 44/50] Increase the maximum. --- .../src/com/comphenix/protocol/async/PacketProcessingQueue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java index a28788ce..37349e83 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java @@ -19,7 +19,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap Date: Sun, 30 Sep 2012 04:49:54 +0200 Subject: [PATCH 45/50] Bumping to 1.2.0 --- ProtocolLib/src/plugin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/plugin.yml b/ProtocolLib/src/plugin.yml index 91fbefd4..f0fd2022 100644 --- a/ProtocolLib/src/plugin.yml +++ b/ProtocolLib/src/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.1.0 +version: 1.2.0 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 3ad24921d919bd5671286a2be81e5dcb11401913 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 1 Oct 2012 00:16:06 +0200 Subject: [PATCH 46/50] Ensure that hook method #3 receieves Packet0KeepAlive. --- .../comphenix/protocol/ProtocolLibrary.java | 3 +- .../injector/InjectedServerConnection.java | 189 ++++++++++++++++++ .../protocol/injector/MinecraftRegistry.java | 2 +- .../injector/NetworkServerInjector.java | 49 ++++- .../injector/PacketFilterManager.java | 16 +- .../protocol/injector/PlayerInjector.java | 1 + .../protocol/injector/ReplacedArrayList.java | 136 +++++++++++++ .../protocol/reflect/FuzzyReflection.java | 84 +++++++- .../protocol/reflect/VolatileField.java | 7 + 9 files changed, 467 insertions(+), 20 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/InjectedServerConnection.java create mode 100644 ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java diff --git a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java index 2305cc7f..1b8a7132 100644 --- a/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/com/comphenix/protocol/ProtocolLibrary.java @@ -53,8 +53,7 @@ public class ProtocolLibrary extends JavaPlugin { @Override public void onLoad() { logger = getLoggerSafely(); - protocolManager = new PacketFilterManager( - getClassLoader(), getServer().getScheduler(), logger); + protocolManager = new PacketFilterManager(getClassLoader(), getServer(), logger); } @Override diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/InjectedServerConnection.java b/ProtocolLib/src/com/comphenix/protocol/injector/InjectedServerConnection.java new file mode 100644 index 00000000..ea2576db --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/InjectedServerConnection.java @@ -0,0 +1,189 @@ +package com.comphenix.protocol.injector; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Server; + +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.VolatileField; + +/** + * Used to ensure that the 1.3 server is referencing the correct server handler. + * + * @author Kristian + */ +class InjectedServerConnection { + + private static Field listenerThreadField; + private static Field minecraftServerField; + private static Method serverConnectionMethod; + private static Field listField; + + private List listFields; + private List> replacedLists; + + private Server server; + private Logger logger; + private boolean hasAttempted; + private boolean hasSuccess; + + private Object minecraftServer = null; + + public InjectedServerConnection(Logger logger, Server server) { + this.listFields = new ArrayList(); + this.replacedLists = new ArrayList>(); + this.logger = logger; + this.server = server; + } + + public void injectList() { + + // Only execute this method once + if (!hasAttempted) + hasAttempted = true; + else + return; + + if (minecraftServerField == null) + minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer"); + + try { + minecraftServer = FieldUtils.readField(minecraftServerField, server, true); + } catch (IllegalAccessException e1) { + logger.log(Level.WARNING, "Cannot extract minecraft server from Bukkit."); + return; + } + + try { + if (serverConnectionMethod == null) + serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()). + getMethodByParameters("getServerConnection", ".*ServerConnection", new String[] {}); + // We're using Minecraft 1.3.1 + injectServerConnection(); + + } catch (RuntimeException e) { + + // Minecraft 1.2.5 or lower + injectListenerThread(); + } + } + + private void injectListenerThread() { + + try { + + if (listenerThreadField == null) + listenerThreadField = FuzzyReflection.fromClass(minecraftServerField.getType()). + getFieldByType(".*NetworkListenThread"); + } catch (RuntimeException e) { + logger.log(Level.SEVERE, "Cannot find listener thread in MinecraftServer."); + return; + } + + Object listenerThread = null; + + // Attempt to get the thread + try { + listenerThread = listenerThreadField.get(minecraftServer); + } catch (Exception e) { + logger.log(Level.WARNING, "Unable to read the listener thread."); + return; + } + + // Ok, great. Get every list field + List lists = FuzzyReflection.fromClass(listenerThreadField.getType()).getFieldListByType(List.class); + + for (Field list : lists) { + injectIntoList(listenerThread, list); + } + + hasSuccess = true; + } + + private void injectServerConnection() { + + Object serverConnection = null; + + // Careful - we might fail + try { + serverConnection = serverConnectionMethod.invoke(minecraftServer); + } catch (Exception ex) { + logger.log(Level.WARNING, "Unable to retrieve server connection", ex); + return; + } + + if (listField == null) + listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). + getFieldByType("serverConnection", List.class); + injectIntoList(serverConnection, listField); + hasSuccess = true; + } + + @SuppressWarnings("unchecked") + private void injectIntoList(Object instance, Field field) { + VolatileField listFieldRef = new VolatileField(listField, instance, true); + List list = (List) listFieldRef.getValue(); + + // Careful not to inject twice + if (list instanceof ReplacedArrayList) { + replacedLists.add((ReplacedArrayList) list); + } else { + replacedLists.add(new ReplacedArrayList(list)); + listFieldRef.setValue(replacedLists.get(0)); + listFields.add(listFieldRef); + } + } + + /** + * Replace the server handler instance kept by the "keep alive" object. + * @param oldHandler - old server handler. + * @param newHandler - new, proxied server handler. + */ + public void replaceServerHandler(Object oldHandler, Object newHandler) { + if (!hasAttempted) { + injectList(); + } + + if (hasSuccess) { + for (ReplacedArrayList replacedList : replacedLists) { + replacedList.addMapping(oldHandler, newHandler); + } + } + } + + /** + * Revert to the old vanilla server handler, if it has been replaced. + * @param oldHandler - old vanilla server handler. + */ + public void revertServerHandler(Object oldHandler) { + if (hasSuccess) { + for (ReplacedArrayList replacedList : replacedLists) { + replacedList.removeMapping(oldHandler); + } + } + } + + /** + * Undoes everything. + */ + public void cleanupAll() { + if (replacedLists.size() > 0) { + // Repair the underlying lists + for (ReplacedArrayList replacedList : replacedLists) { + replacedList.revertAll(); + } + for (VolatileField field : listFields) { + field.revertValue(); + } + + listFields.clear(); + replacedLists.clear(); + } + } +} diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java b/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java index c66ef956..2e372e1e 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java @@ -49,7 +49,7 @@ class MinecraftRegistry { // Initialize it, if we haven't already if (packetToID == null) { try { - Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("java\\.util\\.Map"); + Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("packetsField", Map.class); packetToID = (Map) FieldUtils.readStaticField(packetsField, true); } catch (IllegalAccessException e) { diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java index ba9d6ee0..fb901a1a 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/NetworkServerInjector.java @@ -13,8 +13,10 @@ import net.sf.cglib.proxy.MethodProxy; import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.instances.CollectionGenerator; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.ExistingGenerator; @@ -28,9 +30,14 @@ import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; public class NetworkServerInjector extends PlayerInjector { private static Method sendPacketMethod; + + private StructureModifier serverHandlerModifier; + private InjectedServerConnection serverInjection; - public NetworkServerInjector(Player player, PacketFilterManager manager, Set sendingFilters) throws IllegalAccessException { + public NetworkServerInjector(Player player, PacketFilterManager manager, + Set sendingFilters, InjectedServerConnection serverInjection) throws IllegalAccessException { super(player, manager, sendingFilters); + this.serverInjection = serverInjection; } @Override @@ -41,6 +48,8 @@ public class NetworkServerInjector extends PlayerInjector { if (hasInitialized) { if (sendPacketMethod == null) sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*"); + if (serverHandlerModifier == null) + serverHandlerModifier = new StructureModifier(serverHandler.getClass(), null, false); } } @@ -98,12 +107,8 @@ public class NetworkServerInjector extends PlayerInjector { } } - // Delegate to our underlying class - try { - return method.invoke(serverHandler, args); - } catch (InvocationTargetException e) { - throw e.getCause(); - } + // Call the method directly + return proxy.invokeSuper(obj, args); } }); @@ -114,22 +119,46 @@ public class NetworkServerInjector extends PlayerInjector { CollectionGenerator.INSTANCE); Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass); - + serverInjection.replaceServerHandler(serverHandler, proxyObject); + // Inject it now if (proxyObject != null) { + copyTo(serverHandler, proxyObject); serverHandlerRef.setValue(proxyObject); } else { throw new RuntimeException( "Cannot hook player: Unable to find a valid constructor for the NetServerHandler object."); } } - + + /** + * Copy every field in server handler A to server handler B. + * @param source - fields to copy. + * @param destination - fields to copy to. + */ + private void copyTo(Object source, Object destination) { + StructureModifier modifierSource = serverHandlerModifier.withTarget(source); + StructureModifier modifierDest = serverHandlerModifier.withTarget(destination); + + // Copy every field + try { + for (int i = 0; i < modifierSource.size(); i++) { + modifierDest.write(i, modifierSource.read(i)); + } + } catch (FieldAccessException e) { + throw new RuntimeException("Unable to copy fields from NetServerHandler.", e); + } + } + @Override public void cleanupAll() { - if (serverHandlerRef != null) { + if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) { + copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue()); serverHandlerRef.revertValue(); } + serverInjection.revertServerHandler(serverHandler); + try { if (getNetHandler() != null) { // Restore packet listener diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 0288ea1d..6d854ccb 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -34,6 +34,7 @@ import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; +import org.bukkit.Server; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -44,7 +45,6 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -import org.bukkit.scheduler.BukkitScheduler; import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.ProtocolManager; @@ -92,11 +92,14 @@ public final class PacketFilterManager implements ProtocolManager { private Map playerInjection = new HashMap(); // Player injection type - private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; + private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_HANDLER_FIELDS; // Packet injection private PacketInjector packetInjector; + // Server connection injection + private InjectedServerConnection serverInjection; + // Enabled packet filters private Set sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap()); @@ -122,7 +125,7 @@ public final class PacketFilterManager implements ProtocolManager { /** * Only create instances of this class if protocol lib is disabled. */ - public PacketFilterManager(ClassLoader classLoader, BukkitScheduler scheduler, Logger logger) { + public PacketFilterManager(ClassLoader classLoader, Server server, Logger logger) { if (logger == null) throw new IllegalArgumentException("logger cannot be NULL."); if (classLoader == null) @@ -133,7 +136,8 @@ public final class PacketFilterManager implements ProtocolManager { this.classLoader = classLoader; this.logger = logger; this.packetInjector = new PacketInjector(classLoader, this, connectionLookup); - this.asyncFilterManager = new AsyncFilterManager(logger, scheduler, this); + this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this); + this.serverInjection = new InjectedServerConnection(logger, server); } catch (IllegalAccessException e) { logger.log(Level.SEVERE, "Unable to initialize packet injector.", e); } @@ -474,7 +478,7 @@ public final class PacketFilterManager implements ProtocolManager { case NETWORK_MANAGER_OBJECT: return new NetworkObjectInjector(player, this, sendingFilters); case NETWORK_SERVER_OBJECT: - return new NetworkServerInjector(player, this, sendingFilters); + return new NetworkServerInjector(player, this, sendingFilters, serverInjection); default: throw new IllegalArgumentException("Cannot construct a player injector."); } @@ -714,6 +718,8 @@ public final class PacketFilterManager implements ProtocolManager { if (packetInjector != null) packetInjector.cleanupAll(); + // Remove server handler + serverInjection.cleanupAll(); hasClosed = true; // Remove listeners diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index b92fd3c0..c9c6e3a5 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -221,6 +221,7 @@ abstract class PlayerInjector { Packet handlePacketRecieved(Packet packet) { // Get the packet ID too Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass()); + System.out.println(id); // Make sure we're listening if (id != null && sendingFilters.contains(id)) { diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java b/ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java new file mode 100644 index 00000000..64e3b0de --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java @@ -0,0 +1,136 @@ +package com.comphenix.protocol.injector; + +import java.util.Collection; +import java.util.List; + +import com.google.common.base.Objects; +import com.google.common.collect.BiMap; +import com.google.common.collect.ForwardingList; +import com.google.common.collect.HashBiMap; + +/** + * Represents an array list that wraps another list, while automatically replacing one element with another. + *

+ * The replaced elements can be recovered. + * + * @author Kristian + * @param - type of the elements we're replacing. + */ +class ReplacedArrayList extends ForwardingList { + private BiMap replaceMap = HashBiMap.create(); + private List underlyingList; + + public ReplacedArrayList(List underlyingList) { + this.underlyingList = underlyingList; + } + + @Override + public boolean add(TKey element) { + if (replaceMap.containsKey(element)) { + return super.add(replaceMap.get(element)); + } else { + return super.add(element); + } + } + + @Override + public void add(int index, TKey element) { + if (replaceMap.containsKey(element)) { + super.add(index, replaceMap.get(element)); + } else { + super.add(index, element); + } + } + + @Override + public boolean addAll(Collection collection) { + int oldSize = size(); + + for (TKey element : collection) + add(element); + return size() != oldSize; + } + + @Override + public boolean addAll(int index, Collection elements) { + int oldSize = size(); + + for (TKey element : elements) + add(index++, element); + return size() != oldSize; + } + + @Override + protected List delegate() { + return underlyingList; + } + + /** + * Add a replace rule. + *

+ * This automatically replaces every existing element. + * @param target - instance to find. + * @param replacement - instance to replace with. + */ + public synchronized void addMapping(TKey target, TKey replacement) { + replaceMap.put(target, replacement); + + // Replace existing elements + replaceAll(target, replacement); + } + + /** + * Revert the given mapping. + * @param target - the instance we replaced. + */ + public synchronized void removeMapping(TKey target) { + // Make sure the mapping exist + if (replaceMap.containsKey(target)) { + TKey replacement = replaceMap.get(target); + replaceMap.remove(target); + + // Revert existing elements + replaceAll(replacement, target); + } + } + + /** + * Replace all instances of the given object. + * @param find - object to find. + * @param replace - object to replace it with. + */ + public synchronized void replaceAll(TKey find, TKey replace) { + for (int i = 0; i < underlyingList.size(); i++) { + if (Objects.equal(underlyingList.get(i), find)) + underlyingList.set(i, replace); + } + } + + /** + * Undo all replacements. + */ + public synchronized void revertAll() { + + // No need to do anything else + if (replaceMap.size() < 1) + return; + + BiMap inverse = replaceMap.inverse(); + + for (int i = 0; i < underlyingList.size(); i++) { + TKey replaced = underlyingList.get(i); + + if (inverse.containsKey(replaced)) { + underlyingList.set(i, inverse.get(replaced)); + } + } + + replaceMap.clear(); + } + + @Override + protected void finalize() throws Throwable { + revertAll(); + super.finalize(); + } +} \ No newline at end of file diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/FuzzyReflection.java b/ProtocolLib/src/com/comphenix/protocol/reflect/FuzzyReflection.java index ae30b604..448507f7 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/FuzzyReflection.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/FuzzyReflection.java @@ -142,11 +142,38 @@ public class FuzzyReflection { * @return The first method that satisfies the parameter types. */ public Method getMethodByParameters(String name, Class returnType, Class[] args) { + // Find the correct method to call + List methods = getMethodListByParameters(returnType, args); + + if (methods.size() > 0) { + return methods.get(0); + } else { + // That sucks + throw new RuntimeException("Unable to find " + name + " in " + source.getName()); + } + } + /** + * Retrieves a method by looking at the parameter types and return type only. + * @param name - potential name of the method. Only used by the error mechanism. + * @param returnType - regular expression matching the return type of the method to find. + * @param args - regular expressions of the matching parameter types. + * @return The first method that satisfies the parameter types. + */ + public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) { + + Pattern match = Pattern.compile(returnTypeRegex); + Pattern[] argMatch = new Pattern[argsRegex.length]; + + for (int i = 0; i < argsRegex.length; i++) { + argMatch[i] = Pattern.compile(argsRegex[i]); + } + // Find the correct method to call for (Method method : getMethods()) { - if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) { - return method; + if (match.matcher(method.getReturnType().getName()).matches()) { + if (matchParameters(argMatch, method.getParameterTypes())) + return method; } } @@ -154,6 +181,19 @@ public class FuzzyReflection { throw new RuntimeException("Unable to find " + name + " in " + source.getName()); } + private boolean matchParameters(Pattern[] parameterMatchers, Class[] argTypes) { + if (parameterMatchers.length != argTypes.length) + throw new IllegalArgumentException("Arrays must have the same cardinality."); + + // Check types against the regular expressions + for (int i = 0; i < argTypes.length; i++) { + if (!parameterMatchers[i].matcher(argTypes[i].getName()).matches()) + return false; + } + + return true; + } + /** * Retrieves every method that has the given parameter types and return type. * @param returnType - return type of the method to find. @@ -195,6 +235,46 @@ public class FuzzyReflection { nameRegex + " in " + source.getName()); } + /** + * Retrieves the first field with a type equal to or more specific to the given type. + * @param name - name the field probably is given. This will only be used in the error message. + * @param type - type of the field to find. + * @return The first field with a type that is an instance of the given type. + */ + public Field getFieldByType(String name, Class type) { + + List fields = getFieldListByType(type); + + if (fields.size() > 0) { + return fields.get(0); + } else { + // Looks like we're outdated. Too bad. + throw new RuntimeException(String.format("Unable to find a field %s with the type %s in %s", + name, type.getName(), source.getName()) + ); + } + } + + /** + * Retrieves every field with a type equal to or more specific to the given type. + * @param type - type of the fields to find. + * @return Every field with a type that is an instance of the given type. + */ + public List getFieldListByType(Class type) { + + List fields = new ArrayList(); + + // Field with a compatible type + for (Field field : getFields()) { + // A assignable from B -> B instanceOf A + if (type.isAssignableFrom(field.getType())) { + fields.add(field); + } + } + + return fields; + } + /** * Retrieves a field by type. *

diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/VolatileField.java b/ProtocolLib/src/com/comphenix/protocol/reflect/VolatileField.java index 4e22f7ef..64272b96 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/VolatileField.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/VolatileField.java @@ -155,6 +155,13 @@ public class VolatileField { } } + /** + * Determine whether or not we'll need to revert the value. + */ + public boolean isCurrentSet() { + return currentSet; + } + private void ensureLoaded() { // Load the value if we haven't already if (!previousLoaded) { From e6de9ae7053a06d09db33c7974096708378ea121 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 1 Oct 2012 00:16:34 +0200 Subject: [PATCH 47/50] Remove debug line. --- .../src/com/comphenix/protocol/injector/PlayerInjector.java | 1 - 1 file changed, 1 deletion(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index c9c6e3a5..b92fd3c0 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -221,7 +221,6 @@ abstract class PlayerInjector { Packet handlePacketRecieved(Packet packet) { // Get the packet ID too Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass()); - System.out.println(id); // Make sure we're listening if (id != null && sendingFilters.contains(id)) { From 46d9a6e9758c662d5345720f12ebcdaeee2cc51d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 1 Oct 2012 00:57:14 +0200 Subject: [PATCH 48/50] Ensure that the structure compiler is thread safe. --- .../protocol/injector/StructureCache.java | 58 ++++++++++++++++--- .../reflect/compiler/BackgroundCompiler.java | 27 ++++++++- .../reflect/compiler/CompileListener.java | 17 ++++++ 3 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompileListener.java diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java b/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java index bc80d438..6dff5dfc 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java @@ -17,12 +17,17 @@ package com.comphenix.protocol.injector; -import java.util.HashMap; -import java.util.Map; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import net.minecraft.server.Packet; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; +import com.comphenix.protocol.reflect.compiler.CompileListener; +import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier; /** * Caches structure modifiers. @@ -30,7 +35,10 @@ import com.comphenix.protocol.reflect.StructureModifier; */ public class StructureCache { // Structure modifiers - private static Map> structureModifiers = new HashMap>(); + private static ConcurrentMap> structureModifiers = + new ConcurrentHashMap>(); + + private static Set compiling = new HashSet(); /** * Creates an empty Minecraft packet of the given ID. @@ -53,15 +61,51 @@ public class StructureCache { * @return A structure modifier. */ public static StructureModifier getStructure(int id) { + // Compile structures by default + return getStructure(id, true); + } + + /** + * Retrieve a cached structure modifier for the given packet id. + * @param id - packet ID. + * @param compile - whether or not to asynchronously compile the structure modifier. + * @return A structure modifier. + */ + public static StructureModifier getStructure(int id, boolean compile) { StructureModifier result = structureModifiers.get(id); - - // Use the vanilla class definition + + // We don't want to create this for every lookup if (result == null) { - result = new StructureModifier( + // Use the vanilla class definition + final StructureModifier value = new StructureModifier( MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true); - structureModifiers.put(id, result); + result = structureModifiers.putIfAbsent(id, value); + + // We may end up creating multiple modifiers, but we'll agree on which to use + if (result == null) { + result = value; + } + } + + // Automatically compile the structure modifier + if (compile && !(result instanceof CompiledStructureModifier)) { + // Compilation is many orders of magnitude slower than synchronization + synchronized (compiling) { + final int idCopy = id; + final BackgroundCompiler compiler = BackgroundCompiler.getInstance(); + + if (!compiling.contains(id) && compiler != null) { + compiler.scheduleCompilation(result, new CompileListener() { + @Override + public void onCompiled(StructureModifier compiledModifier) { + structureModifiers.put(idCopy, compiledModifier); + } + }); + compiling.add(id); + } + } } return result; diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java index 7bf702ab..61a42d48 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java @@ -80,6 +80,27 @@ public class BackgroundCompiler { @SuppressWarnings("rawtypes") public void scheduleCompilation(final Map cache, final Class key) { + @SuppressWarnings("unchecked") + final StructureModifier uncompiled = cache.get(key); + + if (uncompiled != null) { + scheduleCompilation(uncompiled, new CompileListener() { + @Override + public void onCompiled(StructureModifier compiledModifier) { + // Update cache + cache.put(key, compiledModifier); + } + }); + } + } + + /** + * Ensure that the given structure modifier is eventually compiled. + * @param uncompiled - structure modifier to compile. + * @param listener - listener responsible for responding to the compilation. + */ + public void scheduleCompilation(final StructureModifier uncompiled, final CompileListener listener) { + // Only schedule if we're enabled if (enabled && !shuttingDown) { @@ -92,11 +113,11 @@ public class BackgroundCompiler { @Override public Object call() throws Exception { - StructureModifier modifier = cache.get(key); + StructureModifier modifier = uncompiled; - // Update the cache! + // Do our compilation modifier = compiler.compile(modifier); - cache.put(key, modifier); + listener.onCompiled(modifier); // We'll also return the new structure modifier return modifier; diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompileListener.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompileListener.java new file mode 100644 index 00000000..5ab92978 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompileListener.java @@ -0,0 +1,17 @@ +package com.comphenix.protocol.reflect.compiler; + +import com.comphenix.protocol.reflect.StructureModifier; + +/** + * Used to save the result of an compilation. + * + * @author Kristian + * @param - type of the structure modifier field. + */ +public interface CompileListener { + /** + * Invoked when a structure modifier has been successfully compiled. + * @param compiledModifier - the compiled structure modifier. + */ + public void onCompiled(StructureModifier compiledModifier); +} From c2b4b5fce3ba6c65dc06be1dfe4c99276f4770b7 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 1 Oct 2012 02:17:13 +0200 Subject: [PATCH 49/50] Ensure that the compiled structures respect the converter field. In addition, fixed a bug that prevented client listeners from receiving any packets. --- .../protocol/events/PacketEvent.java | 2 + .../injector/PacketFilterManager.java | 11 +++-- .../protocol/injector/PlayerInjector.java | 2 +- .../protocol/injector/ReadPacketModifier.java | 2 +- .../injector/SortedPacketListenerList.java | 2 +- .../protocol/reflect/StructureModifier.java | 17 ++++++-- .../compiler/CompiledStructureModifier.java | 30 +++++++++++++- .../reflect/compiler/StructureCompiler.java | 41 +++++++++---------- 8 files changed, 75 insertions(+), 32 deletions(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java index 799154f3..92b039fa 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java @@ -148,6 +148,8 @@ public class PacketEvent extends EventObject implements Cancellable { /** * Whether or not this packet was created by the server. + *

+ * Most listeners can deduce this by noting which listener method was invoked. * @return TRUE if the packet was created by the server, FALSE if it was created by a client. */ public boolean isServerPacket() { diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 6d854ccb..e9009586 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -293,7 +293,7 @@ public final class PacketFilterManager implements ProtocolManager { * @param event - the packet event to invoke. */ public void invokePacketRecieving(PacketEvent event) { - handlePacket(recievedListeners, event); + handlePacket(recievedListeners, event, false); } /** @@ -301,7 +301,7 @@ public final class PacketFilterManager implements ProtocolManager { * @param event - the packet event to invoke. */ public void invokePacketSending(PacketEvent event) { - handlePacket(sendingListeners, event); + handlePacket(sendingListeners, event, true); } /** @@ -311,7 +311,7 @@ public final class PacketFilterManager implements ProtocolManager { * @param packetListeners - packet listeners that will receive this event. * @param event - the evnet to broadcast. */ - private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event) { + private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event, boolean sending) { // By default, asynchronous packets are queued for processing if (asyncFilterManager.hasAsynchronousListeners(event)) { @@ -319,7 +319,10 @@ public final class PacketFilterManager implements ProtocolManager { } // Process synchronous events - packetListeners.invokePacketRecieving(logger, event); + if (sending) + packetListeners.invokePacketSending(logger, event); + else + packetListeners.invokePacketRecieving(logger, event); // To cancel asynchronous processing, use the async marker if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) { diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java index b92fd3c0..2ae2976d 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PlayerInjector.java @@ -223,7 +223,7 @@ abstract class PlayerInjector { Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass()); // Make sure we're listening - if (id != null && sendingFilters.contains(id)) { + if (id != null && 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/ReadPacketModifier.java b/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java index d2e71c60..d5b84bbe 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/ReadPacketModifier.java @@ -97,7 +97,7 @@ class ReadPacketModifier implements MethodInterceptor { // We need this in order to get the correct player DataInputStream input = (DataInputStream) args[0]; - + // Let the people know PacketContainer container = new PacketContainer(packetID, (Packet) thisObj); PacketEvent event = packetInjector.packetRecieved(container, input); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java b/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java index 27df4585..7ff8cddd 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/SortedPacketListenerList.java @@ -57,7 +57,7 @@ class SortedPacketListenerList extends AbstractConcurrentListenerMultimap { // Cache of previous types protected Map subtypeCache; + // Whether or subclasses should handle conversion + protected boolean customConvertHandling; + /** * Creates a structure modifier. * @param targetType - the structure to modify. @@ -125,7 +128,7 @@ public class StructureModifier { Object result = FieldUtils.readField(data.get(fieldIndex), target, true); // Use the converter, if we have it - if (converter != null) + if (needConversion()) return converter.getSpecific(result); else return (TField) result; @@ -164,7 +167,7 @@ public class StructureModifier { throw new IllegalStateException("Cannot write to a NULL target."); // Use the converter, if it exists - Object obj = converter != null ? converter.getGeneric(value) : value; + Object obj = needConversion() ? converter.getGeneric(value) : value; try { FieldUtils.writeField(data.get(fieldIndex), target, obj, true); @@ -176,6 +179,14 @@ public class StructureModifier { return this; } + /** + * Whether or not we should use the converter instance. + * @return TRUE if we should, FALSE otherwise. + */ + private final boolean needConversion() { + return converter != null && !customConvertHandling; + } + /** * Writes the value of a given field IF and ONLY if it exists. * @param fieldIndex - index of the potential field. @@ -362,7 +373,7 @@ public class StructureModifier { /** * Retrieves a structure modifier with the same type and target, but using a new object converter. - * @param converter- the object converter to use. + * @param converter - the object converter to use. * @return Structure modifier with the new converter. */ @SuppressWarnings("unchecked") diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java index 5ddb19f8..d96236f7 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java @@ -13,10 +13,15 @@ import com.comphenix.protocol.reflect.instances.DefaultInstances; * @author Kristian * @param Field type. */ -public class CompiledStructureModifier extends StructureModifier { +public abstract class CompiledStructureModifier extends StructureModifier { // Used to compile instances of structure modifiers protected StructureCompiler compiler; + public CompiledStructureModifier() { + super(); + customConvertHandling = true; + } + // Speed up the default writer @SuppressWarnings("unchecked") @Override @@ -35,6 +40,29 @@ public class CompiledStructureModifier extends StructureModifier return this; } + @SuppressWarnings("unchecked") + @Override + public final TField read(int fieldIndex) throws FieldAccessException { + Object result = readGenerated(fieldIndex); + + if (converter != null) + return converter.getSpecific(result); + else + return (TField) result; + } + + protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException; + + @SuppressWarnings("unchecked") + @Override + public StructureModifier write(int index, Object value) throws FieldAccessException { + if (converter != null) + value = converter.getGeneric((TField) value); + return writeGenerated(index, value); + } + + protected abstract StructureModifier writeGenerated(int index, Object value) throws FieldAccessException; + @Override public StructureModifier withTarget(Object target) { if (compiler != null) diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index 837aa778..24477dff 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -14,40 +14,39 @@ import com.google.common.base.Objects; import net.sf.cglib.asm.*; -// This class will automatically generate the following type of structure modifier: -// -// public class CompiledStructure$Packet20NamedEntitySpawnObject extends CompiledStructureModifier { +// public class CompiledStructureModifierPacket20 extends CompiledStructureModifier { // // private Packet20NamedEntitySpawn typedTarget; -// -// public CompiledStructure$Packet20NamedEntitySpawnObject(StructureModifier other, StructureCompiler compiler) { +// +// public CompiledStructureModifierPacket20(StructureModifier other, StructureCompiler compiler) { +// super(); // initialize(other); -// this.typedTarget = (Packet20NamedEntitySpawn) other.getTarget(); +// this.target = other.getTarget(); +// this.typedTarget = (Packet20NamedEntitySpawn) target; // this.compiler = compiler; // } // -// @SuppressWarnings("unchecked") // @Override -// public TField read(int fieldIndex) throws FieldAccessException { +// protected Object readGenerated(int fieldIndex) throws FieldAccessException { // // Packet20NamedEntitySpawn target = typedTarget; // // switch (fieldIndex) { -// case 0: return (TField) (Object) target.a; -// case 1: return (TField) (Object) target.b; -// case 2: return (TField) (Object) target.c; -// case 3: return (TField) (Object) target.d; -// case 4: return (TField) (Object) target.e; -// case 5: return (TField) (Object) target.f; -// case 6: return (TField) (Object) target.g; -// case 7: return (TField) (Object) target.h; +// case 0: return (Object) target.a; +// case 1: return (Object) target.b; +// case 2: return (Object) target.c; +// case 3: return super.read(fieldIndex); +// case 4: return super.read(fieldIndex); +// case 5: return (Object) target.f; +// case 6: return (Object) target.g; +// case 7: return (Object) target.h; // default: // throw new FieldAccessException("Invalid index " + fieldIndex); // } // } // // @Override -// public StructureModifier write(int index, Object value) { +// protected StructureModifier writeGenerated(int index, Object value) throws FieldAccessException { // // Packet20NamedEntitySpawn target = typedTarget; // @@ -56,8 +55,8 @@ import net.sf.cglib.asm.*; // case 1: target.b = (String) value; break; // case 2: target.c = (Integer) value; break; // case 3: target.d = (Integer) value; break; -// case 4: target.e = (Integer) value; break; -// case 5: target.f = (Byte) value; break; +// case 4: super.write(index, value); break; +// case 5: super.write(index, value); break; // case 6: target.g = (Byte) value; break; // case 7: target.h = (Integer) value; break; // default: @@ -265,7 +264,7 @@ public final class StructureCompiler { String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"; String methodSignature = "(ITTField;)L" + SUPER_CLASS + ";"; - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, "write", methodDescriptor, methodSignature, + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature, new String[] { FIELD_EXCEPTION_CLASS }); BoxingHelper boxingHelper = new BoxingHelper(mv); @@ -349,7 +348,7 @@ public final class StructureCompiler { } private void createReadMethod(ClassWriter cw, String className, List fields, String targetSignature, String targetName) { - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, "read", "(I)Ljava/lang/Object;", "(I)TTField;", + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null, new String[] { "com/comphenix/protocol/reflect/FieldAccessException" }); BoxingHelper boxingHelper = new BoxingHelper(mv); From 961b34da38331e9fa845e590facfb98235753ef8 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 1 Oct 2012 04:59:27 +0200 Subject: [PATCH 50/50] Update documentation (may still need some work). --- .../src/com/comphenix/protocol/async/AsyncFilterManager.java | 2 +- ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java index a09bfd13..0862bed4 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java @@ -182,7 +182,7 @@ public class AsyncFilterManager implements AsynchronousManager { // Start the process getSendingQueue(syncPacket).enqueue(newEvent); - // We know this is occuring on the main thread, to pass TRUE + // We know this is occuring on the main thread, so pass TRUE getProcessingQueue(syncPacket).enqueue(newEvent, true); } diff --git a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java index 3f063eb8..8cecb1c8 100644 --- a/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java @@ -18,6 +18,8 @@ import com.google.common.primitives.Longs; /** * Contains information about the packet that is being processed by asynchronous listeners. + *

+ * Asynchronous listeners can use this to set packet timeout or transmission order. * * @author Kristian */