diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java index e38f32cb..63ebed9e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java @@ -17,5 +17,10 @@ public enum ListenerOptions { * Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when * a packet ID is known to be transmitted during login. */ - DISABLE_GAMEPHASE_DETECTION; + DISABLE_GAMEPHASE_DETECTION, + + /** + * Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe. + */ + ASYNC; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java index eea779f1..26e5383a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java @@ -85,6 +85,17 @@ public abstract class PacketAdapter implements PacketListener { this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority)); } + /** + * Initialize a packet listener with the given parameters. + * @param plugin - the plugin. + * @param listenerPriority - the priority. + * @param types - the packet types. + * @param options - the options. + */ + public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable types, ListenerOptions... options) { + this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options)); + } + /** * Initialize a packet listener with the given parameters. * @param plugin - the plugin. @@ -391,7 +402,6 @@ public abstract class PacketAdapter implements PacketListener { * @return Helper object. */ public static AdapterParameteters params(Plugin plugin, PacketType... packets) { - return new AdapterParameteters().plugin(plugin).types(packets); } @@ -490,13 +500,59 @@ public abstract class PacketAdapter implements PacketListener { this.options = Preconditions.checkNotNull(options, "options cannot be NULL."); return this; } + + /** + * Set listener options that decide whether or not to intercept the raw packet data as read from the network stream. + *

+ * The default is to disable this raw packet interception. + * @param options - every option to use. + * @return This builder, for chaining. + */ + public AdapterParameteters options(@Nonnull Set options) { + Preconditions.checkNotNull(options, "options cannot be NULL."); + this.options = options.toArray(new ListenerOptions[0]); + return this; + } + + /** + * Add a given option to the current builder. + * @param option - the option to add. + * @return This builder, for chaining. + */ + private AdapterParameteters addOption(ListenerOptions option) { + if (options == null) { + return options(option); + } else { + Set current = Sets.newHashSet(options); + current.add(option); + return options(current); + } + } /** * Set the listener option to {@link ListenerOptions#INTERCEPT_INPUT_BUFFER}, causing ProtocolLib to read the raw packet data from the network stream. * @return This builder, for chaining. */ public AdapterParameteters optionIntercept() { - return options(ListenerOptions.INTERCEPT_INPUT_BUFFER); + return addOption(ListenerOptions.INTERCEPT_INPUT_BUFFER); + } + + /** + * Set the listener option to {@link ListenerOptions#DISABLE_GAMEPHASE_DETECTION}, causing ProtocolLib to ignore automatic game phase detection. + *

+ * This is no longer relevant in 1.7.2. + * @return This builder, for chaining. + */ + public AdapterParameteters optionManualGamePhase() { + return addOption(ListenerOptions.DISABLE_GAMEPHASE_DETECTION); + } + + /** + * Set the listener option to {@link ListenerOptions#ASYNC}, causing ProtocolLib to ignore automatic game phase detection. + * @return This builder, for chaining. + */ + public AdapterParameteters optionAsync() { + return addOption(ListenerOptions.DISABLE_GAMEPHASE_DETECTION); } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java index 18b8f096..53c0c427 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java @@ -34,8 +34,8 @@ public interface PacketListener { *

* This method is executed on the main thread in 1.6.4 and earlier, and thus the Bukkit API is safe to use. *

- * Warning: In 1.7.2 and later, login and status packets are executed on a worker thread. - * Call {@link PacketEvent#isAsync()} to detect this in your listener. + * In Minecraft 1.7.2 and later, this method MAY be executed asynchronously, but only if {@link ListenerOptions#ASYNC} + * have been specified in the listener. This is off by default. * @param event - the packet that should be sent. */ public void onPacketSending(PacketEvent event); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 87c95c74..05f95f97 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -613,7 +613,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (side.isForServer()) { // Note that we may update the packet list here if (!knowsServerPackets || PacketRegistry.getServerPacketTypes().contains(type)) - playerInjection.addPacketHandler(type); + playerInjection.addPacketHandler(type, listener.getSendingWhitelist().getOptions()); else reporter.reportWarning(this, Report.newBuilder(REPORT_UNSUPPORTED_SERVER_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), type) @@ -623,7 +623,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // As above, only for client packets if (side.isForClient() && packetInjector != null) { if (!knowsClientPackets || PacketRegistry.getClientPacketTypes().contains(type)) - packetInjector.addPacketHandler(type); + packetInjector.addPacketHandler(type, listener.getReceivingWhitelist().getOptions()); else reporter.reportWarning(this, Report.newBuilder(REPORT_UNSUPPORTED_CLIENT_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), type) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java index 4de16dee..cd343177 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java @@ -25,7 +25,7 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import com.comphenix.protocol.PacketType.Protocol; -import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.events.ConnectionSide; @@ -52,47 +52,6 @@ class ChannelInjector extends ByteToMessageDecoder { public static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet."); public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet."); - /** - * Represents a listener for received or sent packets. - * @author Kristian - */ - interface ChannelListener { - /** - * Invoked when a packet is being sent to the client. - *

- * This is invoked on the main thread. - * @param injector - the channel injector. - * @param packet - the packet. - * @param marker - the associated network marker, if any. - * @return The new packet, if it should be changed, or NULL to cancel. - */ - public Object onPacketSending(ChannelInjector injector, Object packet, NetworkMarker marker); - - /** - * Invoked when a packet is being received from a client. - *

- * This is invoked on an asynchronous worker thread. - * @param injector - the channel injector. - * @param packet - the packet. - * @param marker - the associated network marker, if any. - * @return The new packet, if it should be changed, or NULL to cancel. - */ - public Object onPacketReceiving(ChannelInjector injector, Object packet, NetworkMarker marker); - - /** - * Determine if we need the buffer data of a given client side packet. - * @param packetClass - the packet class. - * @return TRUE if we do, FALSE otherwise. - */ - public boolean includeBuffer(Class packetClass); - - /** - * Retrieve the current error reporter. - * @return The error reporter. - */ - public ErrorReporter getReporter(); - } - private static final ConcurrentMap cachedInjector = new MapMaker().weakKeys().makeMap(); // Saved accessors @@ -340,9 +299,19 @@ class ChannelInjector extends ByteToMessageDecoder { // Try again, in case this packet was sent directly in the event loop if (event == null && !processedPackets.remove(packet)) { - packet = processSending(packet); - marker = getMarker(packet); - event = markerEvent.remove(marker); + Class clazz = packet.getClass(); + + // Schedule the transmission on the main thread instead + if (channelListener.hasMainThreadListener(clazz)) { + // Delay the packet + scheduleMainThread(marker, packet); + packet = null; + + } else { + packet = processSending(packet); + marker = getMarker(packet); + event = markerEvent.remove(marker); + } } // Process output handler @@ -368,6 +337,15 @@ class ChannelInjector extends ByteToMessageDecoder { } } } + + private void scheduleMainThread(final NetworkMarker marker, final Object packetCopy) { + ProtocolLibrary.getExecutorSync().execute(new Runnable() { + @Override + public void run() { + sendServerPacket(packetCopy, marker, true); + } + }); + } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List packets) throws Exception { @@ -620,7 +598,7 @@ class ChannelInjector extends ByteToMessageDecoder { * Represents a socket injector that foreards to the current channel injector. * @author Kristian */ - private static class ChannelSocketInjector implements SocketInjector { + static class ChannelSocketInjector implements SocketInjector { private final ChannelInjector injector; public ChannelSocketInjector(ChannelInjector injector) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java new file mode 100644 index 00000000..a8e38a61 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java @@ -0,0 +1,59 @@ +package com.comphenix.protocol.injector.netty; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.events.NetworkMarker; + +/** + * Represents a listener for received or sent packets. + * @author Kristian + */ +interface ChannelListener { + /** + * Invoked when a packet is being sent to the client. + *

+ * This is invoked on the main thread. + * @param injector - the channel injector. + * @param packet - the packet. + * @param marker - the associated network marker, if any. + * @return The new packet, if it should be changed, or NULL to cancel. + */ + public Object onPacketSending(ChannelInjector injector, Object packet, NetworkMarker marker); + + /** + * Invoked when a packet is being received from a client. + *

+ * This is invoked on an asynchronous worker thread. + * @param injector - the channel injector. + * @param packet - the packet. + * @param marker - the associated network marker, if any. + * @return The new packet, if it should be changed, or NULL to cancel. + */ + public Object onPacketReceiving(ChannelInjector injector, Object packet, NetworkMarker marker); + + /** + * Determine if there is a packet listener for the given packet. + * @param packetClass - the packet class to check. + * @return TRUE if there is such a listener, FALSE otherwise. + */ + public boolean hasListener(Class packetClass); + + /** + * Determine if there is a server packet listener that must be executed on the main thread. + * @param packetClass - the packet class to check. + * @return TRUE if there is, FALSE otherwise. + */ + public boolean hasMainThreadListener(Class packetClass); + + /** + * Determine if we need the buffer data of a given client side packet. + * @param packetClass - the packet class. + * @return TRUE if we do, FALSE otherwise. + */ + public boolean includeBuffer(Class packetClass); + + /** + * Retrieve the current error reporter. + * @return The error reporter. + */ + public ErrorReporter getReporter(); +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java index 738bf706..1866d532 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java @@ -22,11 +22,11 @@ import com.comphenix.protocol.PacketType; import com.comphenix.protocol.concurrency.PacketTypeSet; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ConnectionSide; +import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.ListenerInvoker; -import com.comphenix.protocol.injector.netty.ChannelInjector.ChannelListener; import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; @@ -47,8 +47,10 @@ public class NettyProtocolInjector implements ChannelListener { private List bootstrapFields = Lists.newArrayList(); // Different sending filters - private PacketTypeSet queuedFilters = new PacketTypeSet(); + private PacketTypeSet sendingFilters = new PacketTypeSet(); private PacketTypeSet reveivedFilters = new PacketTypeSet(); + // Packets that must be executed on the main thread + private PacketTypeSet mainThreadFilters = new PacketTypeSet(); // Which packets are buffered private PacketTypeSet bufferedPackets = new PacketTypeSet(); @@ -118,6 +120,16 @@ public class NettyProtocolInjector implements ChannelListener { } } + @Override + public boolean hasListener(Class packetClass) { + return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass); + } + + @Override + public boolean hasMainThreadListener(Class packetClass) { + return mainThreadFilters.contains(packetClass); + } + @Override public ErrorReporter getReporter() { return reporter; @@ -175,7 +187,7 @@ public class NettyProtocolInjector implements ChannelListener { public Object onPacketSending(ChannelInjector injector, Object packet, NetworkMarker marker) { Class clazz = packet.getClass(); - if (queuedFilters.contains(clazz)) { + if (sendingFilters.contains(clazz)) { // Check for ignored packets if (injector.unignorePacket(packet)) { return packet; @@ -248,8 +260,9 @@ public class NettyProtocolInjector implements ChannelListener { return event; } + // Server side public PlayerInjectionHandler getPlayerInjector() { - return new AbstractPlayerHandler(queuedFilters) { + return new AbstractPlayerHandler(sendingFilters) { private ChannelListener listener = NettyProtocolInjector.this; @Override @@ -263,6 +276,19 @@ public class NettyProtocolInjector implements ChannelListener { return true; } + @Override + public void addPacketHandler(PacketType type, Set options) { + if (options != null && !options.contains(ListenerOptions.ASYNC)) + mainThreadFilters.addType(type); + super.addPacketHandler(type, options); + } + + @Override + public void removePacketHandler(PacketType type) { + mainThreadFilters.removeType(type); + super.removePacketHandler(type); + } + @Override public boolean uninjectPlayer(Player player) { ChannelInjector.fromPlayer(player, listener).close(); @@ -303,6 +329,7 @@ public class NettyProtocolInjector implements ChannelListener { * Retrieve a view of this protocol injector as a packet injector. * @return The packet injector. */ + // Client side public PacketInjector getPacketInjector() { return new AbstractPacketInjector(reveivedFilters) { @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java index 7d633324..a3a60e45 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java @@ -5,6 +5,7 @@ import java.util.Set; import org.bukkit.entity.Player; import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; @@ -31,9 +32,10 @@ public interface PacketInjector { /** * Start intercepting packets with the given packet type. * @param type - the type of the packets to start intercepting. + * @param options - any listener options. * @return TRUE if we didn't already intercept these packets, FALSE otherwise. */ - public abstract boolean addPacketHandler(PacketType type); + public abstract boolean addPacketHandler(PacketType type, Set options); /** * Stop intercepting packets with the given packet type. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java index e005c9f8..94747491 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java @@ -38,6 +38,7 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.events.ConnectionSide; +import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; @@ -204,7 +205,7 @@ class ProxyPacketInjector implements PacketInjector { @Override @SuppressWarnings({"rawtypes", "deprecation"}) - public boolean addPacketHandler(PacketType type) { + public boolean addPacketHandler(PacketType type, Set options) { final int packetID = type.getLegacyId(); if (hasPacketHandler(type)) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index e4fd780d..b35015c0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -8,6 +8,7 @@ import java.util.Set; import org.bukkit.entity.Player; import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; @@ -62,8 +63,9 @@ public interface PlayerInjectionHandler { /** * Add an underlying packet handler of the given type. * @param type - packet type to register. + * @param options - any specified listener options. */ - public abstract void addPacketHandler(PacketType type); + public abstract void addPacketHandler(PacketType type, Set options); /** * Remove an underlying packet handler of this type. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index f2f463aa..57b8d7e4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -42,6 +42,7 @@ import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; @@ -203,7 +204,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { } @Override - public void addPacketHandler(PacketType type) { + public void addPacketHandler(PacketType type, Set options) { sendingFilters.add(type.getLegacyId()); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPacketInjector.java index 3b82afba..925423d7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPacketInjector.java @@ -4,6 +4,7 @@ import java.util.Set; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.concurrency.PacketTypeSet; +import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.injector.packet.PacketInjector; public abstract class AbstractPacketInjector implements PacketInjector { @@ -25,7 +26,7 @@ public abstract class AbstractPacketInjector implements PacketInjector { } @Override - public boolean addPacketHandler(PacketType type) { + public boolean addPacketHandler(PacketType type, Set options) { reveivedFilters.addType(type); return true; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPlayerHandler.java index e88f571f..6f40a6a4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPlayerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPlayerHandler.java @@ -7,6 +7,7 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.concurrency.PacketTypeSet; +import com.comphenix.protocol.events.ListenerOptions; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; @@ -30,7 +31,7 @@ public abstract class AbstractPlayerHandler implements PlayerInjectionHandler { } @Override - public void addPacketHandler(PacketType type) { + public void addPacketHandler(PacketType type, Set options) { sendingFilters.addType(type); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java index a5b04a3c..aa1e65c5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java @@ -35,7 +35,7 @@ class DummyPacketInjector extends AbstractPacketInjector implements PacketInject injector.getProxyPacketInjector().removePacketHandler(packet); } for (PacketType packet : added) { - injector.getProxyPacketInjector().addPacketHandler(packet); + injector.getProxyPacketInjector().addPacketHandler(packet, null); } }