From 9b9565dbbdb23046728d63fccb3564a77de6e85f Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Tue, 2 Feb 2021 12:44:36 +0100 Subject: [PATCH] Early loading of Mixin and code modifiers + System property to disable early loading if necessary --- .../java/net/minestom/server/Bootstrap.java | 3 ++ .../server/extensions/ExtensionManager.java | 37 ++++++++++++++++++- .../MinestomRootClassLoader.java | 9 +++++ .../improveextensions/DisableEarlyLoad.java | 34 +++++++++++++++++ .../MixinIntoMinestomCore.java | 37 +++++++++++++++++++ .../MixinIntoMinestomCoreLauncher.java | 15 ++++++++ .../mixins/InstanceContainerMixin.java | 19 ++++++++++ .../disableearlyload/extension.json | 6 +++ .../disableearlyload/mixins.minestomcore.json | 10 +++++ .../improveextensions/extension.json | 6 +++ .../mixins.minestomcore.json | 10 +++++ 11 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/test/java/improveextensions/DisableEarlyLoad.java create mode 100644 src/test/java/improveextensions/MixinIntoMinestomCore.java create mode 100644 src/test/java/improveextensions/MixinIntoMinestomCoreLauncher.java create mode 100644 src/test/java/improveextensions/mixins/InstanceContainerMixin.java create mode 100644 src/test/resources/improveextensions/disableearlyload/extension.json create mode 100644 src/test/resources/improveextensions/disableearlyload/mixins.minestomcore.json create mode 100644 src/test/resources/improveextensions/extension.json create mode 100644 src/test/resources/improveextensions/mixins.minestomcore.json diff --git a/src/main/java/net/minestom/server/Bootstrap.java b/src/main/java/net/minestom/server/Bootstrap.java index 5939923f7..3e5b26455 100644 --- a/src/main/java/net/minestom/server/Bootstrap.java +++ b/src/main/java/net/minestom/server/Bootstrap.java @@ -1,5 +1,6 @@ package net.minestom.server; +import net.minestom.server.extensions.ExtensionManager; import net.minestom.server.extras.selfmodification.MinestomRootClassLoader; import net.minestom.server.extras.selfmodification.mixins.MixinCodeModifier; import net.minestom.server.extras.selfmodification.mixins.MixinServiceMinestom; @@ -22,6 +23,8 @@ public final class Bootstrap { startMixin(args); MinestomRootClassLoader.getInstance().addCodeModifier(new MixinCodeModifier()); + ExtensionManager.loadCodeModifiersEarly(); + MixinServiceMinestom.gotoPreinitPhase(); // ensure extensions are loaded when starting the server Class serverClass = classLoader.loadClass("net.minestom.server.MinecraftServer"); diff --git a/src/main/java/net/minestom/server/extensions/ExtensionManager.java b/src/main/java/net/minestom/server/extensions/ExtensionManager.java index e7f81be86..edfdfec97 100644 --- a/src/main/java/net/minestom/server/extensions/ExtensionManager.java +++ b/src/main/java/net/minestom/server/extensions/ExtensionManager.java @@ -27,6 +27,8 @@ import java.util.zip.ZipFile; public class ExtensionManager { + public final static String DISABLE_EARLY_LOAD_SYSTEM_KEY = "minestom.extension.disable_early_load"; + public final static Logger LOGGER = LoggerFactory.getLogger(ExtensionManager.class); private final static String INDEV_CLASSES_FOLDER = "minestom.extension.indevfolder.classes"; @@ -469,7 +471,7 @@ public class ExtensionManager { } @NotNull - public Map getExtensionLoaders() { + public Map getExtensionLoaders() { return new HashMap<>(extensionLoaders); } @@ -485,6 +487,10 @@ public class ExtensionManager { return; } MinestomRootClassLoader modifiableClassLoader = (MinestomRootClassLoader) cl; + setupCodeModifiers(extensions, modifiableClassLoader); + } + + private void setupCodeModifiers(@NotNull List extensions, MinestomRootClassLoader modifiableClassLoader) { LOGGER.info("Start loading code modifiers..."); for (DiscoveredExtension extension : extensions) { try { @@ -664,4 +670,33 @@ public class ExtensionManager { public void shutdown() { this.extensionList.forEach(this::unload); } + + /** + * Loads code modifiers early, that is before MinecraftServer.init() is called. + */ + public static void loadCodeModifiersEarly() { + // allow users to disable early code modifier load + if("true".equalsIgnoreCase(System.getProperty(DISABLE_EARLY_LOAD_SYSTEM_KEY))) { + return; + } + LOGGER.info("Early load of code modifiers from extensions."); + ExtensionManager manager = new ExtensionManager(); + + // discover extensions that are present + List discovered = manager.discoverExtensions(); + + // setup extension class loaders, so that Mixin can load the json configuration file correctly + for(DiscoveredExtension e : discovered) { + manager.setupClassLoader(e); + } + + // setup code modifiers and mixins + manager.setupCodeModifiers(discovered, MinestomRootClassLoader.getInstance()); + + // setup is done, remove all extension classloaders + for(MinestomExtensionClassLoader extensionLoader : manager.getExtensionLoaders().values()) { + MinestomRootClassLoader.getInstance().removeChildInHierarchy(extensionLoader); + } + LOGGER.info("Early load of code modifiers from extensions done!"); + } } diff --git a/src/main/java/net/minestom/server/extras/selfmodification/MinestomRootClassLoader.java b/src/main/java/net/minestom/server/extras/selfmodification/MinestomRootClassLoader.java index bcb9d4232..c80df5528 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/MinestomRootClassLoader.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/MinestomRootClassLoader.java @@ -65,6 +65,11 @@ public class MinestomRootClassLoader extends HierarchyClassLoader { // TODO: priorities? private final List modifiers = new LinkedList<>(); + /** + * List of already loaded code modifier class names. This prevents loading the same class twice. + */ + private final Set alreadyLoadedCodeModifiers = new HashSet<>(); + private MinestomRootClassLoader(ClassLoader parent) { super("Minestom Root ClassLoader", extractURLsFromClasspath(), parent); asmClassLoader = newChild(new URL[0]); @@ -246,6 +251,9 @@ public class MinestomRootClassLoader extends HierarchyClassLoader { } public void loadModifier(File[] originFiles, String codeModifierClass) { + if(alreadyLoadedCodeModifiers.contains(codeModifierClass)) { + return; + } URL[] urls = new URL[originFiles.length]; try { for (int i = 0; i < originFiles.length; i++) { @@ -258,6 +266,7 @@ public class MinestomRootClassLoader extends HierarchyClassLoader { synchronized (modifiers) { LOGGER.warn("Added Code modifier: {}", modifier); addCodeModifier(modifier); + alreadyLoadedCodeModifiers.add(codeModifierClass); } } } catch (MalformedURLException | ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { diff --git a/src/test/java/improveextensions/DisableEarlyLoad.java b/src/test/java/improveextensions/DisableEarlyLoad.java new file mode 100644 index 000000000..a36ad8182 --- /dev/null +++ b/src/test/java/improveextensions/DisableEarlyLoad.java @@ -0,0 +1,34 @@ +package improveextensions; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.extensions.Extension; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.world.DimensionType; +import org.junit.jupiter.api.Assertions; +import org.opentest4j.AssertionFailedError; + +import java.util.UUID; + +/** + * Extensions should be able to use Mixins for classes loaded very early by Minestom (InstanceContainer for instance) + */ +public class DisableEarlyLoad extends Extension { + + @Override + public void initialize() { + // force load of InstanceContainer class + InstanceContainer c = new InstanceContainer(UUID.randomUUID(), DimensionType.OVERWORLD, null); + System.out.println(c.toString()); + try { + Assertions.assertFalse(MixinIntoMinestomCore.success, "InstanceContainer must NOT have been mixed in with improveextensions.InstanceContainerMixin, because early loading has been disabled"); + } catch (AssertionFailedError e) { + e.printStackTrace(); + } + MinecraftServer.stopCleanly(); + } + + @Override + public void terminate() { + getLogger().info("Terminate extension"); + } +} diff --git a/src/test/java/improveextensions/MixinIntoMinestomCore.java b/src/test/java/improveextensions/MixinIntoMinestomCore.java new file mode 100644 index 000000000..131734e91 --- /dev/null +++ b/src/test/java/improveextensions/MixinIntoMinestomCore.java @@ -0,0 +1,37 @@ +package improveextensions; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.extensions.Extension; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.world.DimensionType; +import org.junit.jupiter.api.Assertions; +import org.opentest4j.AssertionFailedError; + +import java.util.UUID; + +/** + * Extensions should be able to use Mixins for classes loaded very early by Minestom (InstanceContainer for instance) + */ +public class MixinIntoMinestomCore extends Extension { + + public static boolean success = false; + + @Override + public void initialize() { + // force load of InstanceContainer class + InstanceContainer c = new InstanceContainer(UUID.randomUUID(), DimensionType.OVERWORLD, null); + System.out.println(c.toString()); + try { + Assertions.assertTrue(success, "InstanceContainer must have been mixed in with improveextensions.InstanceContainerMixin"); + Assertions.assertEquals(1, MinecraftServer.getExtensionManager().getExtensionLoaders().size(), "Only one extension classloader (this extension's) must be active."); + } catch (AssertionFailedError e) { + e.printStackTrace(); + } + MinecraftServer.stopCleanly(); + } + + @Override + public void terminate() { + getLogger().info("Terminate extension"); + } +} diff --git a/src/test/java/improveextensions/MixinIntoMinestomCoreLauncher.java b/src/test/java/improveextensions/MixinIntoMinestomCoreLauncher.java new file mode 100644 index 000000000..eb1a436a4 --- /dev/null +++ b/src/test/java/improveextensions/MixinIntoMinestomCoreLauncher.java @@ -0,0 +1,15 @@ +package improveextensions; + +import net.minestom.server.Bootstrap; + +// To launch with VM arguments: + +// To test early Mixin injections: +// -Dminestom.extension.indevfolder.classes=build/classes/java/test/ -Dminestom.extension.indevfolder.resources=build/resources/test/improveextensions +// To test disabling early Mixin injections: +// -Dminestom.extension.disable_early_load=true -Dminestom.extension.indevfolder.classes=build/classes/java/test/ -Dminestom.extension.indevfolder.resources=build/resources/test/improveextensions/disableearlyload +public class MixinIntoMinestomCoreLauncher { + public static void main(String[] args) { + Bootstrap.bootstrap("demo.MainDemo", args); + } +} diff --git a/src/test/java/improveextensions/mixins/InstanceContainerMixin.java b/src/test/java/improveextensions/mixins/InstanceContainerMixin.java new file mode 100644 index 000000000..f34ec9bcd --- /dev/null +++ b/src/test/java/improveextensions/mixins/InstanceContainerMixin.java @@ -0,0 +1,19 @@ +package improveextensions.mixins; + +import improveextensions.MixinIntoMinestomCore; +import net.minestom.server.instance.InstanceContainer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(InstanceContainer.class) +public class InstanceContainerMixin { + + @Inject(method = "", at = @At("RETURN"), require = 1) + private void constructorHead(CallbackInfo ci) { + System.out.println("Mixin into InstanceContainerMixin"); + MixinIntoMinestomCore.success = true; + } + +} diff --git a/src/test/resources/improveextensions/disableearlyload/extension.json b/src/test/resources/improveextensions/disableearlyload/extension.json new file mode 100644 index 000000000..c26927c24 --- /dev/null +++ b/src/test/resources/improveextensions/disableearlyload/extension.json @@ -0,0 +1,6 @@ +{ + "entrypoint": "improveextensions.DisableEarlyLoad", + "name": "DisableEarlyLoad", + "codeModifiers": [], + "mixinConfig": "mixins.minestomcore.json" +} \ No newline at end of file diff --git a/src/test/resources/improveextensions/disableearlyload/mixins.minestomcore.json b/src/test/resources/improveextensions/disableearlyload/mixins.minestomcore.json new file mode 100644 index 000000000..7c8c03fe2 --- /dev/null +++ b/src/test/resources/improveextensions/disableearlyload/mixins.minestomcore.json @@ -0,0 +1,10 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "improveextensions.mixins", + "target": "@env(DEFAULT)", + "compatibilityLevel": "JAVA_11", + "mixins": [ + "InstanceContainerMixin" + ] +} \ No newline at end of file diff --git a/src/test/resources/improveextensions/extension.json b/src/test/resources/improveextensions/extension.json new file mode 100644 index 000000000..abbe3eb02 --- /dev/null +++ b/src/test/resources/improveextensions/extension.json @@ -0,0 +1,6 @@ +{ + "entrypoint": "improveextensions.MixinIntoMinestomCore", + "name": "MixinIntoMinestomCore", + "codeModifiers": [], + "mixinConfig": "mixins.minestomcore.json" +} \ No newline at end of file diff --git a/src/test/resources/improveextensions/mixins.minestomcore.json b/src/test/resources/improveextensions/mixins.minestomcore.json new file mode 100644 index 000000000..7c8c03fe2 --- /dev/null +++ b/src/test/resources/improveextensions/mixins.minestomcore.json @@ -0,0 +1,10 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "improveextensions.mixins", + "target": "@env(DEFAULT)", + "compatibilityLevel": "JAVA_11", + "mixins": [ + "InstanceContainerMixin" + ] +} \ No newline at end of file