Merge branch 'master' into gh-pages
Dieser Commit ist enthalten in:
Commit
1d1f96cda4
@ -4,7 +4,7 @@
|
|||||||
<groupId>com.comphenix.protocol</groupId>
|
<groupId>com.comphenix.protocol</groupId>
|
||||||
<artifactId>ProtocolLib</artifactId>
|
<artifactId>ProtocolLib</artifactId>
|
||||||
<name>ProtocolLib</name>
|
<name>ProtocolLib</name>
|
||||||
<version>1.6.0</version>
|
<version>1.7.0</version>
|
||||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||||
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
|
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
|
||||||
<developers>
|
<developers>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.comphenix.protocol</groupId>
|
<groupId>com.comphenix.protocol</groupId>
|
||||||
<artifactId>ProtocolLib</artifactId>
|
<artifactId>ProtocolLib</artifactId>
|
||||||
<version>1.6.1-SNAPSHOT</version>
|
<version>1.7.0</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import java.util.Set;
|
|||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
import com.comphenix.protocol.async.AsyncListenerHandler;
|
import com.comphenix.protocol.async.AsyncListenerHandler;
|
||||||
|
import com.comphenix.protocol.async.AsyncMarker;
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
import com.comphenix.protocol.events.PacketListener;
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
@ -35,6 +36,8 @@ public interface AsynchronousManager {
|
|||||||
/**
|
/**
|
||||||
* Registers an asynchronous packet handler.
|
* Registers an asynchronous packet handler.
|
||||||
* <p>
|
* <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.
|
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||||
* @param listener - the packet listener that will recieve these asynchronous events.
|
* @param listener - the packet listener that will recieve these asynchronous events.
|
||||||
* @return An asynchrouns handler.
|
* @return An asynchrouns handler.
|
||||||
|
@ -19,7 +19,8 @@ abstract class CommandBase implements CommandExecutor {
|
|||||||
private String permission;
|
private String permission;
|
||||||
private String name;
|
private String name;
|
||||||
private int minimumArgumentCount;
|
private int minimumArgumentCount;
|
||||||
private ErrorReporter reporter;
|
|
||||||
|
protected ErrorReporter reporter;
|
||||||
|
|
||||||
public CommandBase(ErrorReporter reporter, String permission, String name) {
|
public CommandBase(ErrorReporter reporter, String permission, String name) {
|
||||||
this(reporter, permission, name, 0);
|
this(reporter, permission, name, 0);
|
||||||
|
@ -65,7 +65,6 @@ class CommandPacket extends CommandBase {
|
|||||||
|
|
||||||
private Plugin plugin;
|
private Plugin plugin;
|
||||||
private Logger logger;
|
private Logger logger;
|
||||||
private ErrorReporter reporter;
|
|
||||||
private ProtocolManager manager;
|
private ProtocolManager manager;
|
||||||
|
|
||||||
private ChatExtensions chatter;
|
private ChatExtensions chatter;
|
||||||
@ -460,9 +459,9 @@ class CommandPacket extends CommandBase {
|
|||||||
|
|
||||||
// The interval tree will automatically remove the listeners for us
|
// The interval tree will automatically remove the listeners for us
|
||||||
if (side.isForClient())
|
if (side.isForClient())
|
||||||
result.addAll(clientListeners.remove(idStart, idStop));
|
result.addAll(clientListeners.remove(idStart, idStop, true));
|
||||||
if (side.isForServer())
|
if (side.isForServer())
|
||||||
result.addAll(serverListeners.remove(idStart, idStop));
|
result.addAll(serverListeners.remove(idStart, idStop, true));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@ class ProtocolConfig {
|
|||||||
|
|
||||||
private static final String METRICS_ENABLED = "metrics";
|
private static final String METRICS_ENABLED = "metrics";
|
||||||
|
|
||||||
|
private static final String BACKGROUND_COMPILER_ENABLED = "background compiler";
|
||||||
|
|
||||||
private static final String UPDATER_NOTIFY = "notify";
|
private static final String UPDATER_NOTIFY = "notify";
|
||||||
private static final String UPDATER_DOWNLAD = "download";
|
private static final String UPDATER_DOWNLAD = "download";
|
||||||
private static final String UPDATER_DELAY = "delay";
|
private static final String UPDATER_DELAY = "delay";
|
||||||
@ -166,6 +168,25 @@ class ProtocolConfig {
|
|||||||
global.set(METRICS_ENABLED, enabled);
|
global.set(METRICS_ENABLED, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve whether or not the background compiler for structure modifiers is enabled or not.
|
||||||
|
* @return TRUE if it is enabled, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isBackgroundCompilerEnabled() {
|
||||||
|
return global.getBoolean(BACKGROUND_COMPILER_ENABLED, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether or not the background compiler for structure modifiers is enabled or not.
|
||||||
|
* <p>
|
||||||
|
* This setting will take effect next time ProtocolLib is started.
|
||||||
|
*
|
||||||
|
* @param enabled - TRUE if is enabled/running, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public void setBackgroundCompilerEnabled(boolean enabled) {
|
||||||
|
global.set(BACKGROUND_COMPILER_ENABLED, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
||||||
* @param lastTimeSeconds - new last update time.
|
* @param lastTimeSeconds - new last update time.
|
||||||
|
@ -79,6 +79,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
|
|
||||||
// Logger
|
// Logger
|
||||||
private Logger logger;
|
private Logger logger;
|
||||||
|
private Handler redirectHandler;
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
private CommandProtocol commandProtocol;
|
private CommandProtocol commandProtocol;
|
||||||
@ -90,39 +91,43 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
logger = getLoggerSafely();
|
logger = getLoggerSafely();
|
||||||
|
|
||||||
// Add global parameters
|
// Add global parameters
|
||||||
DetailedErrorReporter reporter = new DetailedErrorReporter();
|
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
|
||||||
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
||||||
|
reporter = detailedReporter;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
config = new ProtocolConfig(this);
|
config = new ProtocolConfig(this);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
reporter.reportWarning(this, "Cannot load configuration", e);
|
detailedReporter.reportWarning(this, "Cannot load configuration", e);
|
||||||
|
|
||||||
// Load it again
|
// Load it again
|
||||||
deleteConfig();
|
if (deleteConfig()) {
|
||||||
config = new ProtocolConfig(this);
|
config = new ProtocolConfig(this);
|
||||||
|
} else {
|
||||||
|
reporter.reportWarning(this, "Cannot delete old ProtocolLib configuration.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
unhookTask = new DelayedSingleTask(this);
|
unhookTask = new DelayedSingleTask(this);
|
||||||
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, reporter);
|
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, detailedReporter);
|
||||||
reporter.addGlobalParameter("manager", protocolManager);
|
detailedReporter.addGlobalParameter("manager", protocolManager);
|
||||||
|
|
||||||
// Initialize command handlers
|
// Initialize command handlers
|
||||||
commandProtocol = new CommandProtocol(reporter, this, updater, config);
|
commandProtocol = new CommandProtocol(detailedReporter, this, updater, config);
|
||||||
commandPacket = new CommandPacket(reporter, this, logger, protocolManager);
|
commandPacket = new CommandPacket(detailedReporter, this, logger, protocolManager);
|
||||||
|
|
||||||
// Send logging information to player listeners too
|
// Send logging information to player listeners too
|
||||||
broadcastUsers(PERMISSION_INFO);
|
broadcastUsers(PERMISSION_INFO);
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
reporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager);
|
detailedReporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager);
|
||||||
disablePlugin();
|
disablePlugin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteConfig() {
|
private boolean deleteConfig() {
|
||||||
config.getFile().delete();
|
return config.getFile().delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -136,8 +141,12 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void broadcastUsers(final String permission) {
|
private void broadcastUsers(final String permission) {
|
||||||
// Broadcast information to every user too
|
// Guard against multiple calls
|
||||||
logger.addHandler(new Handler() {
|
if (redirectHandler != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Broadcast information to every user too
|
||||||
|
redirectHandler = new Handler() {
|
||||||
@Override
|
@Override
|
||||||
public void publish(LogRecord record) {
|
public void publish(LogRecord record) {
|
||||||
commandPacket.broadcastMessageSilently(record.getMessage(), permission);
|
commandPacket.broadcastMessageSilently(record.getMessage(), permission);
|
||||||
@ -152,7 +161,9 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
public void close() throws SecurityException {
|
public void close() throws SecurityException {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
logger.addHandler(redirectHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -166,9 +177,13 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Initialize background compiler
|
// Initialize background compiler
|
||||||
if (backgroundCompiler == null) {
|
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
|
||||||
backgroundCompiler = new BackgroundCompiler(getClassLoader());
|
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
|
||||||
BackgroundCompiler.setInstance(backgroundCompiler);
|
BackgroundCompiler.setInstance(backgroundCompiler);
|
||||||
|
|
||||||
|
logger.info("Started structure compiler thread.");
|
||||||
|
} else {
|
||||||
|
logger.info("Structure compiler thread has been disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up command handlers
|
// Set up command handlers
|
||||||
@ -288,6 +303,11 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
asyncPacketTask = -1;
|
asyncPacketTask = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// And redirect handler too
|
||||||
|
if (redirectHandler != null) {
|
||||||
|
logger.removeHandler(redirectHandler);
|
||||||
|
}
|
||||||
|
|
||||||
unhookTask.close();
|
unhookTask.close();
|
||||||
protocolManager.close();
|
protocolManager.close();
|
||||||
protocolManager = null;
|
protocolManager = null;
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol;
|
package com.comphenix.protocol;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ import org.bukkit.entity.Entity;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.async.AsyncMarker;
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
import com.comphenix.protocol.events.PacketListener;
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
import com.comphenix.protocol.injector.PacketConstructor;
|
import com.comphenix.protocol.injector.PacketConstructor;
|
||||||
@ -37,6 +39,37 @@ import com.google.common.collect.ImmutableSet;
|
|||||||
*/
|
*/
|
||||||
public interface ProtocolManager extends PacketStream {
|
public interface ProtocolManager extends PacketStream {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a packet to the given player.
|
||||||
|
* <p>
|
||||||
|
* Re-sending a previously cancelled packet is discuraged. Use {@link AsyncMarker#incrementProcessingDelay()}
|
||||||
|
* to delay a packet until a certain condition has been met.
|
||||||
|
*
|
||||||
|
* @param reciever - the reciever.
|
||||||
|
* @param packet - packet to send.
|
||||||
|
* @param filters - whether or not to invoke any packet filters.
|
||||||
|
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
|
||||||
|
throws InvocationTargetException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulate recieving a certain packet from a given player.
|
||||||
|
* <p>
|
||||||
|
* Receiving a previously cancelled packet is discuraged. Use {@link AsyncMarker#incrementProcessingDelay()}
|
||||||
|
* to delay a packet until a certain condition has been met.
|
||||||
|
*
|
||||||
|
* @param sender - the sender.
|
||||||
|
* @param packet - the packet that was sent.
|
||||||
|
* @param filters - whether or not to invoke any packet filters.
|
||||||
|
* @throws InvocationTargetException If the reflection machinery failed.
|
||||||
|
* @throws IllegalAccessException If the underlying method caused an error.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
|
||||||
|
throws IllegalAccessException, InvocationTargetException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a list of every registered packet listener.
|
* Retrieves a list of every registered packet listener.
|
||||||
* @return Every registered packet listener.
|
* @return Every registered packet listener.
|
||||||
|
@ -23,6 +23,7 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.scheduler.BukkitScheduler;
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
|
|
||||||
@ -42,7 +43,9 @@ import com.google.common.collect.Sets;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a filter manager for asynchronous packets.
|
* 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
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public class AsyncFilterManager implements AsynchronousManager {
|
public class AsyncFilterManager implements AsynchronousManager {
|
||||||
@ -52,58 +55,44 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
private Set<PacketListener> timeoutListeners;
|
private Set<PacketListener> timeoutListeners;
|
||||||
|
|
||||||
private PacketProcessingQueue serverProcessingQueue;
|
private PacketProcessingQueue serverProcessingQueue;
|
||||||
private PacketSendingQueue serverQueue;
|
|
||||||
|
|
||||||
|
|
||||||
private PacketProcessingQueue clientProcessingQueue;
|
private PacketProcessingQueue clientProcessingQueue;
|
||||||
private PacketSendingQueue clientQueue;
|
|
||||||
|
|
||||||
private ErrorReporter reporter;
|
// Sending queues
|
||||||
|
private final PlayerSendingHandler playerSendingHandler;
|
||||||
|
|
||||||
|
// Report exceptions
|
||||||
|
private final ErrorReporter reporter;
|
||||||
|
|
||||||
// The likely main thread
|
// The likely main thread
|
||||||
private Thread mainThread;
|
private final Thread mainThread;
|
||||||
|
|
||||||
// Default scheduler
|
// Default scheduler
|
||||||
private BukkitScheduler scheduler;
|
private final BukkitScheduler scheduler;
|
||||||
|
|
||||||
// Our protocol manager
|
// Our protocol manager
|
||||||
private ProtocolManager manager;
|
private final ProtocolManager manager;
|
||||||
|
|
||||||
// Current packet index
|
// Current packet index
|
||||||
private AtomicInteger currentSendingIndex = new AtomicInteger();
|
private final AtomicInteger currentSendingIndex = new AtomicInteger();
|
||||||
|
|
||||||
// Whether or not we're currently cleaning up
|
|
||||||
private volatile boolean cleaningUp;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @param manager - protocol manager.
|
||||||
|
*/
|
||||||
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) {
|
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) {
|
||||||
|
|
||||||
// Initialize timeout listeners
|
// Initialize timeout listeners
|
||||||
serverTimeoutListeners = new SortedPacketListenerList();
|
this.serverTimeoutListeners = new SortedPacketListenerList();
|
||||||
clientTimeoutListeners = new SortedPacketListenerList();
|
this.clientTimeoutListeners = new SortedPacketListenerList();
|
||||||
timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
|
this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
|
||||||
|
|
||||||
// Server packets are synchronized already
|
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
|
||||||
this.serverQueue = new PacketSendingQueue(false) {
|
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||||
@Override
|
this.clientProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||||
protected void onPacketTimeout(PacketEvent event) {
|
this.playerSendingHandler.initializeScheduler();
|
||||||
if (!cleaningUp) {
|
|
||||||
serverTimeoutListeners.invokePacketSending(AsyncFilterManager.this.reporter, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Client packets must be synchronized
|
|
||||||
this.clientQueue = new PacketSendingQueue(true) {
|
|
||||||
@Override
|
|
||||||
protected void onPacketTimeout(PacketEvent event) {
|
|
||||||
if (!cleaningUp) {
|
|
||||||
clientTimeoutListeners.invokePacketSending(AsyncFilterManager.this.reporter, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.serverProcessingQueue = new PacketProcessingQueue(serverQueue);
|
|
||||||
this.clientProcessingQueue = new PacketProcessingQueue(clientQueue);
|
|
||||||
|
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@ -141,6 +130,8 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
/**
|
/**
|
||||||
* Registers an asynchronous packet handler.
|
* Registers an asynchronous packet handler.
|
||||||
* <p>
|
* <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.
|
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||||
* <p>
|
* <p>
|
||||||
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
|
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
|
||||||
@ -219,15 +210,12 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
List<Integer> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
|
List<Integer> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
|
||||||
|
|
||||||
// We're already taking care of this, so don't do anything
|
// We're already taking care of this, so don't do anything
|
||||||
if (!cleaningUp)
|
playerSendingHandler.sendServerPackets(removed, synchronusOK);
|
||||||
serverQueue.signalPacketUpdate(removed, synchronusOK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
|
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
|
||||||
List<Integer> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
|
List<Integer> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
|
||||||
|
playerSendingHandler.sendClientPackets(removed, synchronusOK);
|
||||||
if (!cleaningUp)
|
|
||||||
clientQueue.signalPacketUpdate(removed, synchronusOK);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,12 +275,11 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to create a default asynchronous task.
|
* Retrieve the current task scheduler.
|
||||||
* @param plugin - the calling plugin.
|
* @return Current task scheduler.
|
||||||
* @param runnable - the runnable.
|
|
||||||
*/
|
*/
|
||||||
public void scheduleAsyncTask(Plugin plugin, Runnable runnable) {
|
public BukkitScheduler getScheduler() {
|
||||||
scheduler.scheduleAsyncDelayedTask(plugin, runnable);
|
return scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -337,11 +324,10 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cleanupAll() {
|
public void cleanupAll() {
|
||||||
cleaningUp = true;
|
|
||||||
serverProcessingQueue.cleanupAll();
|
serverProcessingQueue.cleanupAll();
|
||||||
serverQueue.cleanupAll();
|
playerSendingHandler.cleanupAll();
|
||||||
|
|
||||||
timeoutListeners.clear();
|
timeoutListeners.clear();
|
||||||
|
|
||||||
serverTimeoutListeners = null;
|
serverTimeoutListeners = null;
|
||||||
clientTimeoutListeners = null;
|
clientTimeoutListeners = null;
|
||||||
}
|
}
|
||||||
@ -367,7 +353,11 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
|
|
||||||
// Only send if the packet is ready
|
// Only send if the packet is ready
|
||||||
if (marker.decrementProcessingDelay() == 0) {
|
if (marker.decrementProcessingDelay() == 0) {
|
||||||
getSendingQueue(packet).signalPacketUpdate(packet, onMainThread);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,8 +366,27 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
* @param packet - the packet.
|
* @param packet - the packet.
|
||||||
* @return The server or client sending queue the packet belongs to.
|
* @return The server or client sending queue the packet belongs to.
|
||||||
*/
|
*/
|
||||||
private PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||||
return packet.isServerPacket() ? serverQueue : clientQueue;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -388,24 +397,23 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
getProcessingQueue(packet).signalProcessingDone();
|
getProcessingQueue(packet).signalProcessingDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the processing queue this packet belongs to.
|
|
||||||
* @param packet - the packet.
|
|
||||||
* @return The server or client sending processing the packet belongs to.
|
|
||||||
*/
|
|
||||||
private PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
|
|
||||||
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send any due packets, or clean up packets that have expired.
|
* Send any due packets, or clean up packets that have expired.
|
||||||
*/
|
*/
|
||||||
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
|
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
|
||||||
// The server queue is unlikely to need checking that often
|
// The server queue is unlikely to need checking that often
|
||||||
if (tickCounter % 10 == 0) {
|
if (tickCounter % 10 == 0) {
|
||||||
serverQueue.trySendPackets(onMainThread);
|
playerSendingHandler.trySendServerPackets(onMainThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientQueue.trySendPackets(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ package com.comphenix.protocol.async;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@ -34,7 +35,8 @@ import com.google.common.base.Joiner;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a handler for an asynchronous event.
|
* Represents a handler for an asynchronous event.
|
||||||
*
|
* <p>
|
||||||
|
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until a certain condition has been met.
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public class AsyncListenerHandler {
|
public class AsyncListenerHandler {
|
||||||
@ -75,10 +77,19 @@ public class AsyncListenerHandler {
|
|||||||
private final Set<Integer> stoppedTasks = new HashSet<Integer>();
|
private final Set<Integer> stoppedTasks = new HashSet<Integer>();
|
||||||
private final Object stopLock = new Object();
|
private final Object stopLock = new Object();
|
||||||
|
|
||||||
|
// Processing task on the main thread
|
||||||
|
private int syncTask = -1;
|
||||||
|
|
||||||
// Minecraft main thread
|
// Minecraft main thread
|
||||||
private Thread mainThread;
|
private Thread mainThread;
|
||||||
|
|
||||||
public AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
|
/**
|
||||||
|
* Construct a manager for an asynchronous packet handler.
|
||||||
|
* @param mainThread - the main game thread.
|
||||||
|
* @param filterManager - the parent filter manager.
|
||||||
|
* @param listener - the current packet listener.
|
||||||
|
*/
|
||||||
|
AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
|
||||||
if (filterManager == null)
|
if (filterManager == null)
|
||||||
throw new IllegalArgumentException("filterManager cannot be NULL");
|
throw new IllegalArgumentException("filterManager cannot be NULL");
|
||||||
if (listener == null)
|
if (listener == null)
|
||||||
@ -89,10 +100,18 @@ public class AsyncListenerHandler {
|
|||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether or not this asynchronous handler has been cancelled.
|
||||||
|
* @return TRUE if it has been cancelled/stopped, FALSE otherwise.
|
||||||
|
*/
|
||||||
public boolean isCancelled() {
|
public boolean isCancelled() {
|
||||||
return cancelled;
|
return cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current asynchronous packet listener.
|
||||||
|
* @return Current packet listener.
|
||||||
|
*/
|
||||||
public PacketListener getAsyncListener() {
|
public PacketListener getAsyncListener() {
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
@ -223,7 +242,7 @@ public class AsyncListenerHandler {
|
|||||||
|
|
||||||
final AsyncRunnable listenerLoop = getListenerLoop();
|
final AsyncRunnable listenerLoop = getListenerLoop();
|
||||||
|
|
||||||
filterManager.scheduleAsyncTask(listener.getPlugin(), new Runnable() {
|
filterManager.getScheduler().scheduleAsyncDelayedTask(listener.getPlugin(), new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Thread thread = Thread.currentThread();
|
Thread thread = Thread.currentThread();
|
||||||
@ -271,7 +290,7 @@ public class AsyncListenerHandler {
|
|||||||
final AsyncRunnable listenerLoop = getListenerLoop();
|
final AsyncRunnable listenerLoop = getListenerLoop();
|
||||||
final Function<AsyncRunnable, Void> delegateCopy = executor;
|
final Function<AsyncRunnable, Void> delegateCopy = executor;
|
||||||
|
|
||||||
filterManager.scheduleAsyncTask(listener.getPlugin(), new Runnable() {
|
filterManager.getScheduler().scheduleAsyncDelayedTask(listener.getPlugin(), new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
delegateCopy.apply(listenerLoop);
|
delegateCopy.apply(listenerLoop);
|
||||||
@ -308,6 +327,104 @@ public class AsyncListenerHandler {
|
|||||||
return Joiner.on(", ").join(whitelist.getWhitelist());
|
return Joiner.on(", ").join(whitelist.getWhitelist());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start processing packets on the main thread.
|
||||||
|
* <p>
|
||||||
|
* This is useful if you need to synchronize with the main thread in your packet listener, but
|
||||||
|
* you're not performing any expensive processing.
|
||||||
|
* <p>
|
||||||
|
* <b>Note</b>: Use a asynchronous worker if the packet listener may use more than 0.5 ms
|
||||||
|
* of processing time on a single packet. Do as much as possible on the worker thread, and schedule synchronous tasks
|
||||||
|
* to use the Bukkit API instead.
|
||||||
|
* @return TRUE if the synchronized processing was successfully started, FALSE if it's already running.
|
||||||
|
* @throws IllegalStateException If we couldn't start the underlying task.
|
||||||
|
*/
|
||||||
|
public synchronized boolean syncStart() {
|
||||||
|
return syncStart(500, TimeUnit.MICROSECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start processing packets on the main thread.
|
||||||
|
* <p>
|
||||||
|
* This is useful if you need to synchronize with the main thread in your packet listener, but
|
||||||
|
* you're not performing any expensive processing.
|
||||||
|
* <p>
|
||||||
|
* The processing time parameter gives the upper bound for the amount of time spent processing pending packets.
|
||||||
|
* It should be set to a fairly low number, such as 0.5 ms or 1% of a game tick - to reduce the impact
|
||||||
|
* on the main thread. Never go beyond 50 milliseconds.
|
||||||
|
* <p>
|
||||||
|
* <b>Note</b>: Use a asynchronous worker if the packet listener may exceed the ideal processing time
|
||||||
|
* on a single packet. Do as much as possible on the worker thread, and schedule synchronous tasks
|
||||||
|
* to use the Bukkit API instead.
|
||||||
|
*
|
||||||
|
* @param time - the amount of processing time alloted per game tick (20 ticks per second).
|
||||||
|
* @param unit - the unit of the processingTime argument.
|
||||||
|
* @return TRUE if the synchronized processing was successfully started, FALSE if it's already running.
|
||||||
|
* @throws IllegalStateException If we couldn't start the underlying task.
|
||||||
|
*/
|
||||||
|
public synchronized boolean syncStart(final long time, final TimeUnit unit) {
|
||||||
|
if (time <= 0)
|
||||||
|
throw new IllegalArgumentException("Time must be greater than zero.");
|
||||||
|
if (unit == null)
|
||||||
|
throw new IllegalArgumentException("TimeUnit cannot be NULL.");
|
||||||
|
|
||||||
|
final long tickDelay = 1;
|
||||||
|
final int workerID = nextID.incrementAndGet();
|
||||||
|
|
||||||
|
if (syncTask < 0) {
|
||||||
|
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
|
||||||
|
|
||||||
|
while (!cancelled) {
|
||||||
|
PacketEvent packet = queuedPackets.poll();
|
||||||
|
|
||||||
|
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
|
||||||
|
// Sorry, asynchronous threads!
|
||||||
|
queuedPackets.add(packet);
|
||||||
|
|
||||||
|
// Try again next tick
|
||||||
|
break;
|
||||||
|
} else if (packet != null && packet.getAsyncMarker() != null) {
|
||||||
|
processPacket(workerID, packet, "onSyncPacket()");
|
||||||
|
} else {
|
||||||
|
// No more packets left - wait a tick
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check time here, ensuring that we at least process one packet
|
||||||
|
if (System.nanoTime() < stopTime)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, tickDelay, tickDelay);
|
||||||
|
|
||||||
|
// This is very bad - force the caller to handle it
|
||||||
|
if (syncTask < 0)
|
||||||
|
throw new IllegalStateException("Cannot start synchronous task.");
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop processing packets on the main thread.
|
||||||
|
* @return TRUE if we stopped any processing tasks, FALSE if it has already been stopped.
|
||||||
|
*/
|
||||||
|
public synchronized boolean syncStop() {
|
||||||
|
if (syncTask > 0) {
|
||||||
|
filterManager.getScheduler().cancelTask(syncTask);
|
||||||
|
|
||||||
|
syncTask = -1;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start multiple worker threads for this listener.
|
* Start multiple worker threads for this listener.
|
||||||
* @param count - number of worker threads to start.
|
* @param count - number of worker threads to start.
|
||||||
@ -386,9 +503,13 @@ public class AsyncListenerHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO NOT call this method from the main thread
|
/**
|
||||||
|
* The main processing loop of asynchronous threads.
|
||||||
|
* <p>
|
||||||
|
* Note: DO NOT call this method from the main thread
|
||||||
|
* @param workerID - the current worker ID.
|
||||||
|
*/
|
||||||
private void listenerLoop(int workerID) {
|
private void listenerLoop(int workerID) {
|
||||||
|
|
||||||
// Danger, danger!
|
// Danger, danger!
|
||||||
if (Thread.currentThread().getId() == mainThread.getId())
|
if (Thread.currentThread().getId() == mainThread.getId())
|
||||||
throw new IllegalStateException("Do not call this method from the main thread.");
|
throw new IllegalStateException("Do not call this method from the main thread.");
|
||||||
@ -403,16 +524,11 @@ public class AsyncListenerHandler {
|
|||||||
// Proceed
|
// Proceed
|
||||||
started.incrementAndGet();
|
started.incrementAndGet();
|
||||||
|
|
||||||
mainLoop:
|
|
||||||
while (!cancelled) {
|
while (!cancelled) {
|
||||||
PacketEvent packet = queuedPackets.take();
|
PacketEvent packet = queuedPackets.take();
|
||||||
AsyncMarker marker = packet.getAsyncMarker();
|
|
||||||
|
|
||||||
// Handle cancel requests
|
// Handle cancel requests
|
||||||
if (packet == null || marker == null || packet == INTERUPT_PACKET) {
|
if (packet == WAKEUP_PACKET) {
|
||||||
return;
|
|
||||||
|
|
||||||
} else if (packet == WAKEUP_PACKET) {
|
|
||||||
// This is a bit slow, but it should be safe
|
// This is a bit slow, but it should be safe
|
||||||
synchronized (stopLock) {
|
synchronized (stopLock) {
|
||||||
// Are we the one who is supposed to stop?
|
// Are we the one who is supposed to stop?
|
||||||
@ -421,42 +537,13 @@ public class AsyncListenerHandler {
|
|||||||
if (waitForStops())
|
if (waitForStops())
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (packet == INTERUPT_PACKET) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here's the core of the asynchronous processing
|
if (packet != null && packet.getAsyncMarker() != null) {
|
||||||
try {
|
processPacket(workerID, packet, "onAsyncPacket()");
|
||||||
marker.setListenerHandler(this);
|
|
||||||
marker.setWorkerID(workerID);
|
|
||||||
|
|
||||||
synchronized (marker.getProcessingLock()) {
|
|
||||||
if (packet.isServerPacket())
|
|
||||||
listener.onPacketSending(packet);
|
|
||||||
else
|
|
||||||
listener.onPacketReceiving(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Throwable e) {
|
|
||||||
// Minecraft doesn't want your Exception.
|
|
||||||
filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), "onAsyncPacket()", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, get the next non-cancelled listener
|
|
||||||
if (!marker.hasExpired()) {
|
|
||||||
for (; marker.getListenerTraversal().hasNext(); ) {
|
|
||||||
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
|
|
||||||
|
|
||||||
if (!handler.isCancelled()) {
|
|
||||||
handler.enqueuePacket(packet);
|
|
||||||
continue mainLoop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are no more listeners - queue the packet for transmission
|
|
||||||
filterManager.signalFreeProcessingSlot(packet);
|
|
||||||
|
|
||||||
// Note that listeners can opt to delay the packet transmission
|
|
||||||
filterManager.signalPacketTransmission(packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@ -464,16 +551,66 @@ public class AsyncListenerHandler {
|
|||||||
} finally {
|
} finally {
|
||||||
// Clean up
|
// Clean up
|
||||||
started.decrementAndGet();
|
started.decrementAndGet();
|
||||||
close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a packet is scheduled for processing.
|
||||||
|
* @param workerID - the current worker ID.
|
||||||
|
* @param packet - the current packet.
|
||||||
|
* @param methodName - name of the method.
|
||||||
|
*/
|
||||||
|
private void processPacket(int workerID, PacketEvent packet, String methodName) {
|
||||||
|
AsyncMarker marker = packet.getAsyncMarker();
|
||||||
|
|
||||||
|
// Here's the core of the asynchronous processing
|
||||||
|
try {
|
||||||
|
synchronized (marker.getProcessingLock()) {
|
||||||
|
marker.setListenerHandler(this);
|
||||||
|
marker.setWorkerID(workerID);
|
||||||
|
|
||||||
|
if (packet.isServerPacket())
|
||||||
|
listener.onPacketSending(packet);
|
||||||
|
else
|
||||||
|
listener.onPacketReceiving(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// Minecraft doesn't want your Exception.
|
||||||
|
filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), methodName, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, get the next non-cancelled listener
|
||||||
|
if (!marker.hasExpired()) {
|
||||||
|
for (; marker.getListenerTraversal().hasNext(); ) {
|
||||||
|
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
|
||||||
|
|
||||||
|
if (!handler.isCancelled()) {
|
||||||
|
handler.enqueuePacket(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are no more listeners - queue the packet for transmission
|
||||||
|
filterManager.signalFreeProcessingSlot(packet);
|
||||||
|
|
||||||
|
// Note that listeners can opt to delay the packet transmission
|
||||||
|
filterManager.signalPacketTransmission(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all worker threads and the handler itself.
|
||||||
|
*/
|
||||||
private synchronized void close() {
|
private synchronized void close() {
|
||||||
// Remove the listener itself
|
// Remove the listener itself
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
filterManager.unregisterAsyncHandlerInternal(this);
|
filterManager.unregisterAsyncHandlerInternal(this);
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
|
|
||||||
|
// Close processing tasks
|
||||||
|
syncStop();
|
||||||
|
|
||||||
// Tell every uncancelled thread to end
|
// Tell every uncancelled thread to end
|
||||||
stopThreads();
|
stopThreads();
|
||||||
}
|
}
|
||||||
|
@ -99,8 +99,8 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
|||||||
private transient int workerID;
|
private transient int workerID;
|
||||||
|
|
||||||
// Determine if Minecraft processes this packet asynchronously
|
// Determine if Minecraft processes this packet asynchronously
|
||||||
private static Method isMinecraftAsync;
|
private volatile static Method isMinecraftAsync;
|
||||||
private static boolean alwaysSync;
|
private volatile static boolean alwaysSync;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a container for asyncronous packets.
|
* Create a container for asyncronous packets.
|
||||||
@ -206,7 +206,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increment the number of times this packet must be signalled as done before its transmitted.
|
* Increment the number of times the current packet must be signalled as done before its transmitted.
|
||||||
* <p>
|
* <p>
|
||||||
* This is useful if an asynchronous listener is waiting for further information before the
|
* This is useful if an asynchronous listener is waiting for further information before the
|
||||||
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
|
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
|
||||||
@ -215,9 +215,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
|||||||
* <p>
|
* <p>
|
||||||
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
|
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
|
||||||
* using the {@link #getProcessingLock()} method.
|
* using the {@link #getProcessingLock()} method.
|
||||||
* <p>
|
*
|
||||||
* 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.
|
* @return The new processing delay.
|
||||||
*/
|
*/
|
||||||
public int incrementProcessingDelay() {
|
public int incrementProcessingDelay() {
|
||||||
@ -447,4 +445,20 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
|||||||
else
|
else
|
||||||
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
|
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
// Standard equals
|
||||||
|
if (other == this)
|
||||||
|
return true;
|
||||||
|
if (other instanceof AsyncMarker)
|
||||||
|
return getNewSendingIndex() == ((AsyncMarker) other).getNewSendingIndex();
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Longs.hashCode(getNewSendingIndex());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ package com.comphenix.protocol.async;
|
|||||||
import com.comphenix.protocol.events.PacketEvent;
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ComparisonChain;
|
import com.google.common.collect.ComparisonChain;
|
||||||
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a comparable to a packet event.
|
* Provides a comparable to a packet event.
|
||||||
@ -56,4 +57,20 @@ class PacketEventHolder implements Comparable<PacketEventHolder> {
|
|||||||
compare(sendingIndex, other.sendingIndex).
|
compare(sendingIndex, other.sendingIndex).
|
||||||
result();
|
result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
// Standard equals
|
||||||
|
if (other == this)
|
||||||
|
return true;
|
||||||
|
if (other instanceof PacketEventHolder)
|
||||||
|
return sendingIndex == ((PacketEventHolder) other).sendingIndex;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Longs.hashCode(sendingIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,13 +58,13 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
|
|||||||
private Queue<PacketEventHolder> processingQueue;
|
private Queue<PacketEventHolder> processingQueue;
|
||||||
|
|
||||||
// Packets for sending
|
// Packets for sending
|
||||||
private PacketSendingQueue sendingQueue;
|
private PlayerSendingHandler sendingHandler;
|
||||||
|
|
||||||
public PacketProcessingQueue(PacketSendingQueue sendingQueue) {
|
public PacketProcessingQueue(PlayerSendingHandler sendingHandler) {
|
||||||
this(sendingQueue, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
|
this(sendingHandler, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PacketProcessingQueue(PacketSendingQueue sendingQueue, int initialSize, int maximumSize, int maximumConcurrency) {
|
public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
|
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
|
||||||
@ -74,7 +74,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
|
|||||||
|
|
||||||
this.maximumConcurrency = maximumConcurrency;
|
this.maximumConcurrency = maximumConcurrency;
|
||||||
this.concurrentProcessing = new Semaphore(maximumConcurrency);
|
this.concurrentProcessing = new Semaphore(maximumConcurrency);
|
||||||
this.sendingQueue = sendingQueue;
|
this.sendingHandler = sendingHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,8 +131,13 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The packet has no further listeners. Just send it.
|
// The packet has no further listeners. Just send it.
|
||||||
if (marker.decrementProcessingDelay() == 0)
|
if (marker.decrementProcessingDelay() == 0) {
|
||||||
sendingQueue.signalPacketUpdate(packet, onMainThread);
|
PacketSendingQueue sendingQueue = sendingHandler.getSendingQueue(packet, false);
|
||||||
|
|
||||||
|
// In case the player has logged out
|
||||||
|
if (sendingQueue != null)
|
||||||
|
sendingQueue.signalPacketUpdate(packet, onMainThread);
|
||||||
|
}
|
||||||
signalProcessingDone();
|
signalProcessingDone();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -168,5 +173,8 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
|
|||||||
|
|
||||||
// Remove the rest, just in case
|
// Remove the rest, just in case
|
||||||
clearListeners();
|
clearListeners();
|
||||||
|
|
||||||
|
// Remove every packet in the queue
|
||||||
|
processingQueue.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import java.io.IOException;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -35,12 +36,28 @@ import com.comphenix.protocol.reflect.FieldAccessException;
|
|||||||
*/
|
*/
|
||||||
abstract class PacketSendingQueue {
|
abstract class PacketSendingQueue {
|
||||||
|
|
||||||
public static final int INITIAL_CAPACITY = 64;
|
public static final int INITIAL_CAPACITY = 10;
|
||||||
|
|
||||||
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
|
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
|
||||||
|
|
||||||
// Whether or not packet transmission can only occur on the main thread
|
// Asynchronous packet sending
|
||||||
private final boolean synchronizeMain;
|
private Executor asynchronousSender;
|
||||||
|
|
||||||
|
// Whether or not packet transmission must occur on a specific thread
|
||||||
|
private final boolean notThreadSafe;
|
||||||
|
|
||||||
|
// Whether or not we've run the cleanup procedure
|
||||||
|
private boolean cleanedUp = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a packet sending queue.
|
||||||
|
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
|
||||||
|
*/
|
||||||
|
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
|
||||||
|
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
|
||||||
|
this.notThreadSafe = notThreadSafe;
|
||||||
|
this.asynchronousSender = asynchronousSender;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of packet events in the queue.
|
* Number of packet events in the queue.
|
||||||
@ -50,15 +67,6 @@ abstract class PacketSendingQueue {
|
|||||||
return sendingQueue.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<PacketEventHolder>(INITIAL_CAPACITY);
|
|
||||||
this.synchronizeMain = synchronizeMain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueue a packet for sending.
|
* Enqueue a packet for sending.
|
||||||
* @param packet - packet to queue.
|
* @param packet - packet to queue.
|
||||||
@ -119,56 +127,107 @@ abstract class PacketSendingQueue {
|
|||||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||||
*/
|
*/
|
||||||
public void trySendPackets(boolean onMainThread) {
|
public void trySendPackets(boolean onMainThread) {
|
||||||
|
// Whether or not to continue sending packets
|
||||||
|
boolean sending = true;
|
||||||
|
|
||||||
// Transmit as many packets as we can
|
// Transmit as many packets as we can
|
||||||
while (true) {
|
while (sending) {
|
||||||
PacketEventHolder holder = sendingQueue.peek();
|
PacketEventHolder holder = sendingQueue.poll();
|
||||||
|
|
||||||
if (holder != null) {
|
if (holder != null) {
|
||||||
PacketEvent current = holder.getEvent();
|
sending = processPacketHolder(onMainThread, holder);
|
||||||
AsyncMarker marker = current.getAsyncMarker();
|
|
||||||
boolean hasExpired = marker.hasExpired();
|
|
||||||
|
|
||||||
// Abort if we're not on the main thread
|
if (!sending) {
|
||||||
if (synchronizeMain) {
|
// Add it back again
|
||||||
|
sendingQueue.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// No more packets to send
|
||||||
|
sending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a packet might be ready for transmission.
|
||||||
|
* @param onMainThread - TRUE if we're on the main thread, FALSE otherwise.
|
||||||
|
* @param holder - packet container.
|
||||||
|
* @return TRUE to continue sending packets, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) {
|
||||||
|
PacketEvent current = holder.getEvent();
|
||||||
|
AsyncMarker marker = current.getAsyncMarker();
|
||||||
|
boolean hasExpired = marker.hasExpired();
|
||||||
|
|
||||||
|
// Guard in cause the queue is closed
|
||||||
|
if (cleanedUp) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End condition?
|
||||||
|
if (marker.isProcessed() || hasExpired) {
|
||||||
|
if (hasExpired) {
|
||||||
|
// Notify timeout listeners
|
||||||
|
onPacketTimeout(current);
|
||||||
|
|
||||||
|
// Recompute
|
||||||
|
marker = current.getAsyncMarker();
|
||||||
|
hasExpired = marker.hasExpired();
|
||||||
|
|
||||||
|
// Could happen due to the timeout listeners
|
||||||
|
if (!marker.isProcessed() && !hasExpired) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it okay to send the packet?
|
||||||
|
if (!current.isCancelled() && !hasExpired) {
|
||||||
|
// Make sure we're on the main thread
|
||||||
|
if (notThreadSafe) {
|
||||||
try {
|
try {
|
||||||
boolean wantAsync = marker.isMinecraftAsync(current);
|
boolean wantAsync = marker.isMinecraftAsync(current);
|
||||||
boolean wantSync = !wantAsync;
|
boolean wantSync = !wantAsync;
|
||||||
|
|
||||||
// Quit if we haven't fulfilled our promise
|
// Wait for the next main thread heartbeat if we haven't fulfilled our promise
|
||||||
if ((onMainThread && wantAsync) || (!onMainThread && wantSync))
|
if (!onMainThread && wantSync) {
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's give it what it wants
|
||||||
|
if (onMainThread && wantAsync) {
|
||||||
|
asynchronousSender.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// We know this isn't on the main thread
|
||||||
|
processPacketHolder(false, holder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scheduler will do the rest
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (FieldAccessException e) {
|
} catch (FieldAccessException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return;
|
|
||||||
|
// Just drop the packet
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (marker.isProcessed() || hasExpired) {
|
// Silently skip players that have logged out
|
||||||
if (hasExpired) {
|
if (isOnline(current.getPlayer())) {
|
||||||
// Notify timeout listeners
|
sendPacket(current);
|
||||||
onPacketTimeout(current);
|
|
||||||
|
|
||||||
// Recompute
|
|
||||||
marker = current.getAsyncMarker();
|
|
||||||
hasExpired = marker.hasExpired();
|
|
||||||
}
|
|
||||||
if (marker.isProcessed() && !current.isCancelled() && !hasExpired) {
|
|
||||||
// Silently skip players that have logged out
|
|
||||||
if (isOnline(current.getPlayer())) {
|
|
||||||
sendPacket(current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendingQueue.poll();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only repeat when packets are removed
|
// Drop the packet
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add it back and stop sending
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -201,7 +260,7 @@ abstract class PacketSendingQueue {
|
|||||||
* @return TRUE if it must, FALSE otherwise.
|
* @return TRUE if it must, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isSynchronizeMain() {
|
public boolean isSynchronizeMain() {
|
||||||
return synchronizeMain;
|
return notThreadSafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,7 +293,12 @@ abstract class PacketSendingQueue {
|
|||||||
* Automatically transmits every delayed packet.
|
* Automatically transmits every delayed packet.
|
||||||
*/
|
*/
|
||||||
public void cleanupAll() {
|
public void cleanupAll() {
|
||||||
// Note that the cleanup itself will always occur on the main thread
|
if (!cleanedUp) {
|
||||||
forceSend();
|
// Note that the cleanup itself will always occur on the main thread
|
||||||
|
forceSend();
|
||||||
|
|
||||||
|
// And we're done
|
||||||
|
cleanedUp = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,249 @@
|
|||||||
|
package com.comphenix.protocol.async;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains every sending queue for every player.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
class PlayerSendingHandler {
|
||||||
|
|
||||||
|
private ErrorReporter reporter;
|
||||||
|
private ConcurrentHashMap<String, QueueContainer> playerSendingQueues;
|
||||||
|
|
||||||
|
// Timeout listeners
|
||||||
|
private SortedPacketListenerList serverTimeoutListeners;
|
||||||
|
private SortedPacketListenerList clientTimeoutListeners;
|
||||||
|
|
||||||
|
// Asynchronous packet sending
|
||||||
|
private Executor asynchronousSender;
|
||||||
|
|
||||||
|
// Whether or not we're currently cleaning up
|
||||||
|
private volatile boolean cleaningUp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sending queues for a given player.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
private class QueueContainer {
|
||||||
|
private PacketSendingQueue serverQueue;
|
||||||
|
private PacketSendingQueue clientQueue;
|
||||||
|
|
||||||
|
public QueueContainer() {
|
||||||
|
// Server packets can be sent concurrently
|
||||||
|
serverQueue = new PacketSendingQueue(false, asynchronousSender) {
|
||||||
|
@Override
|
||||||
|
protected void onPacketTimeout(PacketEvent event) {
|
||||||
|
if (!cleaningUp) {
|
||||||
|
serverTimeoutListeners.invokePacketSending(reporter, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Client packets must be synchronized
|
||||||
|
clientQueue = new PacketSendingQueue(true, asynchronousSender) {
|
||||||
|
@Override
|
||||||
|
protected void onPacketTimeout(PacketEvent event) {
|
||||||
|
if (!cleaningUp) {
|
||||||
|
clientTimeoutListeners.invokePacketSending(reporter, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketSendingQueue getServerQueue() {
|
||||||
|
return serverQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketSendingQueue getClientQueue() {
|
||||||
|
return clientQueue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a packet sending handler.
|
||||||
|
* @param reporter - error reporter.
|
||||||
|
* @param serverTimeoutListeners - set of server timeout listeners.
|
||||||
|
* @param clientTimeoutListeners - set of client timeout listeners.
|
||||||
|
*/
|
||||||
|
public PlayerSendingHandler(ErrorReporter reporter,
|
||||||
|
SortedPacketListenerList serverTimeoutListeners, SortedPacketListenerList clientTimeoutListeners) {
|
||||||
|
|
||||||
|
this.reporter = reporter;
|
||||||
|
this.serverTimeoutListeners = serverTimeoutListeners;
|
||||||
|
this.clientTimeoutListeners = clientTimeoutListeners;
|
||||||
|
|
||||||
|
// Initialize storage of queues
|
||||||
|
this.playerSendingQueues = new ConcurrentHashMap<String, QueueContainer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the asynchronous packet sender.
|
||||||
|
*/
|
||||||
|
public synchronized void initializeScheduler() {
|
||||||
|
if (asynchronousSender == null) {
|
||||||
|
ThreadFactory factory = new ThreadFactoryBuilder().
|
||||||
|
setDaemon(true).
|
||||||
|
setNameFormat("ProtocolLib-AsyncSender %s").
|
||||||
|
build();
|
||||||
|
asynchronousSender = Executors.newSingleThreadExecutor(factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 getSendingQueue(packet, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
String name = packet.getPlayer().getName();
|
||||||
|
QueueContainer queues = playerSendingQueues.get(name);
|
||||||
|
|
||||||
|
// Safe concurrent initialization
|
||||||
|
if (queues == null && createNew) {
|
||||||
|
final QueueContainer newContainer = new QueueContainer();
|
||||||
|
|
||||||
|
// Attempt to map the queue
|
||||||
|
queues = playerSendingQueues.putIfAbsent(name, newContainer);
|
||||||
|
|
||||||
|
if (queues == null) {
|
||||||
|
queues = newContainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for NULL again
|
||||||
|
if (queues != null)
|
||||||
|
return packet.isServerPacket() ? queues.getServerQueue() : queues.getClientQueue();
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send all pending packets.
|
||||||
|
*/
|
||||||
|
public void sendAllPackets() {
|
||||||
|
if (!cleaningUp) {
|
||||||
|
for (QueueContainer queues : playerSendingQueues.values()) {
|
||||||
|
queues.getClientQueue().cleanupAll();
|
||||||
|
queues.getServerQueue().cleanupAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately send every server packet with the given list of IDs.
|
||||||
|
* @param ids - ID of every packet to send immediately.
|
||||||
|
* @param synchronusOK - whether or not we're running on the main thread.
|
||||||
|
*/
|
||||||
|
public void sendServerPackets(List<Integer> ids, boolean synchronusOK) {
|
||||||
|
if (!cleaningUp) {
|
||||||
|
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||||
|
queue.getServerQueue().signalPacketUpdate(ids, synchronusOK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately send every client packet with the given list of IDs.
|
||||||
|
* @param ids - ID of every packet to send immediately.
|
||||||
|
* @param synchronusOK - whether or not we're running on the main thread.
|
||||||
|
*/
|
||||||
|
public void sendClientPackets(List<Integer> ids, boolean synchronusOK) {
|
||||||
|
if (!cleaningUp) {
|
||||||
|
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||||
|
queue.getClientQueue().signalPacketUpdate(ids, synchronusOK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send any outstanding server packets.
|
||||||
|
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||||
|
*/
|
||||||
|
public void trySendServerPackets(boolean onMainThread) {
|
||||||
|
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||||
|
queue.getServerQueue().trySendPackets(onMainThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send any outstanding server packets.
|
||||||
|
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||||
|
*/
|
||||||
|
public void trySendClientPackets(boolean onMainThread) {
|
||||||
|
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||||
|
queue.getClientQueue().trySendPackets(onMainThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every server packet queue for every player.
|
||||||
|
* @return Every sever packet queue.
|
||||||
|
*/
|
||||||
|
public List<PacketSendingQueue> getServerQueues() {
|
||||||
|
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
|
||||||
|
|
||||||
|
for (QueueContainer queue : playerSendingQueues.values())
|
||||||
|
result.add(queue.getServerQueue());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every client packet queue for every player.
|
||||||
|
* @return Every client packet queue.
|
||||||
|
*/
|
||||||
|
public List<PacketSendingQueue> getClientQueues() {
|
||||||
|
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
|
||||||
|
|
||||||
|
for (QueueContainer queue : playerSendingQueues.values())
|
||||||
|
result.add(queue.getClientQueue());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send all pending packets and clean up queues.
|
||||||
|
*/
|
||||||
|
public void cleanupAll() {
|
||||||
|
if (!cleaningUp) {
|
||||||
|
cleaningUp = true;
|
||||||
|
|
||||||
|
sendAllPackets();
|
||||||
|
playerSendingQueues.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a player has just logged out.
|
||||||
|
* @param player - the player that just logged out.
|
||||||
|
*/
|
||||||
|
public void removePlayer(Player player) {
|
||||||
|
String name = player.getName();
|
||||||
|
|
||||||
|
// Every packet will be dropped - there's nothing we can do
|
||||||
|
playerSendingQueues.remove(name);
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import java.util.NavigableMap;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
import com.google.common.collect.Ranges;
|
import com.google.common.collect.Ranges;
|
||||||
|
|
||||||
@ -32,24 +33,25 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
* Represents a range and a value in this interval tree.
|
* Represents a range and a value in this interval tree.
|
||||||
*/
|
*/
|
||||||
public class Entry implements Map.Entry<Range<TKey>, TValue> {
|
public class Entry implements Map.Entry<Range<TKey>, TValue> {
|
||||||
private final Range<TKey> key;
|
|
||||||
private EndPoint left;
|
private EndPoint left;
|
||||||
private EndPoint right;
|
private EndPoint right;
|
||||||
|
|
||||||
Entry(Range<TKey> key, EndPoint left, EndPoint right) {
|
Entry(EndPoint left, EndPoint right) {
|
||||||
if (left == null)
|
if (left == null)
|
||||||
throw new IllegalAccessError("left cannot be NUll");
|
throw new IllegalAccessError("left cannot be NUll");
|
||||||
if (right == null)
|
if (right == null)
|
||||||
throw new IllegalAccessError("right cannot be NUll");
|
throw new IllegalAccessError("right cannot be NUll");
|
||||||
|
if (left.key.compareTo(right.key) > 0)
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Left key (" + left.key + ") cannot be greater than the right key (" + right.key + ")");
|
||||||
|
|
||||||
this.key = key;
|
|
||||||
this.left = left;
|
this.left = left;
|
||||||
this.right = right;
|
this.right = right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Range<TKey> getKey() {
|
public Range<TKey> getKey() {
|
||||||
return key;
|
return Ranges.closed(left.key, right.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -66,6 +68,31 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
right.value = value;
|
right.value = value;
|
||||||
return old;
|
return old;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
// Quick equality check
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
} else if (obj instanceof AbstractIntervalTree.Entry) {
|
||||||
|
return Objects.equal(left.key, ((AbstractIntervalTree.Entry) obj).left.key) &&
|
||||||
|
Objects.equal(right.key, ((AbstractIntervalTree.Entry) obj).right.key) &&
|
||||||
|
Objects.equal(left.value, ((AbstractIntervalTree.Entry) obj).left.value);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(left.key, right.key, left.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Value %s at [%s, %s]", left.value, left.key, right.key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,8 +106,12 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
// The value this range contains
|
// The value this range contains
|
||||||
public TValue value;
|
public TValue value;
|
||||||
|
|
||||||
public EndPoint(State state, TValue value) {
|
// The key of this end point
|
||||||
|
public TKey key;
|
||||||
|
|
||||||
|
public EndPoint(State state, TKey key, TValue value) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
this.key = key;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,31 +138,46 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
checkBounds(lowerBound, upperBound);
|
checkBounds(lowerBound, upperBound);
|
||||||
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
|
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
|
||||||
|
|
||||||
boolean emptyRange = range.isEmpty();
|
EndPoint first = getNextEndPoint(lowerBound, true);
|
||||||
TKey first = !emptyRange ? range.firstKey() : null;
|
EndPoint last = getPreviousEndPoint(upperBound, true);
|
||||||
TKey last = !emptyRange ? range.lastKey() : null;
|
|
||||||
|
// Used while resizing intervals
|
||||||
|
EndPoint previous = null;
|
||||||
|
EndPoint next = null;
|
||||||
|
|
||||||
Set<Entry> resized = new HashSet<Entry>();
|
Set<Entry> resized = new HashSet<Entry>();
|
||||||
Set<Entry> removed = new HashSet<Entry>();
|
Set<Entry> removed = new HashSet<Entry>();
|
||||||
|
|
||||||
// Remove the previous element too. A close end-point must be preceded by an OPEN end-point.
|
// Remove the previous element too. A close end-point must be preceded by an OPEN end-point.
|
||||||
if (first != null && range.get(first).state == State.CLOSE) {
|
if (first != null && first.state == State.CLOSE) {
|
||||||
TKey key = bounds.floorKey(first);
|
previous = getPreviousEndPoint(first.key, false);
|
||||||
EndPoint removedPoint = removeIfNonNull(key);
|
|
||||||
|
|
||||||
// Add the interval back
|
// Add the interval back
|
||||||
if (removedPoint != null && preserveDifference) {
|
if (previous != null) {
|
||||||
resized.add(putUnsafe(key, decrementKey(lowerBound), removedPoint.value));
|
removed.add(getEntry(previous, first));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the closing element too.
|
// Get the closing element too.
|
||||||
if (last != null && range.get(last).state == State.OPEN) {
|
if (last != null && last.state == State.OPEN) {
|
||||||
TKey key = bounds.ceilingKey(last);
|
next = getNextEndPoint(last.key, false);
|
||||||
EndPoint removedPoint = removeIfNonNull(key);
|
|
||||||
|
|
||||||
if (removedPoint != null && preserveDifference) {
|
if (next != null) {
|
||||||
resized.add(putUnsafe(incrementKey(upperBound), key, removedPoint.value));
|
removed.add(getEntry(last, next));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now remove both ranges
|
||||||
|
removeEntrySafely(previous, first);
|
||||||
|
removeEntrySafely(last, next);
|
||||||
|
|
||||||
|
// Add new resized intervals
|
||||||
|
if (preserveDifference) {
|
||||||
|
if (previous != null) {
|
||||||
|
resized.add(putUnsafe(previous.key, decrementKey(lowerBound), previous.value));
|
||||||
|
}
|
||||||
|
if (next != null) {
|
||||||
|
resized.add(putUnsafe(incrementKey(upperBound), next.key, next.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +186,6 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
invokeEntryRemoved(removed);
|
invokeEntryRemoved(removed);
|
||||||
|
|
||||||
if (preserveDifference) {
|
if (preserveDifference) {
|
||||||
invokeEntryRemoved(resized);
|
|
||||||
invokeEntryAdded(resized);
|
invokeEntryAdded(resized);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,12 +194,30 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper
|
/**
|
||||||
private EndPoint removeIfNonNull(TKey key) {
|
* Retrieve the entry from a given set of end points.
|
||||||
if (key != null) {
|
* @param left - leftmost end point.
|
||||||
return bounds.remove(key);
|
* @param right - rightmost end point.
|
||||||
|
* @return The associated entry.
|
||||||
|
*/
|
||||||
|
protected Entry getEntry(EndPoint left, EndPoint right) {
|
||||||
|
if (left == null)
|
||||||
|
throw new IllegalArgumentException("left endpoint cannot be NULL.");
|
||||||
|
if (right == null)
|
||||||
|
throw new IllegalArgumentException("right endpoint cannot be NULL.");
|
||||||
|
|
||||||
|
// Make sure the order is correct
|
||||||
|
if (right.key.compareTo(left.key) < 0) {
|
||||||
|
return getEntry(right, left);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return new Entry(left, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeEntrySafely(EndPoint left, EndPoint right) {
|
||||||
|
if (left != null && right != null) {
|
||||||
|
bounds.remove(left.key);
|
||||||
|
bounds.remove(right.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +228,7 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
if (endPoint != null) {
|
if (endPoint != null) {
|
||||||
endPoint.state = State.BOTH;
|
endPoint.state = State.BOTH;
|
||||||
} else {
|
} else {
|
||||||
endPoint = new EndPoint(state, value);
|
endPoint = new EndPoint(state, key, value);
|
||||||
bounds.put(key, endPoint);
|
bounds.put(key, endPoint);
|
||||||
}
|
}
|
||||||
return endPoint;
|
return endPoint;
|
||||||
@ -199,8 +262,7 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
|
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
|
||||||
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
|
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
|
||||||
|
|
||||||
Range<TKey> range = Ranges.closed(lowerBound, upperBound);
|
return new Entry(left, right);
|
||||||
return new Entry(range, left, right);
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -257,15 +319,16 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
private void getEntries(Set<Entry> destination, NavigableMap<TKey, EndPoint> map) {
|
private void getEntries(Set<Entry> destination, NavigableMap<TKey, EndPoint> map) {
|
||||||
Map.Entry<TKey, EndPoint> last = null;
|
Map.Entry<TKey, EndPoint> last = null;
|
||||||
|
|
||||||
for (Map.Entry<TKey, EndPoint> entry : bounds.entrySet()) {
|
for (Map.Entry<TKey, EndPoint> entry : map.entrySet()) {
|
||||||
switch (entry.getValue().state) {
|
switch (entry.getValue().state) {
|
||||||
case BOTH:
|
case BOTH:
|
||||||
EndPoint point = entry.getValue();
|
EndPoint point = entry.getValue();
|
||||||
destination.add(new Entry(Ranges.singleton(entry.getKey()), point, point));
|
destination.add(new Entry(point, point));
|
||||||
break;
|
break;
|
||||||
case CLOSE:
|
case CLOSE:
|
||||||
Range<TKey> range = Ranges.closed(last.getKey(), entry.getKey());
|
if (last != null) {
|
||||||
destination.add(new Entry(range, last.getValue(), entry.getValue()));
|
destination.add(new Entry(last.getValue(), entry.getValue()));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case OPEN:
|
case OPEN:
|
||||||
// We don't know the full range yet
|
// We don't know the full range yet
|
||||||
@ -284,7 +347,7 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
|
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
|
||||||
// Naively copy every range.
|
// Naively copy every range.
|
||||||
for (Entry entry : other.entrySet()) {
|
for (Entry entry : other.entrySet()) {
|
||||||
put(entry.key.lowerEndpoint(), entry.key.upperEndpoint(), entry.getValue());
|
put(entry.left.key, entry.right.key, entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +366,7 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the end-point composite associated with this key.
|
* Get the left-most end-point associated with this key.
|
||||||
* @param key - key to search for.
|
* @param key - key to search for.
|
||||||
* @return The end point found, or NULL.
|
* @return The end point found, or NULL.
|
||||||
*/
|
*/
|
||||||
@ -311,22 +374,60 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
|
|||||||
EndPoint ends = bounds.get(key);
|
EndPoint ends = bounds.get(key);
|
||||||
|
|
||||||
if (ends != null) {
|
if (ends != null) {
|
||||||
// This is a piece of cake
|
// Always return the end point to the left
|
||||||
return ends;
|
if (ends.state == State.CLOSE) {
|
||||||
} else {
|
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(decrementKey(key));
|
||||||
|
return left != null ? left.getValue() : null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return ends;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
// We need to determine if the point intersects with a range
|
// We need to determine if the point intersects with a range
|
||||||
TKey left = bounds.floorKey(key);
|
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(key);
|
||||||
|
|
||||||
// We only need to check to the left
|
// We only need to check to the left
|
||||||
if (left != null && bounds.get(left).state == State.OPEN) {
|
if (left != null && left.getValue().state == State.OPEN) {
|
||||||
return bounds.get(left);
|
return left.getValue();
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the previous end point of a given key.
|
||||||
|
* @param point - the point to search with.
|
||||||
|
* @param inclusive - whether or not to include the current point in the search.
|
||||||
|
* @return The previous end point of a given given key, or NULL if not found.
|
||||||
|
*/
|
||||||
|
protected EndPoint getPreviousEndPoint(TKey point, boolean inclusive) {
|
||||||
|
if (point != null) {
|
||||||
|
Map.Entry<TKey, EndPoint> previous = bounds.floorEntry(inclusive ? point : decrementKey(point));
|
||||||
|
|
||||||
|
if (previous != null)
|
||||||
|
return previous.getValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next end point of a given key.
|
||||||
|
* @param point - the point to search with.
|
||||||
|
* @param inclusive - whether or not to include the current point in the search.
|
||||||
|
* @return The next end point of a given given key, or NULL if not found.
|
||||||
|
*/
|
||||||
|
protected EndPoint getNextEndPoint(TKey point, boolean inclusive) {
|
||||||
|
if (point != null) {
|
||||||
|
Map.Entry<TKey, EndPoint> next = bounds.ceilingEntry(inclusive ? point : incrementKey(point));
|
||||||
|
|
||||||
|
if (next != null)
|
||||||
|
return next.getValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void invokeEntryAdded(Entry added) {
|
private void invokeEntryAdded(Entry added) {
|
||||||
if (added != null) {
|
if (added != null) {
|
||||||
onEntryAdded(added);
|
onEntryAdded(added);
|
||||||
|
@ -2,6 +2,7 @@ package com.comphenix.protocol.error;
|
|||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -41,6 +42,8 @@ public class DetailedErrorReporter implements ErrorReporter {
|
|||||||
protected int maxErrorCount;
|
protected int maxErrorCount;
|
||||||
protected Logger logger;
|
protected Logger logger;
|
||||||
|
|
||||||
|
protected WeakReference<Plugin> pluginReference;
|
||||||
|
|
||||||
// Whether or not Apache Commons is not present
|
// Whether or not Apache Commons is not present
|
||||||
protected boolean apacheCommonsMissing;
|
protected boolean apacheCommonsMissing;
|
||||||
|
|
||||||
@ -50,17 +53,18 @@ public class DetailedErrorReporter implements ErrorReporter {
|
|||||||
/**
|
/**
|
||||||
* Create a default error reporting system.
|
* Create a default error reporting system.
|
||||||
*/
|
*/
|
||||||
public DetailedErrorReporter() {
|
public DetailedErrorReporter(Plugin plugin) {
|
||||||
this(DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
|
this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a central error reporting system.
|
* Create a central error reporting system.
|
||||||
|
* @param plugin - the plugin owner.
|
||||||
* @param prefix - default line prefix.
|
* @param prefix - default line prefix.
|
||||||
* @param supportURL - URL to report the error.
|
* @param supportURL - URL to report the error.
|
||||||
*/
|
*/
|
||||||
public DetailedErrorReporter(String prefix, String supportURL) {
|
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) {
|
||||||
this(prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
|
this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to get the logger.
|
// Attempt to get the logger.
|
||||||
@ -74,12 +78,17 @@ public class DetailedErrorReporter implements ErrorReporter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a central error reporting system.
|
* Create a central error reporting system.
|
||||||
|
* @param plugin - the plugin owner.
|
||||||
* @param prefix - default line prefix.
|
* @param prefix - default line prefix.
|
||||||
* @param supportURL - URL to report the error.
|
* @param supportURL - URL to report the error.
|
||||||
* @param maxErrorCount - number of errors to print before giving up.
|
* @param maxErrorCount - number of errors to print before giving up.
|
||||||
* @param logger - current logger.
|
* @param logger - current logger.
|
||||||
*/
|
*/
|
||||||
public DetailedErrorReporter(String prefix, String supportURL, int maxErrorCount, Logger logger) {
|
public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) {
|
||||||
|
if (plugin == null)
|
||||||
|
throw new IllegalArgumentException("Plugin cannot be NULL.");
|
||||||
|
|
||||||
|
this.pluginReference = new WeakReference<Plugin>(plugin);
|
||||||
this.prefix = prefix;
|
this.prefix = prefix;
|
||||||
this.supportURL = supportURL;
|
this.supportURL = supportURL;
|
||||||
this.maxErrorCount = maxErrorCount;
|
this.maxErrorCount = maxErrorCount;
|
||||||
@ -112,6 +121,8 @@ public class DetailedErrorReporter implements ErrorReporter {
|
|||||||
@Override
|
@Override
|
||||||
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) {
|
public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) {
|
||||||
|
|
||||||
|
final Plugin plugin = pluginReference.get();
|
||||||
|
|
||||||
// Do not overtly spam the server!
|
// Do not overtly spam the server!
|
||||||
if (++errorCount > maxErrorCount) {
|
if (++errorCount > maxErrorCount) {
|
||||||
String maxReached = String.format("Reached maxmimum error count. Cannot pass error %s from %s.", error, sender);
|
String maxReached = String.format("Reached maxmimum error count. Cannot pass error %s from %s.", error, sender);
|
||||||
@ -157,6 +168,12 @@ public class DetailedErrorReporter implements ErrorReporter {
|
|||||||
writer.println("Sender:");
|
writer.println("Sender:");
|
||||||
writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
|
writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX));
|
||||||
|
|
||||||
|
// And plugin
|
||||||
|
if (plugin != null) {
|
||||||
|
writer.println("Version:");
|
||||||
|
writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX));
|
||||||
|
}
|
||||||
|
|
||||||
// Add the server version too
|
// Add the server version too
|
||||||
if (Bukkit.getServer() != null) {
|
if (Bukkit.getServer() != null) {
|
||||||
writer.println("Server:");
|
writer.println("Server:");
|
||||||
|
@ -20,6 +20,7 @@ package com.comphenix.protocol.events;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.EventObject;
|
import java.util.EventObject;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -33,7 +34,9 @@ public class PacketEvent extends EventObject implements Cancellable {
|
|||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = -5360289379097430620L;
|
private static final long serialVersionUID = -5360289379097430620L;
|
||||||
|
|
||||||
private transient Player player;
|
private transient WeakReference<Player> playerReference;
|
||||||
|
private transient Player offlinePlayer;
|
||||||
|
|
||||||
private PacketContainer packet;
|
private PacketContainer packet;
|
||||||
private boolean serverPacket;
|
private boolean serverPacket;
|
||||||
private boolean cancel;
|
private boolean cancel;
|
||||||
@ -52,14 +55,14 @@ public class PacketEvent extends EventObject implements Cancellable {
|
|||||||
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
|
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
|
||||||
super(source);
|
super(source);
|
||||||
this.packet = packet;
|
this.packet = packet;
|
||||||
this.player = player;
|
this.playerReference = new WeakReference<Player>(player);
|
||||||
this.serverPacket = serverPacket;
|
this.serverPacket = serverPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
|
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
|
||||||
super(origial.source);
|
super(origial.source);
|
||||||
this.packet = origial.packet;
|
this.packet = origial.packet;
|
||||||
this.player = origial.player;
|
this.playerReference = origial.playerReference;
|
||||||
this.cancel = origial.cancel;
|
this.cancel = origial.cancel;
|
||||||
this.serverPacket = origial.serverPacket;
|
this.serverPacket = origial.serverPacket;
|
||||||
this.asyncMarker = asyncMarker;
|
this.asyncMarker = asyncMarker;
|
||||||
@ -131,7 +134,16 @@ public class PacketEvent extends EventObject implements Cancellable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether or not the packet should be cancelled.
|
* Sets whether or not the packet should be cancelled. Uncancelling is possible.
|
||||||
|
* <p>
|
||||||
|
* <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
|
||||||
|
* packet manager if you need to perform extensive processing. It should also be used
|
||||||
|
* if you need to synchronize with the main thread.
|
||||||
|
* <p>
|
||||||
|
* This ensures that other plugins can work with the same packet.
|
||||||
|
* <p>
|
||||||
|
* An asynchronous listener can also delay a packet indefinitely without having to block its thread.
|
||||||
|
*
|
||||||
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
|
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public void setCancelled(boolean cancel) {
|
public void setCancelled(boolean cancel) {
|
||||||
@ -143,7 +155,7 @@ public class PacketEvent extends EventObject implements Cancellable {
|
|||||||
* @return The player associated with this event.
|
* @return The player associated with this event.
|
||||||
*/
|
*/
|
||||||
public Player getPlayer() {
|
public Player getPlayer() {
|
||||||
return player;
|
return playerReference.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,18 +209,20 @@ public class PacketEvent extends EventObject implements Cancellable {
|
|||||||
output.defaultWriteObject();
|
output.defaultWriteObject();
|
||||||
|
|
||||||
// Write the name of the player (or NULL if it's not set)
|
// Write the name of the player (or NULL if it's not set)
|
||||||
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
|
output.writeObject(playerReference.get() != null ? new SerializedOfflinePlayer(playerReference.get()) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||||
// Default deserialization
|
// Default deserialization
|
||||||
input.defaultReadObject();
|
input.defaultReadObject();
|
||||||
|
|
||||||
final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject();
|
final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject();
|
||||||
|
|
||||||
if (offlinePlayer != null) {
|
// Better than nothing
|
||||||
// Better than nothing
|
if (serialized != null) {
|
||||||
player = offlinePlayer.getPlayer();
|
// Store it, to prevent weak reference from cleaning up the reference
|
||||||
|
offlinePlayer = serialized.getPlayer();
|
||||||
|
playerReference = new WeakReference<Player>(offlinePlayer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -598,46 +598,21 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
manager.registerEvents(new Listener() {
|
manager.registerEvents(new Listener() {
|
||||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.LOWEST)
|
||||||
public void onPrePlayerJoin(PlayerJoinEvent event) {
|
public void onPrePlayerJoin(PlayerJoinEvent event) {
|
||||||
try {
|
PacketFilterManager.this.onPrePlayerJoin(event);
|
||||||
// Let's clean up the other injection first.
|
|
||||||
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
|
|
||||||
} catch (Exception e) {
|
|
||||||
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject net handler for player.", e, event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
||||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
try {
|
PacketFilterManager.this.onPlayerJoin(event);
|
||||||
// This call will be ignored if no listeners are registered
|
|
||||||
playerInjection.injectPlayer(event.getPlayer());
|
|
||||||
} catch (Exception e) {
|
|
||||||
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
||||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
try {
|
PacketFilterManager.this.onPlayerQuit(event);
|
||||||
playerInjection.handleDisconnect(event.getPlayer());
|
|
||||||
playerInjection.uninjectPlayer(event.getPlayer());
|
|
||||||
} catch (Exception e) {
|
|
||||||
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject logged off player.", e, event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
||||||
public void onPluginDisabled(PluginDisableEvent event) {
|
public void onPluginDisabled(PluginDisableEvent event) {
|
||||||
try {
|
PacketFilterManager.this.onPluginDisabled(event, plugin);
|
||||||
// Clean up in case the plugin forgets
|
|
||||||
if (event.getPlugin() != plugin) {
|
|
||||||
removePacketListeners(event.getPlugin());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
reporter.reportDetailed(PacketFilterManager.this, "Unable handle disabled plugin.", e, event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, plugin);
|
}, plugin);
|
||||||
@ -648,6 +623,47 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onPrePlayerJoin(PlayerJoinEvent event) {
|
||||||
|
try {
|
||||||
|
// Let's clean up the other injection first.
|
||||||
|
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
|
||||||
|
} catch (Exception e) {
|
||||||
|
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject net handler for player.", e, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
try {
|
||||||
|
// This call will be ignored if no listeners are registered
|
||||||
|
playerInjection.injectPlayer(event.getPlayer());
|
||||||
|
} catch (Exception e) {
|
||||||
|
reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
try {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
asyncFilterManager.removePlayer(player);
|
||||||
|
playerInjection.handleDisconnect(player);
|
||||||
|
playerInjection.uninjectPlayer(player);
|
||||||
|
} catch (Exception e) {
|
||||||
|
reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject logged off player.", e, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPluginDisabled(PluginDisableEvent event, Plugin protocolLibrary) {
|
||||||
|
try {
|
||||||
|
// Clean up in case the plugin forgets
|
||||||
|
if (event.getPlugin() != protocolLibrary) {
|
||||||
|
removePacketListeners(event.getPlugin());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
reporter.reportDetailed(PacketFilterManager.this, "Unable handle disabled plugin.", e, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the number of listeners that expect packets during playing.
|
* Retrieve the number of listeners that expect packets during playing.
|
||||||
* @return Number of listeners.
|
* @return Number of listeners.
|
||||||
@ -689,7 +705,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
|
|
||||||
// Yes, this is crazy.
|
// Yes, this is crazy.
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
private void registerOld(PluginManager manager, Plugin plugin) {
|
private void registerOld(PluginManager manager, final Plugin plugin) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ClassLoader loader = manager.getClass().getClassLoader();
|
ClassLoader loader = manager.getClass().getClassLoader();
|
||||||
@ -699,6 +715,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority");
|
Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority");
|
||||||
|
|
||||||
// Get the priority
|
// Get the priority
|
||||||
|
Object priorityLowest = Enum.valueOf(eventPriority, "Lowest");
|
||||||
Object priorityMonitor = Enum.valueOf(eventPriority, "Monitor");
|
Object priorityMonitor = Enum.valueOf(eventPriority, "Monitor");
|
||||||
|
|
||||||
// Get event types
|
// Get event types
|
||||||
@ -714,26 +731,40 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
Method registerEvent = FuzzyReflection.fromObject(manager).getMethodByParameters("registerEvent",
|
Method registerEvent = FuzzyReflection.fromObject(manager).getMethodByParameters("registerEvent",
|
||||||
eventTypes, Listener.class, eventPriority, Plugin.class);
|
eventTypes, Listener.class, eventPriority, Plugin.class);
|
||||||
|
|
||||||
|
Enhancer playerLow = new Enhancer();
|
||||||
Enhancer playerEx = new Enhancer();
|
Enhancer playerEx = new Enhancer();
|
||||||
Enhancer serverEx = new Enhancer();
|
Enhancer serverEx = new Enhancer();
|
||||||
|
|
||||||
playerEx.setSuperclass(playerListener);
|
playerLow.setSuperclass(playerListener);
|
||||||
playerEx.setClassLoader(classLoader);
|
playerLow.setClassLoader(classLoader);
|
||||||
playerEx.setCallback(new MethodInterceptor() {
|
playerLow.setCallback(new MethodInterceptor() {
|
||||||
@Override
|
@Override
|
||||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
// Must have a parameter
|
// Must have a parameter
|
||||||
|
if (args.length == 1) {
|
||||||
|
Object event = args[0];
|
||||||
|
|
||||||
|
if (event instanceof PlayerJoinEvent) {
|
||||||
|
onPrePlayerJoin((PlayerJoinEvent) event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
playerEx.setSuperclass(playerListener);
|
||||||
|
playerEx.setClassLoader(classLoader);
|
||||||
|
playerEx.setCallback(new MethodInterceptor() {
|
||||||
|
@Override
|
||||||
|
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
Object event = args[0];
|
Object event = args[0];
|
||||||
|
|
||||||
// Check for the correct event
|
// Check for the correct event
|
||||||
if (event instanceof PlayerJoinEvent) {
|
if (event instanceof PlayerJoinEvent) {
|
||||||
Player player = ((PlayerJoinEvent) event).getPlayer();
|
onPlayerJoin((PlayerJoinEvent) event);
|
||||||
playerInjection.injectPlayer(player);
|
|
||||||
} else if (event instanceof PlayerQuitEvent) {
|
} else if (event instanceof PlayerQuitEvent) {
|
||||||
Player player = ((PlayerQuitEvent) event).getPlayer();
|
onPlayerQuit((PlayerQuitEvent) event);
|
||||||
playerInjection.handleDisconnect(player);
|
|
||||||
playerInjection.uninjectPlayer(player);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -751,16 +782,18 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
Object event = args[0];
|
Object event = args[0];
|
||||||
|
|
||||||
if (event instanceof PluginDisableEvent)
|
if (event instanceof PluginDisableEvent)
|
||||||
removePacketListeners(((PluginDisableEvent) event).getPlugin());
|
onPluginDisabled((PluginDisableEvent) event, plugin);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create our listener
|
// Create our listener
|
||||||
|
Object playerProxyLow = playerLow.create();
|
||||||
Object playerProxy = playerEx.create();
|
Object playerProxy = playerEx.create();
|
||||||
Object serverProxy = serverEx.create();
|
Object serverProxy = serverEx.create();
|
||||||
|
|
||||||
|
registerEvent.invoke(manager, playerJoinType, playerProxyLow, priorityLowest, plugin);
|
||||||
registerEvent.invoke(manager, playerJoinType, playerProxy, priorityMonitor, plugin);
|
registerEvent.invoke(manager, playerJoinType, playerProxy, priorityMonitor, plugin);
|
||||||
registerEvent.invoke(manager, playerQuitType, playerProxy, priorityMonitor, plugin);
|
registerEvent.invoke(manager, playerQuitType, playerProxy, priorityMonitor, plugin);
|
||||||
registerEvent.invoke(manager, pluginDisabledType, serverProxy, priorityMonitor, plugin);
|
registerEvent.invoke(manager, pluginDisabledType, serverProxy, priorityMonitor, plugin);
|
||||||
|
@ -83,8 +83,8 @@ class PacketInjector {
|
|||||||
public void undoCancel(Integer id, Packet packet) {
|
public void undoCancel(Integer id, Packet packet) {
|
||||||
ReadPacketModifier modifier = readModifier.get(id);
|
ReadPacketModifier modifier = readModifier.get(id);
|
||||||
|
|
||||||
// Cancelled packets are represented with NULL
|
// See if this packet has been cancelled before
|
||||||
if (modifier != null && modifier.getOverride(packet) == null) {
|
if (modifier != null && modifier.hasCancelled(packet)) {
|
||||||
modifier.removeOverride(packet);
|
modifier.removeOverride(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,15 @@ class ReadPacketModifier implements MethodInterceptor {
|
|||||||
return override.get(packet);
|
return override.get(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given packet has been cancelled before.
|
||||||
|
* @param packet - the packet to check.
|
||||||
|
* @return TRUE if it has been cancelled, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasCancelled(Packet packet) {
|
||||||
|
return getOverride(packet) == CANCEL_MARKER;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
|
|
||||||
|
@ -42,9 +42,9 @@ class InjectedArrayList extends ArrayList<Packet> {
|
|||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = -1173865905404280990L;
|
private static final long serialVersionUID = -1173865905404280990L;
|
||||||
|
|
||||||
private PlayerInjector injector;
|
private transient PlayerInjector injector;
|
||||||
private Set<Packet> ignoredPackets;
|
private transient Set<Packet> ignoredPackets;
|
||||||
private ClassLoader classLoader;
|
private transient ClassLoader classLoader;
|
||||||
|
|
||||||
public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set<Packet> ignoredPackets) {
|
public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set<Packet> ignoredPackets) {
|
||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
|
@ -53,8 +53,8 @@ public class NetworkServerInjector extends PlayerInjector {
|
|||||||
|
|
||||||
private volatile static CallbackFilter callbackFilter;
|
private volatile static CallbackFilter callbackFilter;
|
||||||
|
|
||||||
private static Field disconnectField;
|
private volatile static Field disconnectField;
|
||||||
private static Method sendPacketMethod;
|
private volatile static Method sendPacketMethod;
|
||||||
private InjectedServerConnection serverInjection;
|
private InjectedServerConnection serverInjection;
|
||||||
|
|
||||||
// Determine if we're listening
|
// Determine if we're listening
|
||||||
|
@ -604,10 +604,11 @@ public class PlayerInjectionHandler {
|
|||||||
*/
|
*/
|
||||||
public void scheduleDataInputRefresh(Player player) {
|
public void scheduleDataInputRefresh(Player player) {
|
||||||
final PlayerInjector injector = getInjector(player);
|
final PlayerInjector injector = getInjector(player);
|
||||||
final DataInputStream old = injector.getInputStream(true);
|
|
||||||
|
|
||||||
// Update the DataInputStream
|
// Update the DataInputStream
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
|
final DataInputStream old = injector.getInputStream(true);
|
||||||
|
|
||||||
injector.scheduleAction(new Runnable() {
|
injector.scheduleAction(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package com.comphenix.protocol.metrics;
|
package com.comphenix.protocol.metrics;
|
||||||
|
|
||||||
|
// EXTENSIVELY MODIFIED BY AADNK/COMPHENIX
|
||||||
|
// CHECK GIT FOR DETAILS
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Updater for Bukkit.
|
* Updater for Bukkit.
|
||||||
*
|
*
|
||||||
@ -9,12 +12,10 @@ import java.io.*;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
import javax.xml.stream.XMLEventReader;
|
import javax.xml.stream.XMLEventReader;
|
||||||
import javax.xml.stream.XMLInputFactory;
|
import javax.xml.stream.XMLInputFactory;
|
||||||
import javax.xml.stream.XMLStreamException;
|
import javax.xml.stream.XMLStreamException;
|
||||||
@ -23,6 +24,8 @@ import javax.xml.stream.events.XMLEvent;
|
|||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check dev.bukkit.org to find updates for a given plugin, and download the updates if needed.
|
* Check dev.bukkit.org to find updates for a given plugin, and download the updates if needed.
|
||||||
* <p>
|
* <p>
|
||||||
@ -211,6 +214,13 @@ public class Updater
|
|||||||
*/
|
*/
|
||||||
public Updater(Plugin plugin, Logger logger, String slug, File file, String permission)
|
public Updater(Plugin plugin, Logger logger, String slug, File file, String permission)
|
||||||
{
|
{
|
||||||
|
// I hate NULL
|
||||||
|
Preconditions.checkNotNull(plugin, "plugin");
|
||||||
|
Preconditions.checkNotNull(logger, "logger");
|
||||||
|
Preconditions.checkNotNull(slug, "slug");
|
||||||
|
Preconditions.checkNotNull(file, "file");
|
||||||
|
Preconditions.checkNotNull(permission, "permission");
|
||||||
|
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.slug = slug;
|
this.slug = slug;
|
||||||
@ -253,20 +263,30 @@ public class Updater
|
|||||||
String fileLink = getFile(versionLink);
|
String fileLink = getFile(versionLink);
|
||||||
if(fileLink != null && type != UpdateType.NO_DOWNLOAD)
|
if(fileLink != null && type != UpdateType.NO_DOWNLOAD)
|
||||||
{
|
{
|
||||||
String name = file.getName();
|
String [] split = fileLink.split("/");
|
||||||
// If it's a zip file, it shouldn't be downloaded as the plugin's name
|
String name = split[split.length-1];
|
||||||
if(fileLink.endsWith(".zip"))
|
|
||||||
{
|
logger.info("Downloading " + fileLink);
|
||||||
String [] split = fileLink.split("/");
|
|
||||||
name = split[split.length-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Never download the same file twice
|
// Never download the same file twice
|
||||||
if (!downloadedVersion.equalsIgnoreCase(versionLink)) {
|
if (downloadedVersion == null || !downloadedVersion.equalsIgnoreCase(versionLink)) {
|
||||||
saveFile(new File("plugins/" + updateFolder), name, fileLink);
|
File path = new File("plugins/");
|
||||||
|
|
||||||
|
// We can update the JAR in place as we're using different JAR file names
|
||||||
|
saveFile(path, name, fileLink);
|
||||||
downloadedVersion = versionLink;
|
downloadedVersion = versionLink;
|
||||||
result = UpdateResult.SUCCESS;
|
result = UpdateResult.SUCCESS;
|
||||||
|
|
||||||
|
// ProtocolLib - try to remove the current version
|
||||||
|
try {
|
||||||
|
if (!file.delete()) {
|
||||||
|
File zeroCurrentJar = new File(path, updateFolder + "/" + file.getName());
|
||||||
|
zeroCurrentJar.createNewFile();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warning("Cannot delete old ProtocolLib version: " + file.getName());
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
result = UpdateResult.UPDATE_AVAILABLE;
|
result = UpdateResult.UPDATE_AVAILABLE;
|
||||||
}
|
}
|
||||||
@ -338,26 +358,14 @@ public class Updater
|
|||||||
logger.info("Downloading update: " + percent + "% of " + fileLength + " bytes.");
|
logger.info("Downloading update: " + percent + "% of " + fileLength + " bytes.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Just a quick check to make sure we didn't leave any files from last time...
|
|
||||||
for(File xFile : new File("plugins/" + updateFolder).listFiles())
|
if(announce)
|
||||||
{
|
logger.info("Finished updating.");
|
||||||
if(xFile.getName().endsWith(".zip"))
|
|
||||||
{
|
|
||||||
xFile.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check to see if it's a zip file, if it is, unzip it.
|
|
||||||
File dFile = new File(folder.getAbsolutePath() + "/" + file);
|
|
||||||
if(dFile.getName().endsWith(".zip"))
|
|
||||||
{
|
|
||||||
// Unzip
|
|
||||||
unzip(dFile.getCanonicalPath());
|
|
||||||
}
|
|
||||||
if(announce) logger.info("Finished updating.");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.warning("The auto-updater tried to download a new update, but was unsuccessful.");
|
logger.warning("The auto-updater tried to download a new update, but was unsuccessful.");
|
||||||
|
logger.log(Level.INFO, "Error message to submit as a ticket.", ex);
|
||||||
result = Updater.UpdateResult.FAIL_DOWNLOAD;
|
result = Updater.UpdateResult.FAIL_DOWNLOAD;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@ -379,99 +387,6 @@ public class Updater
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of Zip-File-Extractor, modified by H31IX for use with Bukkit
|
|
||||||
*/
|
|
||||||
private void unzip(String file)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File fSourceZip = new File(file);
|
|
||||||
String zipPath = file.substring(0, file.length()-4);
|
|
||||||
ZipFile zipFile = new ZipFile(fSourceZip);
|
|
||||||
Enumeration<? extends ZipEntry> e = zipFile.entries();
|
|
||||||
while(e.hasMoreElements())
|
|
||||||
{
|
|
||||||
ZipEntry entry = (ZipEntry)e.nextElement();
|
|
||||||
File destinationFilePath = new File(zipPath,entry.getName());
|
|
||||||
destinationFilePath.getParentFile().mkdirs();
|
|
||||||
if(entry.isDirectory())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry));
|
|
||||||
int b;
|
|
||||||
byte buffer[] = new byte[BYTE_SIZE];
|
|
||||||
FileOutputStream fos = new FileOutputStream(destinationFilePath);
|
|
||||||
BufferedOutputStream bos = new BufferedOutputStream(fos, BYTE_SIZE);
|
|
||||||
while((b = bis.read(buffer, 0, BYTE_SIZE)) != -1)
|
|
||||||
{
|
|
||||||
bos.write(buffer, 0, b);
|
|
||||||
}
|
|
||||||
bos.flush();
|
|
||||||
bos.close();
|
|
||||||
bis.close();
|
|
||||||
String name = destinationFilePath.getName();
|
|
||||||
if(name.endsWith(".jar") && pluginFile(name))
|
|
||||||
{
|
|
||||||
destinationFilePath.renameTo(new File("plugins/" + updateFolder + "/" + name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry = null;
|
|
||||||
destinationFilePath = null;
|
|
||||||
}
|
|
||||||
e = null;
|
|
||||||
zipFile.close();
|
|
||||||
zipFile = null;
|
|
||||||
// Move any plugin data folders that were included to the right place, Bukkit won't do this for us.
|
|
||||||
for(File dFile : new File(zipPath).listFiles())
|
|
||||||
{
|
|
||||||
if(dFile.isDirectory())
|
|
||||||
{
|
|
||||||
if(pluginFile(dFile.getName()))
|
|
||||||
{
|
|
||||||
File oFile = new File("plugins/" + dFile.getName()); // Get current dir
|
|
||||||
File [] contents = oFile.listFiles(); // List of existing files in the current dir
|
|
||||||
for(File cFile : dFile.listFiles()) // Loop through all the files in the new dir
|
|
||||||
{
|
|
||||||
boolean found = false;
|
|
||||||
for(File xFile : contents) // Loop through contents to see if it exists
|
|
||||||
{
|
|
||||||
if(xFile.getName().equals(cFile.getName()))
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!found)
|
|
||||||
{
|
|
||||||
// Move the new file into the current dir
|
|
||||||
cFile.renameTo(new File(oFile.getCanonicalFile() + "/" + cFile.getName()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// This file already exists, so we don't need it anymore.
|
|
||||||
cFile.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dFile.delete();
|
|
||||||
}
|
|
||||||
new File(zipPath).delete();
|
|
||||||
fSourceZip.delete();
|
|
||||||
}
|
|
||||||
catch(IOException ex)
|
|
||||||
{
|
|
||||||
ex.printStackTrace();
|
|
||||||
logger.warning("The auto-updater tried to unzip a new update file, but was unsuccessful.");
|
|
||||||
result = Updater.UpdateResult.FAIL_DOWNLOAD;
|
|
||||||
}
|
|
||||||
new File(file).delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the name of a jar is one of the plugins currently installed, used for extracting the correct files out of a zip.
|
* Check if the name of a jar is one of the plugins currently installed, used for extracting the correct files out of a zip.
|
||||||
*/
|
*/
|
||||||
|
@ -202,7 +202,7 @@ public class StructureModifier<TField> {
|
|||||||
*/
|
*/
|
||||||
public boolean isReadOnly(int fieldIndex) {
|
public boolean isReadOnly(int fieldIndex) {
|
||||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||||
new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
throw new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
||||||
|
|
||||||
return Modifier.isFinal(data.get(fieldIndex).getModifiers());
|
return Modifier.isFinal(data.get(fieldIndex).getModifiers());
|
||||||
}
|
}
|
||||||
@ -219,7 +219,7 @@ public class StructureModifier<TField> {
|
|||||||
*/
|
*/
|
||||||
public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException {
|
public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException {
|
||||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||||
new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
throw new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
StructureModifier.setFinalState(data.get(fieldIndex), value);
|
StructureModifier.setFinalState(data.get(fieldIndex), value);
|
||||||
@ -400,7 +400,7 @@ public class StructureModifier<TField> {
|
|||||||
if (a == null)
|
if (a == null)
|
||||||
return b == null;
|
return b == null;
|
||||||
else if (b == null)
|
else if (b == null)
|
||||||
return a == null;
|
return false;
|
||||||
else
|
else
|
||||||
return a.getSpecificType().equals(b.getSpecificType());
|
return a.getSpecificType().equals(b.getSpecificType());
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,16 @@ import java.util.concurrent.Callable;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles structure modifiers on a background thread.
|
* Compiles structure modifiers on a background thread.
|
||||||
@ -37,6 +42,11 @@ import com.comphenix.protocol.reflect.StructureModifier;
|
|||||||
*/
|
*/
|
||||||
public class BackgroundCompiler {
|
public class BackgroundCompiler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default format for the name of new worker threads.
|
||||||
|
*/
|
||||||
|
public static final String THREAD_FORMAT = "ProtocolLib-StructureCompiler %s";
|
||||||
|
|
||||||
// How long to wait for a shutdown
|
// How long to wait for a shutdown
|
||||||
public static final int SHUTDOWN_DELAY_MS = 2000;
|
public static final int SHUTDOWN_DELAY_MS = 2000;
|
||||||
|
|
||||||
@ -48,6 +58,7 @@ public class BackgroundCompiler {
|
|||||||
private boolean shuttingDown;
|
private boolean shuttingDown;
|
||||||
|
|
||||||
private ExecutorService executor;
|
private ExecutorService executor;
|
||||||
|
private ErrorReporter reporter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the current background compiler.
|
* Retrieves the current background compiler.
|
||||||
@ -67,24 +78,38 @@ public class BackgroundCompiler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a background compiler.
|
* Initialize a background compiler.
|
||||||
|
* <p>
|
||||||
|
* Uses the default {@link #THREAD_FORMAT} to name worker threads.
|
||||||
* @param loader - class loader from Bukkit.
|
* @param loader - class loader from Bukkit.
|
||||||
|
* @param reporter - current error reporter.
|
||||||
*/
|
*/
|
||||||
public BackgroundCompiler(ClassLoader loader) {
|
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter) {
|
||||||
this(loader, Executors.newSingleThreadExecutor());
|
ThreadFactory factory = new ThreadFactoryBuilder().
|
||||||
|
setDaemon(true).
|
||||||
|
setNameFormat(THREAD_FORMAT).
|
||||||
|
build();
|
||||||
|
initializeCompiler(loader, reporter, Executors.newSingleThreadExecutor(factory));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a background compiler utilizing the given thread pool.
|
* Initialize a background compiler utilizing the given thread pool.
|
||||||
* @param loader - class loader from Bukkit.
|
* @param loader - class loader from Bukkit.
|
||||||
|
* @param reporter - current error reporter.
|
||||||
* @param executor - thread pool we'll use.
|
* @param executor - thread pool we'll use.
|
||||||
*/
|
*/
|
||||||
public BackgroundCompiler(ClassLoader loader, ExecutorService executor) {
|
public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) {
|
||||||
|
initializeCompiler(loader, reporter, executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid "Constructor call must be the first statement".
|
||||||
|
private void initializeCompiler(ClassLoader loader, @Nullable ErrorReporter reporter, ExecutorService executor) {
|
||||||
if (loader == null)
|
if (loader == null)
|
||||||
throw new IllegalArgumentException("loader cannot be NULL");
|
throw new IllegalArgumentException("loader cannot be NULL");
|
||||||
if (executor == null)
|
if (executor == null)
|
||||||
throw new IllegalArgumentException("executor cannot be NULL");
|
throw new IllegalArgumentException("executor cannot be NULL");
|
||||||
|
|
||||||
this.compiler = new StructureCompiler(loader);
|
this.compiler = new StructureCompiler(loader);
|
||||||
|
this.reporter = reporter;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
}
|
}
|
||||||
@ -129,15 +154,30 @@ public class BackgroundCompiler {
|
|||||||
executor.submit(new Callable<Object>() {
|
executor.submit(new Callable<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object call() throws Exception {
|
public Object call() throws Exception {
|
||||||
|
|
||||||
StructureModifier<TKey> modifier = uncompiled;
|
StructureModifier<TKey> modifier = uncompiled;
|
||||||
|
|
||||||
// Do our compilation
|
// Do our compilation
|
||||||
modifier = compiler.compile(modifier);
|
try {
|
||||||
listener.onCompiled(modifier);
|
modifier = compiler.compile(modifier);
|
||||||
|
listener.onCompiled(modifier);
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// Disable future compilations!
|
||||||
|
setEnabled(false);
|
||||||
|
|
||||||
|
// Inform about this error as best as we can
|
||||||
|
if (reporter != null) {
|
||||||
|
reporter.reportDetailed(BackgroundCompiler.this,
|
||||||
|
"Cannot compile structure. Disabing compiler.", e, uncompiled);
|
||||||
|
} else {
|
||||||
|
System.err.println("Exception occured in structure compiler: ");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We'll also return the new structure modifier
|
// We'll also return the new structure modifier
|
||||||
return modifier;
|
return modifier;
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (RejectedExecutionException e) {
|
} catch (RejectedExecutionException e) {
|
||||||
|
@ -30,9 +30,8 @@ import com.google.common.collect.Sets;
|
|||||||
* Represents a compiled structure modifier.
|
* Represents a compiled structure modifier.
|
||||||
*
|
*
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
* @param <TField> Field type.
|
|
||||||
*/
|
*/
|
||||||
public abstract class CompiledStructureModifier<TField> extends StructureModifier<TField> {
|
public abstract class CompiledStructureModifier extends StructureModifier<Object> {
|
||||||
// Used to compile instances of structure modifiers
|
// Used to compile instances of structure modifiers
|
||||||
protected StructureCompiler compiler;
|
protected StructureCompiler compiler;
|
||||||
|
|
||||||
@ -64,9 +63,8 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Speed up the default writer
|
// Speed up the default writer
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
|
public StructureModifier<Object> writeDefaults() throws FieldAccessException {
|
||||||
|
|
||||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||||
|
|
||||||
@ -75,21 +73,20 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
|
|||||||
Integer index = entry.getValue();
|
Integer index = entry.getValue();
|
||||||
Field field = entry.getKey();
|
Field field = entry.getKey();
|
||||||
|
|
||||||
write(index, (TField) generator.getDefault(field.getType()));
|
write(index, (Object) generator.getDefault(field.getType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public final TField read(int fieldIndex) throws FieldAccessException {
|
public final Object read(int fieldIndex) throws FieldAccessException {
|
||||||
Object result = readGenerated(fieldIndex);
|
Object result = readGenerated(fieldIndex);
|
||||||
|
|
||||||
if (converter != null)
|
if (converter != null)
|
||||||
return converter.getSpecific(result);
|
return converter.getSpecific(result);
|
||||||
else
|
else
|
||||||
return (TField) result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,11 +101,10 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
|
|||||||
|
|
||||||
protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException;
|
protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
|
public StructureModifier<Object> write(int index, Object value) throws FieldAccessException {
|
||||||
if (converter != null)
|
if (converter != null)
|
||||||
value = converter.getGeneric(getFieldType(index), (TField) value);
|
value = converter.getGeneric(getFieldType(index), value);
|
||||||
return writeGenerated(index, value);
|
return writeGenerated(index, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,15 +114,14 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
|
|||||||
* @param value - new value.
|
* @param value - new value.
|
||||||
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
|
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected void writeReflected(int index, Object value) throws FieldAccessException {
|
protected void writeReflected(int index, Object value) throws FieldAccessException {
|
||||||
super.write(index, (TField) value);
|
super.write(index, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException;
|
protected abstract StructureModifier<Object> writeGenerated(int index, Object value) throws FieldAccessException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StructureModifier<TField> withTarget(Object target) {
|
public StructureModifier<Object> withTarget(Object target) {
|
||||||
if (compiler != null)
|
if (compiler != null)
|
||||||
return compiler.compile(super.withTarget(target));
|
return compiler.compile(super.withTarget(target));
|
||||||
else
|
else
|
||||||
|
@ -52,8 +52,8 @@ import net.sf.cglib.asm.*;
|
|||||||
// case 0: return (Object) target.a;
|
// case 0: return (Object) target.a;
|
||||||
// case 1: return (Object) target.b;
|
// case 1: return (Object) target.b;
|
||||||
// case 2: return (Object) target.c;
|
// case 2: return (Object) target.c;
|
||||||
// case 3: return super.read(fieldIndex);
|
// case 3: return super.readReflected(fieldIndex);
|
||||||
// case 4: return super.read(fieldIndex);
|
// case 4: return super.readReflected(fieldIndex);
|
||||||
// case 5: return (Object) target.f;
|
// case 5: return (Object) target.f;
|
||||||
// case 6: return (Object) target.g;
|
// case 6: return (Object) target.g;
|
||||||
// case 7: return (Object) target.h;
|
// case 7: return (Object) target.h;
|
||||||
@ -72,8 +72,8 @@ import net.sf.cglib.asm.*;
|
|||||||
// case 1: target.b = (String) value; break;
|
// case 1: target.b = (String) value; break;
|
||||||
// case 2: target.c = (Integer) value; break;
|
// case 2: target.c = (Integer) value; break;
|
||||||
// case 3: target.d = (Integer) value; break;
|
// case 3: target.d = (Integer) value; break;
|
||||||
// case 4: super.write(index, value); break;
|
// case 4: super.writeReflected(index, value); break;
|
||||||
// case 5: super.write(index, value); break;
|
// case 5: super.writeReflected(index, value); break;
|
||||||
// case 6: target.g = (Byte) value; break;
|
// case 6: target.g = (Byte) value; break;
|
||||||
// case 7: target.h = (Integer) value; break;
|
// case 7: target.h = (Integer) value; break;
|
||||||
// default:
|
// default:
|
||||||
@ -94,7 +94,7 @@ public final class StructureCompiler {
|
|||||||
|
|
||||||
// Used to store generated classes of different types
|
// Used to store generated classes of different types
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
private class StructureKey {
|
private static class StructureKey {
|
||||||
private Class targetType;
|
private Class targetType;
|
||||||
private Class fieldType;
|
private Class fieldType;
|
||||||
|
|
||||||
@ -186,6 +186,15 @@ public final class StructureCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a variable identifier that can uniquely represent the given type.
|
||||||
|
* @param type - a type.
|
||||||
|
* @return A unique and legal identifier for the given type.
|
||||||
|
*/
|
||||||
|
private String getSafeTypeName(Class<?> type) {
|
||||||
|
return type.getCanonicalName().replace("[]", "Array").replace(".", "_");
|
||||||
|
}
|
||||||
|
|
||||||
private <TField> Class<?> generateClass(StructureModifier<TField> source) {
|
private <TField> Class<?> generateClass(StructureModifier<TField> source) {
|
||||||
|
|
||||||
ClassWriter cw = new ClassWriter(0);
|
ClassWriter cw = new ClassWriter(0);
|
||||||
@ -193,7 +202,9 @@ public final class StructureCompiler {
|
|||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
Class targetType = source.getTargetType();
|
Class targetType = source.getTargetType();
|
||||||
|
|
||||||
String className = "CompiledStructure$" + targetType.getSimpleName() + source.getFieldType().getSimpleName();
|
String className = "CompiledStructure$" +
|
||||||
|
getSafeTypeName(targetType) + "$" +
|
||||||
|
getSafeTypeName(source.getFieldType());
|
||||||
String targetSignature = Type.getDescriptor(targetType);
|
String targetSignature = Type.getDescriptor(targetType);
|
||||||
String targetName = targetType.getName().replace('.', '/');
|
String targetName = targetType.getName().replace('.', '/');
|
||||||
|
|
||||||
@ -208,8 +219,7 @@ public final class StructureCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
|
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
|
||||||
"<TField:Ljava/lang/Object;>L" + COMPILED_CLASS + "<TTField;>;",
|
null, COMPILED_CLASS, null);
|
||||||
COMPILED_CLASS, null);
|
|
||||||
|
|
||||||
createFields(cw, targetSignature);
|
createFields(cw, targetSignature);
|
||||||
createConstructor(cw, className, targetSignature, targetName);
|
createConstructor(cw, className, targetSignature, targetName);
|
||||||
@ -284,14 +294,16 @@ public final class StructureCompiler {
|
|||||||
private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
|
private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
|
||||||
|
|
||||||
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
|
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
|
||||||
String methodSignature = "(ITTField;)L" + SUPER_CLASS + "<TTField;>;";
|
String methodSignature = "(ILjava/lang/Object;)L" + SUPER_CLASS + "<Ljava/lang/Object;>;";
|
||||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
|
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
|
||||||
new String[] { FIELD_EXCEPTION_CLASS });
|
new String[] { FIELD_EXCEPTION_CLASS });
|
||||||
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||||
|
|
||||||
|
String generatedClassName = PACKAGE_NAME + "/" + className;
|
||||||
|
|
||||||
mv.visitCode();
|
mv.visitCode();
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature);
|
||||||
mv.visitVarInsn(Opcodes.ASTORE, 3);
|
mv.visitVarInsn(Opcodes.ASTORE, 3);
|
||||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
|
|
||||||
@ -340,7 +352,7 @@ public final class StructureCompiler {
|
|||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, COMPILED_CLASS, "writeReflected", "(ILjava/lang/Object;)V;");
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "writeReflected", "(ILjava/lang/Object;)V");
|
||||||
}
|
}
|
||||||
|
|
||||||
mv.visitJumpInsn(Opcodes.GOTO, returnLabel);
|
mv.visitJumpInsn(Opcodes.GOTO, returnLabel);
|
||||||
@ -373,9 +385,11 @@ public final class StructureCompiler {
|
|||||||
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
|
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
|
||||||
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||||
|
|
||||||
|
String generatedClassName = PACKAGE_NAME + "/" + className;
|
||||||
|
|
||||||
mv.visitCode();
|
mv.visitCode();
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature);
|
||||||
mv.visitVarInsn(Opcodes.ASTORE, 2);
|
mv.visitVarInsn(Opcodes.ASTORE, 2);
|
||||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
|
|
||||||
@ -414,7 +428,7 @@ public final class StructureCompiler {
|
|||||||
// We have to use reflection for private and protected fields.
|
// We have to use reflection for private and protected fields.
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, COMPILED_CLASS, "readReflected", "(I)Ljava/lang/Object;");
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "readReflected", "(I)Ljava/lang/Object;");
|
||||||
}
|
}
|
||||||
|
|
||||||
mv.visitInsn(Opcodes.ARETURN);
|
mv.visitInsn(Opcodes.ARETURN);
|
||||||
@ -440,25 +454,27 @@ public final class StructureCompiler {
|
|||||||
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
|
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
|
||||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
|
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
|
||||||
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
|
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
|
||||||
"(L" + SUPER_CLASS + "<TTField;>;L" + SUPER_CLASS + ";)V", null);
|
"(L" + SUPER_CLASS + "<Ljava/lang/Object;>;L" + PACKAGE_NAME + "/StructureCompiler;)V", null);
|
||||||
|
String fullClassName = PACKAGE_NAME + "/" + className;
|
||||||
|
|
||||||
mv.visitCode();
|
mv.visitCode();
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
|
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, PACKAGE_NAME + "/" + className, "initialize", "(L" + SUPER_CLASS + ";)V");
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, fullClassName, "initialize", "(L" + SUPER_CLASS + ";)V");
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
|
||||||
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "target", "Ljava/lang/Object;");
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
mv.visitFieldInsn(Opcodes.GETFIELD, fullClassName, "target", "Ljava/lang/Object;");
|
||||||
mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
|
mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
|
||||||
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "typedTarget", targetSignature);
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||||
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
|
mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
mv.visitInsn(Opcodes.RETURN);
|
||||||
mv.visitMaxs(2, 3);
|
mv.visitMaxs(2, 3);
|
||||||
mv.visitEnd();
|
mv.visitEnd();
|
||||||
|
@ -47,7 +47,7 @@ public class CollectionGenerator implements InstanceProvider {
|
|||||||
@Override
|
@Override
|
||||||
public Object create(@Nullable Class<?> type) {
|
public Object create(@Nullable Class<?> type) {
|
||||||
// Standard collection types
|
// Standard collection types
|
||||||
if (type.isInterface()) {
|
if (type != null && type.isInterface()) {
|
||||||
if (type.equals(Collection.class) || type.equals(List.class))
|
if (type.equals(Collection.class) || type.equals(List.class))
|
||||||
return new ArrayList<Object>();
|
return new ArrayList<Object>();
|
||||||
else if (type.equals(Set.class))
|
else if (type.equals(Set.class))
|
||||||
|
@ -119,7 +119,6 @@ public class ExistingGenerator implements InstanceProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object create(@Nullable Class<?> type) {
|
public Object create(@Nullable Class<?> type) {
|
||||||
|
|
||||||
Object value = existingValues.get(type.getName());
|
Object value = existingValues.get(type.getName());
|
||||||
|
|
||||||
// NULL values indicate that the generator failed
|
// NULL values indicate that the generator failed
|
||||||
|
@ -57,8 +57,9 @@ public class PrimitiveGenerator implements InstanceProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object create(@Nullable Class<?> type) {
|
public Object create(@Nullable Class<?> type) {
|
||||||
|
if (type == null) {
|
||||||
if (type.isPrimitive()) {
|
return null;
|
||||||
|
} else if (type.isPrimitive()) {
|
||||||
return Defaults.defaultValue(type);
|
return Defaults.defaultValue(type);
|
||||||
} else if (Primitives.isWrapperType(type)) {
|
} else if (Primitives.isWrapperType(type)) {
|
||||||
return Defaults.defaultValue(Primitives.unwrap(type));
|
return Defaults.defaultValue(Primitives.unwrap(type));
|
||||||
|
@ -193,10 +193,11 @@ public class BukkitConverters {
|
|||||||
public Entity getSpecific(Object generic) {
|
public Entity getSpecific(Object generic) {
|
||||||
try {
|
try {
|
||||||
Integer id = (Integer) generic;
|
Integer id = (Integer) generic;
|
||||||
|
ProtocolManager manager = managerRef.get();
|
||||||
|
|
||||||
// Use the
|
// Use the
|
||||||
if (id != null && managerRef.get() != null) {
|
if (id != null && manager != null) {
|
||||||
return managerRef.get().getEntityFromID(container, id);
|
return manager.getEntityFromID(container, id);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ public class ChunkPosition {
|
|||||||
return new EquivalentConverter<ChunkPosition>() {
|
return new EquivalentConverter<ChunkPosition>() {
|
||||||
@Override
|
@Override
|
||||||
public Object getGeneric(Class<?> genericType, ChunkPosition specific) {
|
public Object getGeneric(Class<?> genericType, ChunkPosition specific) {
|
||||||
return new net.minecraft.server.ChunkPosition(specific.x, specific.z, specific.z);
|
return new net.minecraft.server.ChunkPosition(specific.x, specific.y, specific.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -61,7 +61,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the x coordinate of the underlying coordiate.
|
* Retrieve the x coordinate of the underlying coordinate.
|
||||||
* @return The x coordinate.
|
* @return The x coordinate.
|
||||||
*/
|
*/
|
||||||
public int getX() {
|
public int getX() {
|
||||||
@ -69,7 +69,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the x coordinate of the underlying coordiate.
|
* Set the x coordinate of the underlying coordinate.
|
||||||
* @param newX - the new x coordinate.
|
* @param newX - the new x coordinate.
|
||||||
*/
|
*/
|
||||||
public void setX(int newX) {
|
public void setX(int newX) {
|
||||||
@ -77,7 +77,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the y coordinate of the underlying coordiate.
|
* Retrieve the y coordinate of the underlying coordinate.
|
||||||
* @return The y coordinate.
|
* @return The y coordinate.
|
||||||
*/
|
*/
|
||||||
public int getY() {
|
public int getY() {
|
||||||
@ -85,7 +85,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the y coordinate of the underlying coordiate.
|
* Set the y coordinate of the underlying coordinate.
|
||||||
* @param newY - the new y coordinate.
|
* @param newY - the new y coordinate.
|
||||||
*/
|
*/
|
||||||
public void setY(int newY) {
|
public void setY(int newY) {
|
||||||
@ -93,7 +93,7 @@ public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the z coordinate of the underlying coordiate.
|
* Retrieve the z coordinate of the underlying coordinate.
|
||||||
* @return The z coordinate.
|
* @return The z coordinate.
|
||||||
*/
|
*/
|
||||||
public int getZ() {
|
public int getZ() {
|
||||||
|
@ -8,6 +8,7 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ public class WrappedDataWatcher {
|
|||||||
private static Method getKeyValueMethod;
|
private static Method getKeyValueMethod;
|
||||||
|
|
||||||
// Entity methods
|
// Entity methods
|
||||||
private static Field entityDataField;
|
private volatile static Field entityDataField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not this class has already been initialized.
|
* Whether or not this class has already been initialized.
|
||||||
@ -275,12 +276,13 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException If we're unable to read the underlying object.
|
* @throws FieldAccessException If we're unable to read the underlying object.
|
||||||
*/
|
*/
|
||||||
public Set<Integer> indexSet() throws FieldAccessException {
|
public Set<Integer> indexSet() throws FieldAccessException {
|
||||||
try {
|
Lock readLock = getReadWriteLock().readLock();
|
||||||
getReadWriteLock().readLock().lock();
|
readLock.lock();
|
||||||
return new HashSet<Integer>(getWatchableObjectMap().keySet());
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new HashSet<Integer>(getWatchableObjectMap().keySet());
|
||||||
} finally {
|
} finally {
|
||||||
getReadWriteLock().readLock().unlock();
|
readLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,12 +292,13 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException If we're unable to read the underlying object.
|
* @throws FieldAccessException If we're unable to read the underlying object.
|
||||||
*/
|
*/
|
||||||
public int size() throws FieldAccessException {
|
public int size() throws FieldAccessException {
|
||||||
try {
|
Lock readLock = getReadWriteLock().readLock();
|
||||||
getReadWriteLock().readLock().lock();
|
readLock.lock();
|
||||||
return getWatchableObjectMap().size();
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
return getWatchableObjectMap().size();
|
||||||
} finally {
|
} finally {
|
||||||
getReadWriteLock().readLock().unlock();
|
readLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,18 +340,18 @@ public class WrappedDataWatcher {
|
|||||||
* @throws FieldAccessException Cannot read underlying field.
|
* @throws FieldAccessException Cannot read underlying field.
|
||||||
*/
|
*/
|
||||||
private void setObjectRaw(int index, Object newValue, boolean update) throws FieldAccessException {
|
private void setObjectRaw(int index, Object newValue, boolean update) throws FieldAccessException {
|
||||||
WatchableObject watchable;
|
// Aquire write lock
|
||||||
|
Lock writeLock = getReadWriteLock().writeLock();
|
||||||
|
writeLock.lock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Aquire write lock
|
WatchableObject watchable = getWatchedObject(index);
|
||||||
getReadWriteLock().writeLock().lock();
|
|
||||||
watchable = getWatchedObject(index);
|
|
||||||
|
|
||||||
if (watchable != null) {
|
if (watchable != null) {
|
||||||
new WrappedWatchableObject(watchable).setValue(newValue, update);
|
new WrappedWatchableObject(watchable).setValue(newValue, update);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
getReadWriteLock().writeLock().unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package com.comphenix.protocol.wrappers;
|
package com.comphenix.protocol.wrappers;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
|
|
||||||
|
import net.minecraft.server.ItemStack;
|
||||||
import net.minecraft.server.WatchableObject;
|
import net.minecraft.server.WatchableObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,4 +160,35 @@ public class WrappedWatchableObject {
|
|||||||
public boolean getDirtyState() throws FieldAccessException {
|
public boolean getDirtyState() throws FieldAccessException {
|
||||||
return modifier.<Boolean>withType(boolean.class).read(0);
|
return modifier.<Boolean>withType(boolean.class).read(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone the current wrapped watchable object, along with any contained objects.
|
||||||
|
* @return A deep clone of the current watchable object.
|
||||||
|
* @throws FieldAccessException If we're unable to use reflection.
|
||||||
|
*/
|
||||||
|
public WrappedWatchableObject deepClone() throws FieldAccessException {
|
||||||
|
WrappedWatchableObject clone = new WrappedWatchableObject(DefaultInstances.DEFAULT.getDefault(WatchableObject.class));
|
||||||
|
|
||||||
|
clone.setDirtyState(getDirtyState());
|
||||||
|
clone.setIndex(getIndex());
|
||||||
|
clone.setTypeID(getTypeID());
|
||||||
|
clone.setValue(getClonedValue(), false);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper
|
||||||
|
private Object getClonedValue() throws FieldAccessException {
|
||||||
|
Object value = getValue();
|
||||||
|
|
||||||
|
// Only a limited set of references types are supported
|
||||||
|
if (value instanceof net.minecraft.server.ChunkPosition) {
|
||||||
|
EquivalentConverter<ChunkPosition> converter = ChunkPosition.getConverter();
|
||||||
|
return converter.getGeneric(net.minecraft.server.ChunkPosition.class, converter.getSpecific(value));
|
||||||
|
} else if (value instanceof ItemStack) {
|
||||||
|
return ((ItemStack) value).cloneItemStack();
|
||||||
|
} else {
|
||||||
|
// A string or primitive wrapper, which are all immutable.
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,3 +10,6 @@ global:
|
|||||||
last: 0
|
last: 0
|
||||||
|
|
||||||
metrics: true
|
metrics: true
|
||||||
|
|
||||||
|
# Automatically compile structure modifiers
|
||||||
|
background compiler: true
|
@ -1,5 +1,5 @@
|
|||||||
name: ProtocolLib
|
name: ProtocolLib
|
||||||
version: 1.6.1-SNAPSHOT
|
version: 1.7.0
|
||||||
description: Provides read/write access to the Minecraft protocol.
|
description: Provides read/write access to the Minecraft protocol.
|
||||||
author: Comphenix
|
author: Comphenix
|
||||||
website: http://www.comphenix.net/ProtocolLib
|
website: http://www.comphenix.net/ProtocolLib
|
||||||
|
@ -58,7 +58,7 @@ public class SortedCopyOnWriteArrayTest {
|
|||||||
assertEquals(3, test.get(1).id);
|
assertEquals(3, test.get(1).id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PriorityStuff implements Comparable<PriorityStuff> {
|
private static class PriorityStuff implements Comparable<PriorityStuff> {
|
||||||
public ListenerPriority priority;
|
public ListenerPriority priority;
|
||||||
public int id;
|
public int id;
|
||||||
|
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren