/* * 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.injector; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.logging.Level; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.timing.TimedListenerManager; import com.comphenix.protocol.timing.TimedListenerManager.ListenerType; import com.comphenix.protocol.timing.TimedTracker; import javax.annotation.Nullable; /** * Registry of synchronous packet listeners. * * @author Kristian */ public final class SortedPacketListenerList extends AbstractConcurrentListenerMultimap { // The current listener manager private TimedListenerManager timedManager = TimedListenerManager.getInstance(); public SortedPacketListenerList() { super(); } /** * Invokes the given packet event for every registered listener. * @param reporter - the error reporter that will be used to inform about listener exceptions. * @param event - the packet event to invoke. */ public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event) { Collection> list = getListener(event.getPacketType()); if (list == null) return; // The returned list is thread-safe if (timedManager.isTiming()) { for (PrioritizedListener element : list) { TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE); long token = tracker.beginTracking(); // Measure and record the execution time invokeReceivingListener(reporter, event, element); tracker.endTracking(token, event.getPacketType()); } } else { for (PrioritizedListener element : list) { invokeReceivingListener(reporter, event, element); } } } /** * Invokes the given packet event for every registered listener of the given priority. * @param reporter - the error reporter that will be used to inform about listener exceptions. * @param event - the packet event to invoke. * @param priorityFilter - the required priority for a listener to be invoked. */ public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event, ListenerPriority priorityFilter) { Collection> list = getListener(event.getPacketType()); if (list == null) return; // The returned list is thread-safe if (timedManager.isTiming()) { for (PrioritizedListener element : list) { if (element.getPriority() == priorityFilter) { TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE); long token = tracker.beginTracking(); // Measure and record the execution time invokeReceivingListener(reporter, event, element); tracker.endTracking(token, event.getPacketType()); } } } else { for (PrioritizedListener element : list) { if (element.getPriority() == priorityFilter) { invokeReceivingListener(reporter, event, element); } } } } /** * Invoke a particular receiving listener. * @param reporter - the error reporter. * @param event - the related packet event. * @param element - the listener to invoke. */ private void invokeReceivingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener element) { try { event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR); element.getListener().onPacketReceiving(event); } catch (OutOfMemoryError | ThreadDeath e) { throw e; } catch (Throwable e) { // Minecraft doesn't want your Exception. reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e, event.getPacket().getHandle()); } } /** * Invokes the given packet event for every registered listener. * @param reporter - the error reporter that will be used to inform about listener exceptions. * @param event - the packet event to invoke. */ public void invokePacketSending(ErrorReporter reporter, PacketEvent event) { invokePacketSending(reporter, event, null); } /** * Invokes the given packet event for every registered listener of the given priority. * @param reporter - the error reporter that will be used to inform about listener exceptions. * @param event - the packet event to invoke. * @param priorityFilter - the priority for a listener to be invoked. If null is provided, every registered listener will be invoked */ public void invokePacketSending(ErrorReporter reporter, PacketEvent event, @Nullable ListenerPriority priorityFilter) { invokeUnpackedPacketSending(reporter, event, priorityFilter); if (event.getPacketType() == PacketType.Play.Server.BUNDLE && !event.isCancelled()) { // unpack the bundle and invoke for each packet in the bundle Iterable packets = event.getPacket().getPacketBundles().read(0); List outPackets = new ArrayList<>(); for (PacketContainer subPacket : packets) { if(subPacket == null) { ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "Failed to invoke packet event " + (priorityFilter == null ? "" : ("with priority " + priorityFilter)) + " in bundle because bundle contains null packet: " + packets, new Throwable()); continue; } PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(), event.getPlayer()); invokeUnpackedPacketSending(reporter, subPacketEvent, priorityFilter); if (!subPacketEvent.isCancelled()) { // if the packet event has been cancelled, the packet will be removed from the bundle PacketContainer packet = subPacketEvent.getPacket(); if(packet == null) { ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "null packet container returned for " + subPacketEvent, new Throwable()); } else if(packet.getHandle() == null) { ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "null packet handle returned for " + subPacketEvent, new Throwable()); } else { outPackets.add(packet); } } } if (packets.iterator().hasNext()) { event.getPacket().getPacketBundles().write(0, outPackets); } else { // cancel entire packet if each individual packet has been cancelled event.setCancelled(true); } } } private void invokeUnpackedPacketSending(ErrorReporter reporter, PacketEvent event, @org.jetbrains.annotations.Nullable ListenerPriority priorityFilter) { Collection> list = getListener(event.getPacketType()); if (list == null) return; if (timedManager.isTiming()) { for (PrioritizedListener element : list) { if (priorityFilter == null || element.getPriority() == priorityFilter) { TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE); long token = tracker.beginTracking(); // Measure and record the execution time invokeSendingListener(reporter, event, element); tracker.endTracking(token, event.getPacketType()); } } } else { for (PrioritizedListener element : list) { if (priorityFilter == null || element.getPriority() == priorityFilter) { invokeSendingListener(reporter, event, element); } } } } /** * Invoke a particular sending listener. * @param reporter - the error reporter. * @param event - the related packet event. * @param element - the listener to invoke. */ private void invokeSendingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener element) { try { event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR); element.getListener().onPacketSending(event); } catch (OutOfMemoryError | ThreadDeath e) { throw e; } catch (Throwable e) { // Minecraft doesn't want your Exception. reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending(PacketEvent)", e, event.getPacket().getHandle()); } } }