Added the ability to schedule packets after an event has succeeded.

Also fixes post listeners and asynchronous packet listeners.
This commit is contained in:
Kristian S. Stangeland 2014-04-26 00:20:50 +02:00
parent f7c4fd4ec9
commit 1ef602416d
10 changed files with 263 additions and 23 deletions

View File

@ -51,8 +51,10 @@ public abstract class NetworkMarker {
// Custom network handler
private PriorityQueue<PacketOutputHandler> outputHandlers;
// Sent listeners
// Post listeners
private List<PacketPostListener> postListeners;
// Post packets
private List<ScheduledPacket> scheduledPackets;
// The input buffer
private ByteBuffer inputBuffer;
@ -295,6 +297,18 @@ public abstract class NetworkMarker {
return postListeners != null ? Collections.unmodifiableList(postListeners) : Collections.<PacketPostListener>emptyList();
}
/**
* Retrieve a list of packets that will be schedule (in-order) when the current packet has been successfully transmitted.
* <p>
* This list is modifiable.
* @return List of packets that will be scheduled.
*/
public List<ScheduledPacket> 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.
* <p>
* 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<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
return marker.scheduledPackets;
}
}

View File

@ -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.
*/

View File

@ -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.
* <p>
* 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();

View File

@ -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.
* <p>
* 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();
}
}

View File

@ -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<ScheduledPacket> scheduled = NetworkMarker.readScheduledPackets(marker);
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
if (scheduled != null) {
for (ScheduledPacket packet : scheduled) {
packet.schedule(manager);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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.

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}