3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-06 00:00:47 +01:00

Merge remote-tracking branch 'origin/dev/1.1.0' into dev/1.1.0

# Conflicts:
#	proxy/src/test/java/com/velocitypowered/proxy/plugin/util/PluginDependencyUtilsTest.java
Dieser Commit ist enthalten in:
Andrew Steinborn 2020-02-17 16:33:34 -05:00
Commit 599adc6d2c
13 geänderte Dateien mit 223 neuen und 65 gelöschten Zeilen

Datei anzeigen

@ -114,7 +114,7 @@ public final class ServerPing {
if (players != null) {
builder.onlinePlayers = players.online;
builder.maximumPlayers = players.max;
builder.samplePlayers.addAll(players.sample);
builder.samplePlayers.addAll(players.getSample());
} else {
builder.nullOutPlayers = true;
}
@ -367,7 +367,7 @@ public final class ServerPing {
}
public List<SamplePlayer> getSample() {
return sample;
return sample == null ? ImmutableList.of() : sample;
}
@Override

Datei anzeigen

@ -35,7 +35,7 @@ import org.asynchttpclient.Response;
public class Metrics {
// The version of this bStats class
private static final int B_STATS_VERSION = 1;
private static final int B_STATS_METRICS_REVISION = 2;
// The url to which the data is sent
private static final String URL = "https://bstats.org/submitData/server-implementation";
@ -49,6 +49,9 @@ public class Metrics {
// The name of the server software
private final String name;
// The plugin ID for the server software as assigned by bStats.
private final int pluginId;
// The uuid of the server
private final String serverUuid;
@ -60,13 +63,15 @@ public class Metrics {
/**
* Class constructor.
* @param name The name of the server software.
* @param pluginId The plugin ID for the server software as assigned by bStats.
* @param serverUuid The uuid of the server.
* @param logFailedRequests Whether failed requests should be logged or not.
* @param server The Velocity server instance.
*/
private Metrics(String name, String serverUuid, boolean logFailedRequests,
private Metrics(String name, int pluginId, String serverUuid, boolean logFailedRequests,
VelocityServer server) {
this.name = name;
this.pluginId = pluginId;
this.serverUuid = serverUuid;
Metrics.logFailedRequests = logFailedRequests;
this.server = server;
@ -114,6 +119,8 @@ public class Metrics {
JsonObject data = new JsonObject();
data.addProperty("pluginName", name); // Append the name of the server software
data.addProperty("id", pluginId);
data.addProperty("metricsRevision", B_STATS_METRICS_REVISION);
JsonArray customCharts = new JsonArray();
for (CustomChart customChart : charts) {
// Add the data of the custom charts
@ -573,7 +580,7 @@ public class Metrics {
boolean logFailedRequests = metricsConfig.isLogFailure();
// Only start Metrics, if it's enabled in the config
if (metricsConfig.isEnabled()) {
Metrics metrics = new Metrics("Velocity", serverUuid, logFailedRequests, server);
Metrics metrics = new Metrics("Velocity", 4752, serverUuid, logFailedRequests, server);
metrics.addCustomChart(
new Metrics.SingleLineChart("players", server::getPlayerCount)

Datei anzeigen

@ -360,11 +360,12 @@ public class VelocityServer implements ProxyServer {
}
/**
* Shuts down the proxy.
* Shuts down the proxy, kicking players with the specified {@param reason}.
*
* @param explicitExit whether the user explicitly shut down the proxy
* @param reason message to kick online players with
*/
public void shutdown(boolean explicitExit) {
public void shutdown(boolean explicitExit, TextComponent reason) {
if (eventManager == null || pluginManager == null || cm == null || scheduler == null) {
throw new AssertionError();
}
@ -382,7 +383,7 @@ public class VelocityServer implements ProxyServer {
ImmutableList<ConnectedPlayer> players = ImmutableList.copyOf(connectionsByUuid.values());
for (ConnectedPlayer player : players) {
player.disconnect(TextComponent.of("Proxy shutting down."));
player.disconnect(reason);
}
try {
@ -425,6 +426,15 @@ public class VelocityServer implements ProxyServer {
thread.start();
}
/**
* Calls {@link #shutdown(boolean, TextComponent)} with the default reason "Proxy shutting down."
*
* @param explicitExit whether the user explicitly shut down the proxy
*/
public void shutdown(boolean explicitExit) {
shutdown(explicitExit, TextComponent.of("Proxy shutting down."));
}
public AsyncHttpClient getAsyncHttpClient() {
return ensureInitialized(cm).getHttpClient();
}

Datei anzeigen

@ -5,6 +5,7 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.proxy.VelocityServer;
import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.NonNull;
public class ShutdownCommand implements Command {
@ -17,7 +18,12 @@ public class ShutdownCommand implements Command {
@Override
public void execute(CommandSource source, String @NonNull [] args) {
if (args.length == 0) {
server.shutdown(true);
} else {
String reason = String.join(" ", args);
server.shutdown(true, LegacyComponentSerializer.legacy().deserialize(reason, '&'));
}
}
@Override

Datei anzeigen

@ -256,7 +256,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null && serverConnection.getPhase().consideredComplete()) {
if (smc != null && !smc.isClosed() && serverConnection.getPhase().consideredComplete()) {
smc.write(buf.retain());
}
}

Datei anzeigen

@ -4,11 +4,19 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Joiner;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
import com.google.inject.name.Names;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.plugin.meta.PluginDependency;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription;
import com.velocitypowered.proxy.plugin.loader.java.JavaPluginLoader;
import com.velocitypowered.proxy.plugin.util.PluginDependencyUtils;
import java.io.IOException;
@ -18,12 +26,13 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -61,7 +70,7 @@ public class VelocityPluginManager implements PluginManager {
.newDirectoryStream(directory, p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) {
for (Path path : stream) {
try {
found.add(loader.loadPlugin(path));
found.add(loader.loadPluginDescription(path));
} catch (Exception e) {
logger.error("Unable to load plugin {}", path, e);
}
@ -73,38 +82,62 @@ public class VelocityPluginManager implements PluginManager {
return;
}
// Sort the loaded plugins twice. First, sort the already-loaded plugins by their IDs, so as
// to make the topographic sort deterministic (since the order will differ depending on the
// first node chosen in the graph, which is the first plugin we found). Afterwards, we execute
// a depth-first search over the loaded plugins.
found.sort(Comparator.comparing(PluginDescription::getId));
List<PluginDescription> sortedPlugins = PluginDependencyUtils.sortCandidates(found);
Set<String> loadedPluginsById = new HashSet<>();
Map<PluginContainer, Module> pluginContainers = new HashMap<>();
// Now load the plugins
pluginLoad:
for (PluginDescription plugin : sortedPlugins) {
for (PluginDescription candidate : sortedPlugins) {
// Verify dependencies
for (PluginDependency dependency : plugin.getDependencies()) {
if (!dependency.isOptional() && !isLoaded(dependency.getId())) {
logger.error("Can't load plugin {} due to missing dependency {}", plugin.getId(),
for (PluginDependency dependency : candidate.getDependencies()) {
if (!dependency.isOptional() && !loadedPluginsById.contains(dependency.getId())) {
logger.error("Can't load plugin {} due to missing dependency {}", candidate.getId(),
dependency.getId());
continue pluginLoad;
}
}
// Actually create the plugin
PluginContainer pluginObject;
try {
PluginDescription realPlugin = loader.loadPlugin(candidate);
VelocityPluginContainer container = new VelocityPluginContainer(realPlugin);
pluginContainers.put(container, loader.createModule(container));
loadedPluginsById.add(realPlugin.getId());
} catch (Exception e) {
logger.error("Can't create module for plugin {}", candidate.getId(), e);
}
}
// Make a global Guice module that with common bindings for every plugin
AbstractModule commonModule = new AbstractModule() {
@Override
protected void configure() {
bind(ProxyServer.class).toInstance(server);
bind(PluginManager.class).toInstance(server.getPluginManager());
bind(EventManager.class).toInstance(server.getEventManager());
bind(CommandManager.class).toInstance(server.getCommandManager());
for (PluginContainer container : pluginContainers.keySet()) {
bind(PluginContainer.class)
.annotatedWith(Names.named(container.getDescription().getId()))
.toInstance(container);
}
}
};
for (Map.Entry<PluginContainer, Module> plugin : pluginContainers.entrySet()) {
PluginContainer container = plugin.getKey();
PluginDescription description = container.getDescription();
try {
pluginObject = loader.createPlugin(plugin);
loader.createPlugin(container, plugin.getValue(), commonModule);
} catch (Exception e) {
logger.error("Can't create plugin {}", plugin.getId(), e);
logger.error("Can't create plugin {}", description.getId(), e);
continue;
}
logger.info("Loaded plugin {} {} by {}", plugin.getId(), plugin.getVersion()
.orElse("<UNKNOWN>"), Joiner.on(", ").join(plugin.getAuthors()));
registerPlugin(pluginObject);
logger.info("Loaded plugin {} {} by {}", description.getId(), description.getVersion()
.orElse("<UNKNOWN>"), Joiner.on(", ").join(description.getAuthors()));
registerPlugin(container);
}
}

Datei anzeigen

@ -1,7 +1,9 @@
package com.velocitypowered.proxy.plugin.loader;
import com.google.inject.Module;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import java.nio.file.Path;
/**
@ -9,7 +11,36 @@ import java.nio.file.Path;
*/
public interface PluginLoader {
PluginDescription loadPlugin(Path source) throws Exception;
PluginDescription loadPluginDescription(Path source) throws Exception;
PluginContainer createPlugin(PluginDescription plugin) throws Exception;
PluginDescription loadPlugin(PluginDescription source) throws Exception;
/**
* Creates a {@link Module} for the provided {@link PluginContainer}
* and verifies that the container's {@link PluginDescription} is correct.
*
* <p>Does not create an instance of the plugin.</p>
*
* @param container the plugin container
* @return the module containing bindings specific to this plugin
* @throws IllegalArgumentException If the {@link PluginDescription}
* is missing the path
*/
Module createModule(PluginContainer container) throws Exception;
/**
* Creates an instance of the plugin as specified by the
* plugin's main class found in the {@link PluginDescription}.
*
* <p>The provided {@link Module modules} are used to create an
* injector which is then used to create the plugin instance.</p>
*
* <p>The plugin instance is set in the provided {@link PluginContainer}.</p>
*
* @param container the plugin container
* @param modules the modules to be used when creating this plugin's injector
* @throws IllegalStateException If the plugin instance could not be
* created from the provided modules
*/
void createPlugin(PluginContainer container, Module... modules) throws Exception;
}

Datei anzeigen

@ -7,11 +7,10 @@ import java.util.Optional;
public class VelocityPluginContainer implements PluginContainer {
private final PluginDescription description;
private final Object instance;
private Object instance;
public VelocityPluginContainer(PluginDescription description, Object instance) {
public VelocityPluginContainer(PluginDescription description) {
this.description = description;
this.instance = instance;
}
@Override
@ -23,4 +22,8 @@ public class VelocityPluginContainer implements PluginContainer {
public Optional<?> getInstance() {
return Optional.ofNullable(instance);
}
public void setInstance(Object instance) {
this.instance = instance;
}
}

Datei anzeigen

@ -2,6 +2,7 @@ package com.velocitypowered.proxy.plugin.loader.java;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.velocitypowered.api.plugin.InvalidPluginException;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
@ -13,8 +14,6 @@ import com.velocitypowered.proxy.plugin.PluginClassLoader;
import com.velocitypowered.proxy.plugin.loader.PluginLoader;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription;
import com.velocitypowered.proxy.plugin.loader.java.JavaVelocityPluginDescription;
import com.velocitypowered.proxy.plugin.loader.java.VelocityPluginModule;
import java.io.BufferedInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
@ -39,7 +38,7 @@ public class JavaPluginLoader implements PluginLoader {
}
@Override
public PluginDescription loadPlugin(Path source) throws Exception {
public PluginDescription loadPluginDescription(Path source) throws Exception {
Optional<SerializedPluginDescription> serialized = getSerializedPluginInfo(source);
if (!serialized.isPresent()) {
@ -51,17 +50,28 @@ public class JavaPluginLoader implements PluginLoader {
throw new InvalidPluginException("Plugin ID '" + pd.getId() + "' is invalid.");
}
PluginClassLoader loader = new PluginClassLoader(
new URL[]{source.toUri().toURL()}
);
loader.addToClassloaders();
Class mainClass = loader.loadClass(pd.getMain());
return createDescription(pd, source, mainClass);
return createCandidateDescription(pd, source);
}
@Override
public PluginContainer createPlugin(PluginDescription description) throws Exception {
public PluginDescription loadPlugin(PluginDescription source) throws Exception {
if (!(source instanceof JavaVelocityPluginDescriptionCandidate)) {
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
}
PluginClassLoader loader = new PluginClassLoader(
new URL[]{source.getSource().get().toUri().toURL()}
);
loader.addToClassloaders();
JavaVelocityPluginDescriptionCandidate candidate =
(JavaVelocityPluginDescriptionCandidate) source;
Class mainClass = loader.loadClass(candidate.getMainClass());
return createDescription(candidate, mainClass);
}
@Override
public Module createModule(PluginContainer container) throws Exception {
PluginDescription description = container.getDescription();
if (!(description instanceof JavaVelocityPluginDescription)) {
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
}
@ -73,16 +83,29 @@ public class JavaPluginLoader implements PluginLoader {
throw new IllegalArgumentException("No path in plugin description");
}
Injector injector = Guice
.createInjector(new VelocityPluginModule(server, javaDescription, baseDirectory));
Object instance = injector.getInstance(javaDescription.getMainClass());
return new VelocityPluginModule(server, javaDescription, container, baseDirectory);
}
@Override
public void createPlugin(PluginContainer container, Module... modules) {
if (!(container instanceof VelocityPluginContainer)) {
throw new IllegalArgumentException("Container provided isn't of the Java plugin loader");
}
PluginDescription description = container.getDescription();
if (!(description instanceof JavaVelocityPluginDescription)) {
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
}
Injector injector = Guice.createInjector(modules);
Object instance = injector
.getInstance(((JavaVelocityPluginDescription) description).getMainClass());
if (instance == null) {
throw new IllegalStateException(
"Got nothing from injector for plugin " + javaDescription.getId());
"Got nothing from injector for plugin " + description.getId());
}
return new VelocityPluginContainer(description, instance);
((VelocityPluginContainer) container).setInstance(instance);
}
private Optional<SerializedPluginDescription> getSerializedPluginInfo(Path source)
@ -114,15 +137,16 @@ public class JavaPluginLoader implements PluginLoader {
}
}
private VelocityPluginDescription createDescription(SerializedPluginDescription description,
Path source, Class mainClass) {
private VelocityPluginDescription createCandidateDescription(
SerializedPluginDescription description,
Path source) {
Set<PluginDependency> dependencies = new HashSet<>();
for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) {
dependencies.add(toDependencyMeta(dependency));
}
return new JavaVelocityPluginDescription(
return new JavaVelocityPluginDescriptionCandidate(
description.getId(),
description.getName(),
description.getVersion(),
@ -131,6 +155,22 @@ public class JavaPluginLoader implements PluginLoader {
description.getAuthors(),
dependencies,
source,
description.getMain()
);
}
private VelocityPluginDescription createDescription(
JavaVelocityPluginDescriptionCandidate description,
Class mainClass) {
return new JavaVelocityPluginDescription(
description.getId(),
description.getName().orElse(null),
description.getVersion().orElse(null),
description.getDescription().orElse(null),
description.getUrl().orElse(null),
description.getAuthors(),
description.getDependencies(),
description.getSource().orElse(null),
mainClass
);
}

Datei anzeigen

@ -0,0 +1,27 @@
package com.velocitypowered.proxy.plugin.loader.java;
import static com.google.common.base.Preconditions.checkNotNull;
import com.velocitypowered.api.plugin.meta.PluginDependency;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
class JavaVelocityPluginDescriptionCandidate extends VelocityPluginDescription {
private final String mainClass;
JavaVelocityPluginDescriptionCandidate(String id, @Nullable String name, @Nullable String version,
@Nullable String description, @Nullable String url,
@Nullable List<String> authors, Collection<PluginDependency> dependencies, Path source,
String mainClass) {
super(id, name, version, description, url, authors, dependencies, source);
this.mainClass = checkNotNull(mainClass);
}
String getMainClass() {
return mainClass;
}
}

Datei anzeigen

@ -3,10 +3,8 @@ package com.velocitypowered.proxy.plugin.loader.java;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import java.nio.file.Path;
@ -17,12 +15,14 @@ class VelocityPluginModule implements Module {
private final ProxyServer server;
private final JavaVelocityPluginDescription description;
private final PluginContainer pluginContainer;
private final Path basePluginPath;
VelocityPluginModule(ProxyServer server, JavaVelocityPluginDescription description,
Path basePluginPath) {
PluginContainer pluginContainer, Path basePluginPath) {
this.server = server;
this.description = description;
this.pluginContainer = pluginContainer;
this.basePluginPath = basePluginPath;
}
@ -31,12 +31,9 @@ class VelocityPluginModule implements Module {
binder.bind(description.getMainClass()).in(Scopes.SINGLETON);
binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId()));
binder.bind(ProxyServer.class).toInstance(server);
binder.bind(Path.class).annotatedWith(DataDirectory.class)
.toInstance(basePluginPath.resolve(description.getId()));
binder.bind(PluginDescription.class).toInstance(description);
binder.bind(PluginManager.class).toInstance(server.getPluginManager());
binder.bind(EventManager.class).toInstance(server.getEventManager());
binder.bind(CommandManager.class).toInstance(server.getCommandManager());
binder.bind(PluginContainer.class).toInstance(pluginContainer);
}
}

Datei anzeigen

@ -8,6 +8,7 @@ import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.meta.PluginDependency;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
@ -28,17 +29,20 @@ public class PluginDependencyUtils {
* @throws IllegalStateException if there is a circular loop in the dependency graph
*/
public static List<PluginDescription> sortCandidates(List<PluginDescription> candidates) {
List<PluginDescription> sortedCandidates = new ArrayList<>(candidates);
sortedCandidates.sort(Comparator.comparing(PluginDescription::getId));
// Create a graph and populate it with plugin dependencies. Specifically, each graph has plugin
// nodes, and edges that represent the dependencies that plugin relies on. Non-existent plugins
// are ignored.
MutableGraph<PluginDescription> graph = GraphBuilder.directed()
.allowsSelfLoops(false)
.expectedNodeCount(candidates.size())
.expectedNodeCount(sortedCandidates.size())
.build();
Map<String, PluginDescription> candidateMap = Maps.uniqueIndex(candidates,
Map<String, PluginDescription> candidateMap = Maps.uniqueIndex(sortedCandidates,
PluginDescription::getId);
for (PluginDescription description : candidates) {
for (PluginDescription description : sortedCandidates) {
graph.addNode(description);
for (PluginDependency dependency : description.getDependencies()) {

Datei anzeigen

@ -69,8 +69,8 @@ class PluginDependencyUtilsTest {
void sortCandidatesMultiplePluginsDependentOnOne() throws Exception {
List<PluginDescription> plugins = ImmutableList.of(HAS_DEPENDENCY_3, HAS_DEPENDENCY_1,
NO_DEPENDENCY);
List<PluginDescription> expected = ImmutableList.of(NO_DEPENDENCY, HAS_DEPENDENCY_3,
HAS_DEPENDENCY_1);
List<PluginDescription> expected = ImmutableList.of(NO_DEPENDENCY, HAS_DEPENDENCY_1,
HAS_DEPENDENCY_3);
assertEquals(expected, PluginDependencyUtils.sortCandidates(plugins));
}