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

* Asynchronous listeners can use this to set packet timeout or transmission order. * * @author Kristian */ public class AsyncMarker 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_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> 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. *

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

* This is useful if an asynchronous listener is waiting for further information before the * packet can be sent to the user. A packet listener MUST eventually call * {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)}, * even if the packet is cancelled, after this method is called. *

* It is recommended that processing outside a packet listener is wrapped in a synchronized block * using the {@link #getProcessingLock()} method. *

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

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

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

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

* 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> getListenerTraversal() { return listenerTraversal; } /** * Set the iterator for the next listener. * @param listenerTraversal - the new async packet listener iterator. */ 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. */ 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 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()); } }