mirror of
https://github.com/Minestom/Minestom.git
synced 2025-03-02 11:21:15 +01:00
Let users change extension jar after unload, then load it again
This commit is contained in:
parent
925f5fa614
commit
d83bec4732
@ -81,6 +81,16 @@ public final class CommandManager {
|
||||
this.dispatcher.register(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a command from the currently registered commands.
|
||||
* Does nothing if the command was not registered before
|
||||
*
|
||||
* @param command the command to remove
|
||||
*/
|
||||
public void unregister(@NotNull Command command) {
|
||||
this.dispatcher.unregister(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Command} registered by {@link #register(Command)}.
|
||||
*
|
||||
|
@ -23,6 +23,14 @@ public class CommandDispatcher {
|
||||
this.commands.add(command);
|
||||
}
|
||||
|
||||
public void unregister(Command command) {
|
||||
commandMap.remove(command.getName().toLowerCase());
|
||||
for(String alias : command.getAliases()) {
|
||||
this.commandMap.remove(alias.toLowerCase());
|
||||
}
|
||||
commands.remove(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given command
|
||||
*
|
||||
|
@ -12,10 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongepowered.asm.mixin.Mixins;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@ -495,6 +492,12 @@ public class ExtensionManager {
|
||||
|
||||
// remove class loader, required to reload the classes
|
||||
MinestomExtensionClassLoader classloader = extensionLoaders.remove(id);
|
||||
try {
|
||||
// close resources
|
||||
classloader.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
MinestomRootClassLoader.getInstance().removeChildInHierarchy(classloader);
|
||||
}
|
||||
|
||||
@ -530,6 +533,8 @@ public class ExtensionManager {
|
||||
log.info("Unloading extension {}", extensionName);
|
||||
unload(ext);
|
||||
|
||||
System.gc();
|
||||
|
||||
// ext and its dependents should no longer be referenced from now on
|
||||
|
||||
// rediscover extension to reload. We allow dependency changes, so we need to fully reload it
|
||||
@ -545,32 +550,68 @@ public class ExtensionManager {
|
||||
}
|
||||
|
||||
// ensure correct order of dependencies
|
||||
log.debug("Reorder extensions to reload to ensure proper load order");
|
||||
extensionsToReload = generateLoadOrder(extensionsToReload);
|
||||
loadDependencies(extensionsToReload);
|
||||
loadExtensionList(extensionsToReload);
|
||||
}
|
||||
|
||||
public void loadDynamicExtension(File jarFile) throws FileNotFoundException {
|
||||
if(!jarFile.exists()) {
|
||||
throw new FileNotFoundException("File '"+jarFile.getAbsolutePath()+"' does not exists. Cannot load extension.");
|
||||
}
|
||||
|
||||
log.info("Discover dynamic extension from jar {}", jarFile.getAbsolutePath());
|
||||
DiscoveredExtension discoveredExtension = discoverFromJar(jarFile);
|
||||
List<DiscoveredExtension> extensionsToLoad = Collections.singletonList(discoveredExtension);
|
||||
loadExtensionList(extensionsToLoad);
|
||||
}
|
||||
|
||||
private void loadExtensionList(List<DiscoveredExtension> extensionsToLoad) {
|
||||
// ensure correct order of dependencies
|
||||
log.debug("Reorder extensions to ensure proper load order");
|
||||
extensionsToLoad = generateLoadOrder(extensionsToLoad);
|
||||
loadDependencies(extensionsToLoad);
|
||||
|
||||
// setup new classloaders for the extensions to reload
|
||||
for(DiscoveredExtension toReload : extensionsToReload) {
|
||||
for (DiscoveredExtension toReload : extensionsToLoad) {
|
||||
log.debug("Setting up classloader for extension {}", toReload.getName());
|
||||
setupClassLoader(toReload);
|
||||
}
|
||||
|
||||
// setup code modifiers for these extensions
|
||||
// TODO: it is possible the new modifiers cannot be applied (because the targeted classes are already loaded), should we issue a warning?
|
||||
setupCodeModifiers(extensionsToReload);
|
||||
setupCodeModifiers(extensionsToLoad);
|
||||
|
||||
List<Extension> newExtensions = new LinkedList<>();
|
||||
for(DiscoveredExtension toReload : extensionsToReload) {
|
||||
for (DiscoveredExtension toReload : extensionsToLoad) {
|
||||
// reload extensions
|
||||
log.info("Actually load extension {}", toReload.getName());
|
||||
Extension loadedExtension = attemptSingleLoad(toReload);
|
||||
newExtensions.add(loadedExtension);
|
||||
}
|
||||
|
||||
log.info("Reload complete, refiring preinit, init and then postinit callbacks");
|
||||
log.info("Load complete, firing preinit, init and then postinit callbacks");
|
||||
// retrigger preinit, init and postinit
|
||||
newExtensions.forEach(Extension::preInitialize);
|
||||
newExtensions.forEach(Extension::initialize);
|
||||
newExtensions.forEach(Extension::postInitialize);
|
||||
}
|
||||
|
||||
public void unloadExtension(String extensionName) {
|
||||
Extension ext = extensions.get(extensionName.toLowerCase());
|
||||
if(ext == null) {
|
||||
throw new IllegalArgumentException("Extension "+extensionName+" is not currently loaded.");
|
||||
}
|
||||
List<String> dependents = new LinkedList<>(ext.getDescription().getDependents()); // copy dependents list
|
||||
|
||||
for(String dependentID : dependents) {
|
||||
Extension dependentExt = extensions.get(dependentID.toLowerCase());
|
||||
log.info("Unloading dependent extension {} (because it depends on {})", dependentID, extensionName);
|
||||
unload(dependentExt);
|
||||
}
|
||||
|
||||
log.info("Unloading extension {}", extensionName);
|
||||
unload(ext);
|
||||
|
||||
// call GC to try to get rid of classes and classloader
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
|
@ -67,4 +67,10 @@ public class MinestomExtensionClassLoader extends HierarchyClassLoader {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
System.err.println("Class loader "+getName()+" finalized.");
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ public class Main {
|
||||
commandManager.register(new ShutdownCommand());
|
||||
commandManager.register(new TeleportCommand());
|
||||
commandManager.register(new ReloadExtensionCommand());
|
||||
commandManager.register(new UnloadExtensionCommand());
|
||||
commandManager.register(new LoadExtensionCommand());
|
||||
|
||||
|
||||
StorageManager storageManager = MinecraftServer.getStorageManager();
|
||||
|
70
src/test/java/demo/commands/LoadExtensionCommand.java
Normal file
70
src/test/java/demo/commands/LoadExtensionCommand.java
Normal file
@ -0,0 +1,70 @@
|
||||
package demo.commands;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.Arguments;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import net.minestom.server.extensions.Extension;
|
||||
import net.minestom.server.extensions.ExtensionManager;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class LoadExtensionCommand extends Command {
|
||||
public LoadExtensionCommand() {
|
||||
super("load");
|
||||
|
||||
setDefaultExecutor(this::usage);
|
||||
|
||||
Argument extension = ArgumentType.DynamicStringArray("extensionName");
|
||||
|
||||
setArgumentCallback(this::gameModeCallback, extension);
|
||||
|
||||
addSyntax(this::execute, extension);
|
||||
}
|
||||
|
||||
private void usage(CommandSender sender, Arguments arguments) {
|
||||
sender.sendMessage("Usage: /load <extension file name>");
|
||||
}
|
||||
|
||||
private void execute(CommandSender sender, Arguments arguments) {
|
||||
String name = join(arguments.getStringArray("extensionName"));
|
||||
sender.sendMessage("extensionFile = "+name+"....");
|
||||
|
||||
ExtensionManager extensionManager = MinecraftServer.getExtensionManager();
|
||||
Path extensionFolder = extensionManager.getExtensionFolder().toPath().toAbsolutePath();
|
||||
Path extensionJar = extensionFolder.resolve(name);
|
||||
if(!extensionJar.toAbsolutePath().startsWith(extensionFolder)) {
|
||||
sender.sendMessage("File name '"+name+"' does not represent a file inside the extensions folder. Will not load");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
extensionManager.loadDynamicExtension(extensionJar.toFile());
|
||||
sender.sendMessage("Extension loaded!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
sender.sendMessage("Failed to load extension: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void gameModeCallback(CommandSender sender, String extension, int error) {
|
||||
sender.sendMessage("'" + extension + "' is not a valid extension name!");
|
||||
}
|
||||
|
||||
private String join(String[] extensionNameParts) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (int i = 0; i < extensionNameParts.length; i++) {
|
||||
String s = extensionNameParts[i];
|
||||
if(i != 0) {
|
||||
b.append(" ");
|
||||
}
|
||||
b.append(s);
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
}
|
76
src/test/java/demo/commands/UnloadExtensionCommand.java
Normal file
76
src/test/java/demo/commands/UnloadExtensionCommand.java
Normal file
@ -0,0 +1,76 @@
|
||||
package demo.commands;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.command.builder.Arguments;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import net.minestom.server.extensions.Extension;
|
||||
import net.minestom.server.extensions.ExtensionManager;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class UnloadExtensionCommand extends Command {
|
||||
public UnloadExtensionCommand() {
|
||||
super("unload");
|
||||
|
||||
setDefaultExecutor(this::usage);
|
||||
|
||||
Argument extension = ArgumentType.DynamicStringArray("extensionName");
|
||||
|
||||
setArgumentCallback(this::gameModeCallback, extension);
|
||||
|
||||
addSyntax(this::execute, extension);
|
||||
}
|
||||
|
||||
private void usage(CommandSender sender, Arguments arguments) {
|
||||
sender.sendMessage("Usage: /unload <extension name>");
|
||||
}
|
||||
|
||||
private void execute(CommandSender sender, Arguments arguments) {
|
||||
String name = join(arguments.getStringArray("extensionName"));
|
||||
sender.sendMessage("extensionName = "+name+"....");
|
||||
|
||||
ExtensionManager extensionManager = MinecraftServer.getExtensionManager();
|
||||
Extension ext = extensionManager.getExtension(name);
|
||||
if(ext != null) {
|
||||
try {
|
||||
extensionManager.unloadExtension(name);
|
||||
} catch (Throwable t) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
t.printStackTrace();
|
||||
t.printStackTrace(new PrintStream(baos));
|
||||
baos.flush();
|
||||
baos.close();
|
||||
String contents = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||
contents.lines().forEach(sender::sendMessage);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sender.sendMessage("Extension '"+name+"' does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
private void gameModeCallback(CommandSender sender, String extension, int error) {
|
||||
sender.sendMessage("'" + extension + "' is not a valid extension name!");
|
||||
}
|
||||
|
||||
private String join(String[] extensionNameParts) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (int i = 0; i < extensionNameParts.length; i++) {
|
||||
String s = extensionNameParts[i];
|
||||
if(i != 0) {
|
||||
b.append(" ");
|
||||
}
|
||||
b.append(s);
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user