451 lines
14 KiB
Java
451 lines
14 KiB
Java
/*
|
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
|
* Copyright (C) 2012 Kristian S. Stangeland
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with this program;
|
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
* 02111-1307 USA
|
|
*/
|
|
|
|
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 java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
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;
|
|
|
|
/**
|
|
* Contains information about the packet that is being processed by asynchronous listeners.
|
|
* <p>
|
|
* Asynchronous listeners can use this to set packet timeout or transmission order.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
|
|
|
/**
|
|
* Generated by Eclipse.
|
|
*/
|
|
private static final long serialVersionUID = -2621498096616187384L;
|
|
|
|
/**
|
|
* Default number of milliseconds until a packet will rejected.
|
|
*/
|
|
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.
|
|
*/
|
|
private transient PacketStream packetStream;
|
|
|
|
/**
|
|
* Current list of async packet listeners.
|
|
*/
|
|
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
|
|
|
|
// Timeout handling
|
|
private long initialTime;
|
|
private long timeout;
|
|
|
|
// Packet order
|
|
private long originalSendingIndex;
|
|
private long newSendingIndex;
|
|
|
|
// Used to determine if a packet must be reordered in the sending queue
|
|
private Long queuedSendingIndex;
|
|
|
|
// Whether or not the packet has been processed by the listeners
|
|
private volatile boolean processed;
|
|
|
|
// 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;
|
|
|
|
// Whether or not to delay processing
|
|
private AtomicInteger processingDelay = new AtomicInteger();
|
|
|
|
// Used to synchronize processing on the shared PacketEvent
|
|
private Object processingLock = new Object();
|
|
|
|
// Used to identify the asynchronous worker
|
|
private AsyncListenerHandler listenerHandler;
|
|
private int workerID;
|
|
|
|
// Determine if Minecraft processes this packet asynchronously
|
|
private static Method isMinecraftAsync;
|
|
private static boolean alwaysSync;
|
|
|
|
/**
|
|
* Create a container for asyncronous packets.
|
|
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
|
|
*/
|
|
AsyncMarker(PacketStream packetStream, long sendingIndex, long sendingDelta, long initialTime, long timeoutDelta) {
|
|
if (packetStream == null)
|
|
throw new IllegalArgumentException("packetStream cannot be NULL");
|
|
|
|
this.packetStream = packetStream;
|
|
|
|
// Timeout
|
|
this.initialTime = initialTime;
|
|
this.timeout = initialTime + timeoutDelta;
|
|
|
|
// 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;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
public long getOriginalSendingIndex() {
|
|
return originalSendingIndex;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the desired sending order after processing has completed.
|
|
* <p>
|
|
* Higher sending order means lower priority.
|
|
* @return Desired sending order.
|
|
*/
|
|
public long getNewSendingIndex() {
|
|
return newSendingIndex;
|
|
}
|
|
|
|
/**
|
|
* Sets the desired sending order after processing has completed.
|
|
* <p>
|
|
* Higher sending order means lower priority.
|
|
* @param newSendingIndex - new packet send index.
|
|
*/
|
|
public void setNewSendingIndex(long newSendingIndex) {
|
|
this.newSendingIndex = newSendingIndex;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
public 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;
|
|
}
|
|
|
|
/**
|
|
* Increment the number of times this packet must be signalled as done before its transmitted.
|
|
* <p>
|
|
* This is useful if an asynchronous listener is waiting for further information before the
|
|
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
|
|
* {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)},
|
|
* even if the packet is cancelled, after this method is called.
|
|
* <p>
|
|
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
|
|
* using the {@link #getProcessingLock()} method.
|
|
* <p>
|
|
* To decrement the processing delay, call signalPacketUpdate. A thread that calls this method
|
|
* multiple times must call signalPacketUpdate at least that many times.
|
|
* @return The new processing delay.
|
|
*/
|
|
public int incrementProcessingDelay() {
|
|
return processingDelay.incrementAndGet();
|
|
}
|
|
|
|
/**
|
|
* Decrement the number of times this packet must be signalled as done before it's transmitted.
|
|
* @return The new processing delay. If zero, the packet should be sent.
|
|
*/
|
|
int decrementProcessingDelay() {
|
|
return processingDelay.decrementAndGet();
|
|
}
|
|
|
|
/**
|
|
* Retrieve the number of times a packet must be signalled to be done before it's sent.
|
|
* @return Number of processing delays.
|
|
*/
|
|
public int getProcessingDelay() {
|
|
return processingDelay.get();
|
|
}
|
|
|
|
/**
|
|
* Whether or not this packet is or has been queued for processing.
|
|
* @return TRUE if it has, FALSE otherwise.
|
|
*/
|
|
public boolean isQueued() {
|
|
return queuedSendingIndex != null;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the sending index when the packet was queued.
|
|
* @return Queued sending index.
|
|
*/
|
|
public long getQueuedSendingIndex() {
|
|
return queuedSendingIndex != null ? queuedSendingIndex : 0;
|
|
}
|
|
|
|
/**
|
|
* Set the sending index when the packet was queued.
|
|
* @param queuedSendingIndex - sending index.
|
|
*/
|
|
void setQueuedSendingIndex(Long queuedSendingIndex) {
|
|
this.queuedSendingIndex = queuedSendingIndex;
|
|
}
|
|
|
|
/**
|
|
* Processing lock used to synchronize access to the parent PacketEvent and PacketContainer.
|
|
* <p>
|
|
* This lock is automatically acquired for every asynchronous packet listener. It should only be
|
|
* used to synchronize access to a PacketEvent if it's processing has been delayed.
|
|
* @return A processing lock.
|
|
*/
|
|
public Object getProcessingLock() {
|
|
return processingLock;
|
|
}
|
|
|
|
public void setProcessingLock(Object processingLock) {
|
|
this.processingLock = processingLock;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* <p>
|
|
* This is only relevant during the synchronous processing. Asynchronous
|
|
* listeners should use the normal cancel-field to cancel a PacketEvent.
|
|
*
|
|
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
|
|
*/
|
|
public void setAsyncCancelled(boolean asyncCancelled) {
|
|
this.asyncCancelled = asyncCancelled;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the current asynchronous listener handler.
|
|
* @return Asychronous listener handler, or NULL if this packet is not asynchronous.
|
|
*/
|
|
public AsyncListenerHandler getListenerHandler() {
|
|
return listenerHandler;
|
|
}
|
|
|
|
/**
|
|
* Set the current asynchronous listener handler.
|
|
* <p>
|
|
* Used by the worker to update the value.
|
|
* @param listenerHandler - new listener handler.
|
|
*/
|
|
void setListenerHandler(AsyncListenerHandler listenerHandler) {
|
|
this.listenerHandler = listenerHandler;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the current worker ID.
|
|
* @return Current worker ID.
|
|
*/
|
|
public int getWorkerID() {
|
|
return workerID;
|
|
}
|
|
|
|
/**
|
|
* Set the current worker ID.
|
|
* <p>
|
|
* Used by the worker.
|
|
* @param workerID - new worker ID.
|
|
*/
|
|
void setWorkerID(int workerID) {
|
|
this.workerID = workerID;
|
|
}
|
|
|
|
/**
|
|
* Retrieve iterator for the next listener in line.
|
|
* @return Next async packet listener iterator.
|
|
*/
|
|
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
|
return listenerTraversal;
|
|
}
|
|
|
|
/**
|
|
* Set the iterator for the next listener.
|
|
* @param listenerTraversal - the new async packet listener iterator.
|
|
*/
|
|
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> 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.
|
|
*/
|
|
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);
|
|
}
|
|
transmitted = true;
|
|
|
|
} catch (InvocationTargetException e) {
|
|
throw new IOException("Cannot send packet", e);
|
|
} catch (IllegalAccessException e) {
|
|
throw new IOException("Cannot send packet", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 && !alwaysSync) {
|
|
try {
|
|
isMinecraftAsync = FuzzyReflection.fromClass(Packet.class).getMethodByName("a_.*");
|
|
} catch (RuntimeException e) {
|
|
// This will occur in 1.2.5 (or possibly in later versions)
|
|
List<Method> 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)
|
|
return 1;
|
|
else
|
|
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
|
|
}
|
|
}
|