Java tutorial
/* * 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.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitScheduler; import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.PrioritizedListener; import com.comphenix.protocol.injector.SortedPacketListenerList; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; /** * Represents a filter manager for asynchronous packets. * <p> * By using {@link AsyncMarker#incrementProcessingDelay()}, a packet can be delayed without having to block the * processing thread. * @author Kristian */ public class AsyncFilterManager implements AsynchronousManager { private SortedPacketListenerList serverTimeoutListeners; private SortedPacketListenerList clientTimeoutListeners; private Set<PacketListener> timeoutListeners; private PacketProcessingQueue serverProcessingQueue; private PacketProcessingQueue clientProcessingQueue; // Sending queues private final PlayerSendingHandler playerSendingHandler; // Report exceptions private final ErrorReporter reporter; // The likely main thread private final Thread mainThread; // Default scheduler private final BukkitScheduler scheduler; // Current packet index private final AtomicInteger currentSendingIndex = new AtomicInteger(); // Our protocol manager private ProtocolManager manager; /** * Initialize a asynchronous filter manager. * <p> * <b>Internal method</b>. Retrieve the global asynchronous manager from the protocol manager instead. * @param reporter - desired error reporter. * @param scheduler - task scheduler. */ public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) { // Initialize timeout listeners this.serverTimeoutListeners = new SortedPacketListenerList(); this.clientTimeoutListeners = new SortedPacketListenerList(); this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>()); this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners); this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler); this.clientProcessingQueue = new PacketProcessingQueue(playerSendingHandler); this.playerSendingHandler.initializeScheduler(); this.scheduler = scheduler; this.reporter = reporter; this.mainThread = Thread.currentThread(); } /** * Retrieve the protocol manager. * @return The protocol manager. */ public ProtocolManager getManager() { return manager; } /** * Set the associated protocol manager. * @param manager - the new manager. */ public void setManager(ProtocolManager manager) { this.manager = manager; } @Override public AsyncListenerHandler registerAsyncHandler(PacketListener listener) { return registerAsyncHandler(listener, true); } @Override public void registerTimeoutHandler(PacketListener listener) { if (listener == null) throw new IllegalArgumentException("listener cannot be NULL."); if (!timeoutListeners.add(listener)) return; ListeningWhitelist sending = listener.getSendingWhitelist(); ListeningWhitelist receiving = listener.getReceivingWhitelist(); if (!ListeningWhitelist.isEmpty(sending)) serverTimeoutListeners.addListener(listener, sending); if (!ListeningWhitelist.isEmpty(receiving)) serverTimeoutListeners.addListener(listener, receiving); } @Override public Set<PacketListener> getTimeoutHandlers() { return ImmutableSet.copyOf(timeoutListeners); } @Override public Set<PacketListener> getAsyncHandlers() { ImmutableSet.Builder<PacketListener> builder = ImmutableSet.builder(); // Add every asynchronous packet listener for (PrioritizedListener<AsyncListenerHandler> handler : Iterables.concat(serverProcessingQueue.values(), clientProcessingQueue.values())) { builder.add(handler.getListener().getAsyncListener()); } return builder.build(); } /** * Registers an asynchronous packet handler. * <p> * Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted. * <p> * To start listening asynchronously, pass the getListenerLoop() runnable to a different thread. * <p> * Asynchronous events will only be executed if a synchronous listener with the same packets is registered. * If you already have a synchronous event, call this method with autoInject set to FALSE. * * @param listener - the packet listener that will receive these asynchronous events. * @param autoInject - whether or not to automatically create the corresponding synchronous listener, * @return An asynchronous handler. */ public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) { AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener); ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist(); ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist(); if (!hasValidWhitelist(sendingWhitelist) && !hasValidWhitelist(receivingWhitelist)) { throw new IllegalArgumentException("Listener has an empty sending and receiving whitelist."); } // Add listener to either or both processing queue if (hasValidWhitelist(sendingWhitelist)) { manager.verifyWhitelist(listener, sendingWhitelist); serverProcessingQueue.addListener(handler, sendingWhitelist); } if (hasValidWhitelist(receivingWhitelist)) { manager.verifyWhitelist(listener, receivingWhitelist); clientProcessingQueue.addListener(handler, receivingWhitelist); } // We need a synchronized listener to get the ball rolling if (autoInject) { handler.setNullPacketListener(new NullPacketListener(listener)); manager.addPacketListener(handler.getNullPacketListener()); } return handler; } private boolean hasValidWhitelist(ListeningWhitelist whitelist) { return whitelist != null && whitelist.getTypes().size() > 0; } @Override public void unregisterTimeoutHandler(PacketListener listener) { if (listener == null) throw new IllegalArgumentException("listener cannot be NULL."); ListeningWhitelist sending = listener.getSendingWhitelist(); ListeningWhitelist receiving = listener.getReceivingWhitelist(); // Do it in the opposite order if (serverTimeoutListeners.removeListener(listener, sending).size() > 0 || clientTimeoutListeners.removeListener(listener, receiving).size() > 0) { timeoutListeners.remove(listener); } } @Override public void unregisterAsyncHandler(PacketListener listener) { if (listener == null) throw new IllegalArgumentException("listener cannot be NULL."); AsyncListenerHandler handler = findHandler(serverProcessingQueue, listener.getSendingWhitelist(), listener); if (handler == null) { handler = findHandler(clientProcessingQueue, listener.getReceivingWhitelist(), listener); } unregisterAsyncHandler(handler); } // Search for the first correct handler private AsyncListenerHandler findHandler(PacketProcessingQueue queue, ListeningWhitelist search, PacketListener target) { if (ListeningWhitelist.isEmpty(search)) return null; for (PacketType type : search.getTypes()) { for (PrioritizedListener<AsyncListenerHandler> element : queue.getListener(type)) { if (element.getListener().getAsyncListener() == target) { return element.getListener(); } } } return null; } @Override public void unregisterAsyncHandler(AsyncListenerHandler handler) { if (handler == null) throw new IllegalArgumentException("listenerToken cannot be NULL"); handler.cancel(); } // Called by AsyncListenerHandler void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) { PacketListener listener = handler.getAsyncListener(); boolean synchronusOK = onMainThread(); // Unregister null packet listeners if (handler.getNullPacketListener() != null) { manager.removePacketListener(handler.getNullPacketListener()); } // Just remove it from the queue(s) if (hasValidWhitelist(listener.getSendingWhitelist())) { List<PacketType> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist()); // We're already taking care of this, so don't do anything playerSendingHandler.sendServerPackets(removed, synchronusOK); } if (hasValidWhitelist(listener.getReceivingWhitelist())) { List<PacketType> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist()); playerSendingHandler.sendClientPackets(removed, synchronusOK); } } /** * Determine if we're running on the main thread. * @return TRUE if we are, FALSE otherwise. */ private boolean onMainThread() { return Thread.currentThread().getId() == mainThread.getId(); } @Override public void unregisterAsyncHandlers(Plugin plugin) { unregisterAsyncHandlers(serverProcessingQueue, plugin); unregisterAsyncHandlers(clientProcessingQueue, plugin); } private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) { // Iterate through every packet listener for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) { // Remove the listener if (Objects.equal(listener.getListener().getPlugin(), plugin)) { unregisterAsyncHandler(listener.getListener()); } } } /** * Enqueue a packet for asynchronous processing. * * @param syncPacket - synchronous packet event. * @param asyncMarker - the asynchronous marker to use. */ public synchronized void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) { PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker); if (asyncMarker.isQueued() || asyncMarker.isTransmitted()) throw new IllegalArgumentException("Cannot queue a packet that has already been queued."); asyncMarker.setQueuedSendingIndex(asyncMarker.getNewSendingIndex()); // The player is only be null when they're logged out, // so this should be a pretty safe check Player player = newEvent.getPlayer(); if (player != null) { // Start the process getSendingQueue(syncPacket).enqueue(newEvent); // We know this is occuring on the main thread, so pass TRUE getProcessingQueue(syncPacket).enqueue(newEvent, true); } } @Override public Set<Integer> getSendingFilters() { return PacketRegistry.toLegacy(serverProcessingQueue.keySet()); } @Override public Set<PacketType> getReceivingTypes() { return serverProcessingQueue.keySet(); } @Override public Set<Integer> getReceivingFilters() { return PacketRegistry.toLegacy(clientProcessingQueue.keySet()); } @Override public Set<PacketType> getSendingTypes() { return clientProcessingQueue.keySet(); } /** * Retrieve the current task scheduler. * @return Current task scheduler. */ public BukkitScheduler getScheduler() { return scheduler; } @Override public boolean hasAsynchronousListeners(PacketEvent packet) { Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketType()); return list != null && list.size() > 0; } /** * Construct a asynchronous marker with all the default values. * @return Asynchronous marker. */ public AsyncMarker createAsyncMarker() { return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA); } /** * Construct an async marker with the given sending priority delta and timeout delta. * @param timeoutDelta - how long (in ms) until the packet expire. * @return An async marker. */ public AsyncMarker createAsyncMarker(long timeoutDelta) { return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet()); } // Helper method private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) { return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta); } @Override public PacketStream getPacketStream() { return manager; } @Override public ErrorReporter getErrorReporter() { return reporter; } @Override public void cleanupAll() { serverProcessingQueue.cleanupAll(); playerSendingHandler.cleanupAll(); timeoutListeners.clear(); serverTimeoutListeners = null; clientTimeoutListeners = null; } @Override public void signalPacketTransmission(PacketEvent packet) { signalPacketTransmission(packet, onMainThread()); } /** * Signal that a packet is ready to be transmitted. * @param packet - packet to signal. * @param onMainThread - whether or not this method was run by the main thread. */ private void signalPacketTransmission(PacketEvent packet, boolean onMainThread) { AsyncMarker marker = packet.getAsyncMarker(); if (marker == null) throw new IllegalArgumentException("A sync packet cannot be transmitted by the asynchronous manager."); if (!marker.isQueued()) throw new IllegalArgumentException("A packet must have been queued before it can be transmitted."); // Only send if the packet is ready if (marker.decrementProcessingDelay() == 0) { PacketSendingQueue queue = getSendingQueue(packet, false); // No need to create a new queue if the player has logged out if (queue != null) queue.signalPacketUpdate(packet, onMainThread); } } /** * Retrieve the sending queue this packet belongs to. * @param packet - the packet. * @return The server or client sending queue the packet belongs to. */ public PacketSendingQueue getSendingQueue(PacketEvent packet) { return playerSendingHandler.getSendingQueue(packet); } /** * Retrieve the sending queue this packet belongs to. * @param packet - the packet. * @param createNew - if TRUE, create a new queue if it hasn't already been created. * @return The server or client sending queue the packet belongs to. */ public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) { return playerSendingHandler.getSendingQueue(packet, createNew); } /** * Retrieve the processing queue this packet belongs to. * @param packet - the packet. * @return The server or client sending processing the packet belongs to. */ public PacketProcessingQueue getProcessingQueue(PacketEvent packet) { return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue; } /** * Signal that a packet has finished processing. * @param packet - packet to signal. */ public void signalFreeProcessingSlot(PacketEvent packet) { getProcessingQueue(packet).signalProcessingDone(); } /** * Send any due packets, or clean up packets that have expired. * @param tickCounter Tick counter * @param onMainThread Whether or not to execute on the main thread */ public void sendProcessedPackets(int tickCounter, boolean onMainThread) { // The server queue is unlikely to need checking that often if (tickCounter % 10 == 0) { playerSendingHandler.trySendServerPackets(onMainThread); } playerSendingHandler.trySendClientPackets(onMainThread); } /** * Clean up after a given player has logged out. * @param player - the player that has just logged out. */ public void removePlayer(Player player) { playerSendingHandler.removePlayer(player); } }