com.comphenix.protocol.async.PacketProcessingQueue.java Source code

Java tutorial

Introduction

Here is the source code for com.comphenix.protocol.async.PacketProcessingQueue.java

Source

/*
 *  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.util.Collection;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.Semaphore;

import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.MinMaxPriorityQueue;

/**
 * Handles the processing of every packet type.
 * 
 * @author Kristian
 */
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
    public static final ReportType REPORT_GUAVA_CORRUPT_MISSING = new ReportType(
            "Guava is either missing or corrupt. Reverting to PriorityQueue.");

    // Initial number of elements
    public static final int INITIAL_CAPACITY = 64;

    /**
     * Default maximum number of packets to process concurrently.
     */
    public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;

    /**
     * Default maximum number of packets to queue for processing.
     */
    public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;

    /**
     * Number of packets we're processing concurrently.
     */
    private final int maximumConcurrency;
    private Semaphore concurrentProcessing;

    // Queued packets for being processed
    private Queue<PacketEventHolder> processingQueue;

    // Packets for sending
    private PlayerSendingHandler sendingHandler;

    public PacketProcessingQueue(PlayerSendingHandler sendingHandler) {
        this(sendingHandler, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
    }

    public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize,
            int maximumConcurrency) {
        super();

        try {
            this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.expectedSize(initialSize)
                    .maximumSize(maximumSize).<PacketEventHolder>create(), null);
        } catch (IncompatibleClassChangeError e) {
            // Print in the console
            ProtocolLibrary.getErrorReporter().reportWarning(this,
                    Report.newBuilder(REPORT_GUAVA_CORRUPT_MISSING).error(e));

            // It's a Beta class after all
            this.processingQueue = Synchronization.queue(new PriorityQueue<PacketEventHolder>(), null);
        }

        this.maximumConcurrency = maximumConcurrency;
        this.concurrentProcessing = new Semaphore(maximumConcurrency);
        this.sendingHandler = sendingHandler;
    }

    /**
     * Enqueue a packet for processing by the asynchronous listeners.
     * @param packet - packet to process.
     * @param onMainThread - whether or not this is occuring on the main thread.
     * @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
     */
    public boolean enqueue(PacketEvent packet, boolean onMainThread) {
        try {
            processingQueue.add(new PacketEventHolder(packet));

            // Begin processing packets
            signalBeginProcessing(onMainThread);
            return true;
        } catch (IllegalStateException e) {
            return false;
        }
    }

    /**
     * Number of packet events in the queue.
     * @return The number of packet events in the queue.
     */
    public int size() {
        return processingQueue.size();
    }

    /**
     * Called by the current method and each thread to signal that a packet might be ready for processing.
     * @param onMainThread - whether or not this is occuring on the main thread.
     */
    public void signalBeginProcessing(boolean onMainThread) {
        while (concurrentProcessing.tryAcquire()) {
            PacketEventHolder holder = processingQueue.poll();

            // Any packet queued?
            if (holder != null) {
                PacketEvent packet = holder.getEvent();
                AsyncMarker marker = packet.getAsyncMarker();
                Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketType());

                marker.incrementProcessingDelay();

                // Yes, removing the marker will cause the chain to stop
                if (list != null) {
                    Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();

                    if (iterator.hasNext()) {
                        marker.setListenerTraversal(iterator);
                        iterator.next().getListener().enqueuePacket(packet);
                        continue;
                    }
                }

                // The packet has no further listeners. Just send it.
                if (marker.decrementProcessingDelay() == 0) {
                    PacketSendingQueue sendingQueue = sendingHandler.getSendingQueue(packet, false);

                    // In case the player has logged out
                    if (sendingQueue != null)
                        sendingQueue.signalPacketUpdate(packet, onMainThread);
                }
                signalProcessingDone();

            } else {
                // No more queued packets.
                signalProcessingDone();
                return;
            }
        }
    }

    /**
     * Called when a packet has been processed.
     */
    public void signalProcessingDone() {
        concurrentProcessing.release();
    }

    /**
     * Retrieve the maximum number of packets to process at any given time.
     * @return Number of simultaneous packet to process.
     */
    public int getMaximumConcurrency() {
        return maximumConcurrency;
    }

    public void cleanupAll() {
        // Cancel all the threads and every listener
        for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
            if (handler != null) {
                handler.getListener().cancel();
            }
        }

        // Remove the rest, just in case
        clearListeners();

        // Remove every packet in the queue
        processingQueue.clear();
    }
}