From 1ef602416da32098386d379a79f3b850023190bd Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 26 Apr 2014 00:20:50 +0200 Subject: [PATCH] Added the ability to schedule packets after an event has succeeded. Also fixes post listeners and asynchronous packet listeners. --- .../protocol/events/NetworkMarker.java | 27 +++- .../protocol/events/PacketContainer.java | 11 ++ .../protocol/events/PacketEvent.java | 23 +++ .../protocol/events/ScheduledPacket.java | 146 ++++++++++++++++++ .../protocol/injector/NetworkProcessor.java | 30 +++- .../injector/netty/ChannelInjector.java | 24 ++- .../injector/netty/ChannelListener.java | 3 +- .../injector/netty/NettyProtocolInjector.java | 16 +- .../injector/packet/ReadPacketModifier.java | 2 +- .../injector/packet/WritePacketModifier.java | 4 +- 10 files changed, 263 insertions(+), 23 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/events/ScheduledPacket.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java index 193ddc8c..b774054c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java @@ -51,8 +51,10 @@ public abstract class NetworkMarker { // Custom network handler private PriorityQueue outputHandlers; - // Sent listeners + // Post listeners private List postListeners; + // Post packets + private List scheduledPackets; // The input buffer private ByteBuffer inputBuffer; @@ -295,6 +297,18 @@ public abstract class NetworkMarker { return postListeners != null ? Collections.unmodifiableList(postListeners) : Collections.emptyList(); } + /** + * Retrieve a list of packets that will be schedule (in-order) when the current packet has been successfully transmitted. + *

+ * This list is modifiable. + * @return List of packets that will be scheduled. + */ + public List getScheduledPackets() { + if (scheduledPackets == null) + scheduledPackets = Lists.newArrayList(); + return scheduledPackets; + } + /** * Ensure that the packet event is server side. */ @@ -385,4 +399,15 @@ public abstract class NetworkMarker { public static NetworkMarker getNetworkMarker(PacketEvent event) { return event.networkMarker; } + + /** + * Retrieve the scheduled packets of a particular network marker without constructing the list. + *

+ * This is an internal method that should not be used by API users. + * @param marker - the marker. + * @return The list, or NULL if not found or initialized. + */ + public static List readScheduledPackets(NetworkMarker marker) { + return marker.scheduledPackets; + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 91bc1cb2..3fccc55b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -49,6 +49,7 @@ import org.bukkit.inventory.ItemStack; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Protocol; +import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -206,6 +207,16 @@ public class PacketContainer implements Serializable { this.structureModifier = structure; } + /** + * Construct a new packet container from a given handle. + * @param packet - the NMS packet. + * @return The packet container. + */ + public static PacketContainer fromPacket(Object packet) { + PacketType type = PacketType.fromClass(packet.getClass()); + return new PacketContainer(type, packet); + } + /** * For serialization. */ diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java index 76c4cab0..7e6b054f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java @@ -101,6 +101,7 @@ public class PacketEvent extends EventObject implements Cancellable { this.cancel = origial.cancel; this.serverPacket = origial.serverPacket; this.filtered = origial.filtered; + this.networkMarker = origial.networkMarker; this.asyncMarker = asyncMarker; this.asynchronous = true; } @@ -401,6 +402,28 @@ public class PacketEvent extends EventObject implements Cancellable { return asynchronous; } + /** + * Schedule a packet for sending or receiving after the current packet event is successful. + *

+ * The packet will be added to {@link NetworkMarker#getScheduledPackets()}. + * @param scheduled - the packet to transmit or receive. + */ + public void schedule(ScheduledPacket scheduled) { + getNetworkMarker().getScheduledPackets().add(scheduled); + } + + /** + * Unschedule a specific packet. + * @param scheduled - the scheduled packet. + * @return TRUE if it was unscheduled, FALSE otherwise. + */ + public boolean unschedule(ScheduledPacket scheduled) { + if (networkMarker != null) { + return networkMarker.getScheduledPackets().remove(scheduled); + } + return false; + } + private void writeObject(ObjectOutputStream output) throws IOException { // Default serialization output.defaultWriteObject(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ScheduledPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ScheduledPacket.java new file mode 100644 index 00000000..b32bfefe --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ScheduledPacket.java @@ -0,0 +1,146 @@ +package com.comphenix.protocol.events; + +import java.lang.reflect.InvocationTargetException; + +import org.bukkit.entity.Player; + +import com.comphenix.protocol.PacketStream; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.PacketType.Sender; +import com.google.common.base.Preconditions;import com.google.common.base.Objects; + +/** + * Represents a packet that is scheduled for transmission at a later stage. + * @author Kristian + */ +public class ScheduledPacket { + protected PacketContainer packet; + protected Player target; + protected boolean filtered; + + /** + * Construct a new scheduled packet. + *

+ * Note that the sender is infered from the packet type. + * @param packet - the packet. + * @param target - the target player. + * @param filtered - whether or not to + */ + public ScheduledPacket(PacketContainer packet, Player target, boolean filtered) { + setPacket(packet); + setTarget(target); + setFiltered(filtered); + } + + /** + * Construct a new scheduled packet that will not be processed by any packet listeners (except MONITOR). + * @param packet - the packet. + * @param target - the target player. + * @return The scheduled packet. + */ + public static ScheduledPacket fromSilent(PacketContainer packet, Player target) { + return new ScheduledPacket(packet, target, false); + } + + /** + * Construct a new scheduled packet that will be processed by any packet listeners. + * @param packet - the packet. + * @param target - the target player. + * @return The scheduled packet. + */ + public static ScheduledPacket fromFiltered(PacketContainer packet, Player target) { + return new ScheduledPacket(packet, target, true); + } + + /** + * Retrieve the packet that will be sent or transmitted. + * @return The sent or received packet. + */ + public PacketContainer getPacket() { + return packet; + } + + /** + * Set the packet that will be sent or transmitted. + * @param packet - the new packet, cannot be NULL. + */ + public void setPacket(PacketContainer packet) { + this.packet = Preconditions.checkNotNull(packet, "packet cannot be NULL"); + } + + /** + * Retrieve the target player. + * @return The target player. + */ + public Player getTarget() { + return target; + } + + /** + * Set the target player. + * @param target - the new target, cannot be NULL. + */ + public void setTarget(Player target) { + this.target = Preconditions.checkNotNull(target, "target cannot be NULL"); + } + + /** + * Determine if this packet will be processed by any of the packet listeners. + * @return TRUE if it will, FALSE otherwise. + */ + public boolean isFiltered() { + return filtered; + } + + /** + * Set whether or not this packet will be processed by packet listeners (except MONITOR listeners). + * @param filtered - TRUE if it should be processed by listeners, FALSE otherwise. + */ + public void setFiltered(boolean filtered) { + this.filtered = filtered; + } + + /** + * Retrieve the sender of this packet. + * @return The sender. + */ + public Sender getSender() { + return packet.getType().getSender(); + } + + /** + * Schedule the packet transmission or reception. + */ + public void schedule() { + schedule(ProtocolLibrary.getProtocolManager()); + } + + /** + * Schedule the packet transmission or reception. + * @param stream - the packet stream. + */ + public void schedule(PacketStream stream) { + Preconditions.checkNotNull(stream, "stream cannot be NULL"); + + try { + if (getSender() == Sender.CLIENT) { + stream.recieveClientPacket(getTarget(), getPacket(), isFiltered()); + } else { + stream.sendServerPacket(getTarget(), getPacket(), isFiltered()); + } + } catch (InvocationTargetException e) { + throw new RuntimeException("Cannot send packet " + this + " to " + stream); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot send packet " + this + " to " + stream); + } + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("packet", packet) + .add("target", target) + .add("filtered", filtered) + .toString(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java index b74a2e81..28af22ee 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java @@ -1,12 +1,16 @@ package com.comphenix.protocol.injector; +import java.util.List; import java.util.PriorityQueue; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketOutputHandler; import com.comphenix.protocol.events.PacketPostListener; +import com.comphenix.protocol.events.ScheduledPacket; /** * Represents a processor for network markers. @@ -61,10 +65,13 @@ public class NetworkProcessor { } /** - * Invoke the post listeners, if any. + * Invoke the post listeners and packet transmission, if any. * @param marker - the network marker, or NULL. */ - public void invokePostListeners(PacketEvent event, NetworkMarker marker) { + public void invokePostEvent(PacketEvent event, NetworkMarker marker) { + if (marker == null) + return; + if (NetworkMarker.hasPostListeners(marker)) { // Invoke every sent listener for (PacketPostListener listener : marker.getPostListeners()) { @@ -79,5 +86,22 @@ public class NetworkProcessor { } } } + sendScheduledPackets(marker); } -} + + /** + * Send any scheduled packets. + * @param marker - the network marker. + */ + private void sendScheduledPackets(NetworkMarker marker) { + // Next, invoke post packet transmission + List scheduled = NetworkMarker.readScheduledPackets(marker); + ProtocolManager manager = ProtocolLibrary.getProtocolManager(); + + if (scheduled != null) { + for (ScheduledPacket packet : scheduled) { + packet.schedule(manager); + } + } + } + } 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 b66d9bf6..b102c5bd 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 @@ -36,6 +36,7 @@ import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.NetworkProcessor; import com.comphenix.protocol.injector.server.SocketInjector; @@ -261,12 +262,21 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { } protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) { - // See if we've been instructed not to process packets - if (!scheduleProcessPackets.get()) - return BYPASSED_PACKET; - // Let the filters handle this packet Object original = accessor.get(instance); + + // See if we've been instructed not to process packets + if (!scheduleProcessPackets.get()) { + NetworkMarker marker = getMarker(original); + + if (marker != null) { + PacketEvent result = new PacketEvent(ChannelInjector.class); + result.setNetworkMarker(marker); + return result; + } else { + return BYPASSED_PACKET; + } + } PacketEvent event = processSending(original); if (event != null && !event.isCancelled()) { @@ -291,7 +301,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { * @return The resulting message/packet. */ private PacketEvent processSending(Object message) { - return channelListener.onPacketSending(ChannelInjector.this, message); + return channelListener.onPacketSending(ChannelInjector.this, message, getMarker(message)); } /** @@ -395,7 +405,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { finalEvent = null; currentEvent = null; - processor.invokePostListeners(event, NetworkMarker.getNetworkMarker(event)); + processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event)); } } @@ -463,7 +473,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { NetworkMarker marker = NetworkMarker.getNetworkMarker(event); if (marker != null) { - processor.invokePostListeners(event, marker); + processor.invokePostEvent(event, marker); } } } 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 index 9593f656..e0ecd05f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java @@ -16,9 +16,10 @@ interface ChannelListener { * This is invoked on the main thread. * @param injector - the channel injector. * @param packet - the packet. + * @param marker - the network marker. * @return The packet even that was passed to the listeners, with a possible packet change, or NULL. */ - public PacketEvent onPacketSending(Injector injector, Object packet); + public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker); /** * Invoked when a packet is being received from a client. 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 deb8b15d..6884da5a 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 @@ -226,23 +226,23 @@ public class NettyProtocolInjector implements ChannelListener { } } - @Override - public PacketEvent onPacketSending(Injector injector, Object packet) { + @Override + public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) { Class clazz = packet.getClass(); - if (sendingFilters.contains(clazz)) { + if (sendingFilters.contains(clazz) || marker != null) { PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet); - return packetQueued(container, injector.getPlayer()); + return packetQueued(container, injector.getPlayer(), marker); } // Don't change anything return null; - } + } @Override public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) { Class clazz = packet.getClass(); - if (reveivedFilters.contains(clazz)) { + if (reveivedFilters.contains(clazz) || marker != null) { PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet); return packetReceived(container, injector.getPlayer(), marker); } @@ -261,8 +261,8 @@ public class NettyProtocolInjector implements ChannelListener { * @param receiver - the receiver of this packet. * @return The packet event that was used. */ - private PacketEvent packetQueued(PacketContainer packet, Player receiver) { - PacketEvent event = PacketEvent.fromServer(this, packet, receiver); + private PacketEvent packetQueued(PacketContainer packet, Player receiver, NetworkMarker marker) { + PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver); invoker.invokePacketSending(event); return event; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java index 20995bf1..815b1639 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java @@ -163,7 +163,7 @@ class ReadPacketModifier implements MethodInterceptor { // This is fine - received packets are enqueued in any case NetworkMarker marker = NetworkMarker.getNetworkMarker(event); - processor.invokePostListeners(event, marker); + processor.invokePostEvent(event, marker); } } catch (OutOfMemoryError e) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java index 367e51f6..b5662eee 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java @@ -105,7 +105,7 @@ public class WritePacketModifier implements MethodInterceptor { output.write(outputBuffer); // We're done - processor.invokePostListeners(information.event, information.marker); + processor.invokePostEvent(information.event, information.marker); return null; } catch (OutOfMemoryError e) { @@ -122,7 +122,7 @@ public class WritePacketModifier implements MethodInterceptor { // Invoke this write method first proxy.invoke(information.proxyObject, args); - processor.invokePostListeners(information.event, information.marker); + processor.invokePostEvent(information.event, information.marker); return null; }