/* * 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.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.PriorityBlockingQueue; import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.reflect.FieldAccessException; /** * Represents packets ready to be transmitted to a client. * @author Kristian */ class PacketSendingQueue { public static final int INITIAL_CAPACITY = 64; private PriorityBlockingQueue sendingQueue; // Whether or not packet transmission can only occur on the main thread private final boolean synchronizeMain; /** * Number of packet events in the queue. * @return The number of packet events in the queue. */ public int size() { return sendingQueue.size(); } /** * Create a packet sending queue. * @param synchronizeMain - whether or not to synchronize with the main thread. */ public PacketSendingQueue(boolean synchronizeMain) { this.sendingQueue = new PriorityBlockingQueue(INITIAL_CAPACITY); this.synchronizeMain = synchronizeMain; } /** * Enqueue a packet for sending. * @param packet - packet to queue. */ public void enqueue(PacketEvent packet) { sendingQueue.add(new PacketEventHolder(packet)); } /** * Invoked when one of the packets have finished processing. * @param packetUpdated - the packet that has now been updated. * @param onMainThread - whether or not this is occuring on the main thread. */ public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) { AsyncMarker marker = packetUpdated.getAsyncMarker(); // Should we reorder the event? if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex()) { PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker); // "Cancel" the original event packetUpdated.setCancelled(true); // Enqueue the copy with the new sending index enqueue(copy); } // Mark this packet as finished marker.setProcessed(true); trySendPackets(onMainThread); } /*** * Invoked when a list of packet IDs are no longer associated with any listeners. * @param packetsRemoved - packets that no longer have any listeners. * @param onMainThread - whether or not this is occuring on the main thread. */ public synchronized void signalPacketUpdate(List packetsRemoved, boolean onMainThread) { Set lookup = new HashSet(packetsRemoved); // Note that this is O(n), so it might be expensive for (PacketEventHolder holder : sendingQueue) { PacketEvent event = holder.getEvent(); if (lookup.contains(event.getPacketID())) { event.getAsyncMarker().setProcessed(true); } } // This is likely to have changed the situation a bit trySendPackets(onMainThread); } /** * Attempt to send any remaining packets. * @param onMainThread - whether or not this is occuring on the main thread. */ public void trySendPackets(boolean onMainThread) { // Transmit as many packets as we can while (true) { PacketEventHolder holder = sendingQueue.peek(); if (holder != null) { PacketEvent current = holder.getEvent(); AsyncMarker marker = current.getAsyncMarker(); // Abort if we're not on the main thread if (synchronizeMain) { try { boolean wantAsync = marker.isMinecraftAsync(current); boolean wantSync = !wantAsync; // Quit if we haven't fulfilled our promise if ((onMainThread && wantAsync) || (!onMainThread && wantSync)) return; } catch (FieldAccessException e) { e.printStackTrace(); return; } } if (marker.isProcessed() || marker.hasExpired()) { if (marker.isProcessed() && !current.isCancelled()) { // Silently skip players that have logged out if (isOnline(current.getPlayer())) { sendPacket(current); } } sendingQueue.poll(); continue; } } // Only repeat when packets are removed break; } } private boolean isOnline(Player player) { return player != null && player.isOnline(); } /** * Send every packet, regardless of the processing state. */ private void forceSend() { while (true) { PacketEventHolder holder = sendingQueue.poll(); if (holder != null) { sendPacket(holder.getEvent()); } else { break; } } } /** * Whether or not the packet transmission must synchronize with the main thread. * @return TRUE if it must, FALSE otherwise. */ public boolean isSynchronizeMain() { return synchronizeMain; } /** * Transmit a packet, if it hasn't already. * @param event - the packet to transmit. */ private void sendPacket(PacketEvent event) { AsyncMarker marker = event.getAsyncMarker(); try { // Don't send a packet twice if (marker != null && !marker.isTransmitted()) { marker.sendPacket(event); } } catch (PlayerLoggedOutException e) { System.out.println(String.format( "Warning: Dropped packet index %s of ID %s", marker.getOriginalSendingIndex(), event.getPacketID() )); } catch (IOException e) { // Just print the error e.printStackTrace(); } } /** * Automatically transmits every delayed packet. */ public void cleanupAll() { // Note that the cleanup itself will always occur on the main thread forceSend(); } }