From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Riley Park <rileysebastianpark@gmail.com>
Date: Tue, 16 Jul 2024 14:55:23 -0700
Subject: [PATCH] Bundle spark


diff --git a/build.gradle.kts b/build.gradle.kts
index 1a734293c9416f13324bb0edf8f950c9029f8bc4..3588770a9ea6ee0a9508b218758650f43d994715 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -61,6 +61,10 @@ dependencies {
     implementation("io.papermc:reflection-rewriter-runtime:$reflectionRewriterVersion")
     implementation("io.papermc:reflection-rewriter-proxy-generator:$reflectionRewriterVersion")
     // Paper end - Remap reflection
+    // Paper start - spark
+    implementation("me.lucko:spark-api:0.1-20240720.200737-2")
+    implementation("me.lucko:spark-paper:1.10.99-SNAPSHOT")
+    // Paper end - spark
 }
 
 paperweight {
diff --git a/src/main/java/io/papermc/paper/SparksFly.java b/src/main/java/io/papermc/paper/SparksFly.java
new file mode 100644
index 0000000000000000000000000000000000000000..19ee43e1ca053574a0151b4c43b01972183657e6
--- /dev/null
+++ b/src/main/java/io/papermc/paper/SparksFly.java
@@ -0,0 +1,200 @@
+package io.papermc.paper;
+
+import io.papermc.paper.configuration.GlobalConfiguration;
+import io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoaderStorage;
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
+import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage;
+import io.papermc.paper.util.MCUtil;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import me.lucko.spark.paper.api.Compatibility;
+import me.lucko.spark.paper.api.PaperClassLookup;
+import me.lucko.spark.paper.api.PaperScheduler;
+import me.lucko.spark.paper.api.PaperSparkModule;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextColor;
+import net.minecraft.util.ExceptionCollector;
+import org.bukkit.Server;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftServer;
+
+// It's like electricity.
+public final class SparksFly {
+    public static final String ID = "spark";
+    public static final String COMMAND_NAME = "spark";
+
+    private static final String PREFER_SPARK_PLUGIN_PROPERTY = "paper.preferSparkPlugin";
+
+    private static final int SPARK_YELLOW = 0xffc93a;
+
+    private final Logger logger;
+    private final PaperSparkModule spark;
+
+    private boolean enabled;
+    private boolean disabledInConfigurationWarningLogged;
+
+    public SparksFly(final Server server) {
+        this.logger = Logger.getLogger(ID);
+        this.logger.log(Level.INFO, "This server bundles the spark profiler. For more information please visit https://docs.papermc.io/paper/profiling");
+        this.spark = PaperSparkModule.create(Compatibility.VERSION_1_0, server, this.logger, new PaperScheduler() {
+            @Override
+            public void executeAsync(final Runnable runnable) {
+                MCUtil.scheduleAsyncTask(this.catching(runnable, "asynchronous"));
+            }
+
+            @Override
+            public void executeSync(final Runnable runnable) {
+                MCUtil.ensureMain(this.catching(runnable, "synchronous"));
+            }
+
+            private Runnable catching(final Runnable runnable, final String type) {
+                return () -> {
+                    try {
+                        runnable.run();
+                    } catch (final Throwable t) {
+                        SparksFly.this.logger.log(Level.SEVERE, "An exception was encountered while executing a " + type + " spark task", t);
+                    }
+                };
+            }
+        }, new PaperClassLookup() {
+            @Override
+            public Class<?> lookup(final String className) throws Exception {
+                final ExceptionCollector<ClassNotFoundException> exceptions = new ExceptionCollector<>();
+                try {
+                    return Class.forName(className);
+                } catch (final ClassNotFoundException e) {
+                    exceptions.add(e);
+                    for (final ConfiguredPluginClassLoader loader : ((PaperPluginClassLoaderStorage) PaperClassLoaderStorage.instance()).getGlobalGroup().getClassLoaders()) {
+                        try {
+                            final Class<?> loadedClass = loader.loadClass(className, true, false, true);
+                            if (loadedClass != null) {
+                                return loadedClass;
+                            }
+                        } catch (final ClassNotFoundException exception) {
+                            exceptions.add(exception);
+                        }
+                    }
+                    exceptions.throwIfPresent();
+                    return null;
+                }
+            }
+        });
+    }
+
+    public void enableEarlyIfRequested() {
+        if (!isPluginPreferred() && shouldEnableImmediately()) {
+            this.enable();
+        }
+    }
+
+    public void enableBeforePlugins() {
+        if (!isPluginPreferred()) {
+            this.enable();
+        }
+    }
+
+    public void enableAfterPlugins(final Server server) {
+        final boolean isPluginPreferred = isPluginPreferred();
+        final boolean isPluginEnabled = isPluginEnabled(server);
+        if (!isPluginPreferred || !isPluginEnabled) {
+            if (isPluginPreferred && !this.enabled) {
+                this.logger.log(Level.INFO, "The spark plugin has been preferred but was not loaded. The bundled spark profiler will enabled instead.");
+            }
+            this.enable();
+        }
+    }
+
+    private void enable() {
+        if (!this.enabled) {
+            if (GlobalConfiguration.get().spark.enabled) {
+                this.enabled = true;
+                this.spark.enable();
+            } else {
+                if (!this.disabledInConfigurationWarningLogged) {
+                    this.logger.log(Level.INFO, "The spark profiler will not be enabled because it is currently disabled in the configuration.");
+                    this.disabledInConfigurationWarningLogged = true;
+                }
+            }
+        }
+    }
+
+    public void disable() {
+        if (this.enabled) {
+            this.spark.disable();
+            this.enabled = false;
+        }
+    }
+
+    public void registerCommandBeforePlugins(final Server server) {
+        if (!isPluginPreferred()) {
+            this.registerCommand(server);
+        }
+    }
+
+    public void registerCommandAfterPlugins(final Server server) {
+        if ((!isPluginPreferred() || !isPluginEnabled(server)) && server.getCommandMap().getCommand(COMMAND_NAME) == null) {
+            this.registerCommand(server);
+        }
+    }
+
+    private void registerCommand(final Server server) {
+        server.getCommandMap().register(COMMAND_NAME, "paper", new CommandImpl(COMMAND_NAME));
+    }
+
+    public void tickStart() {
+        this.spark.onServerTickStart();
+    }
+
+    public void tickEnd(final double duration) {
+        this.spark.onServerTickEnd(duration);
+    }
+
+    void executeCommand(final CommandSender sender, final String[] args) {
+        this.spark.executeCommand(sender, args);
+    }
+
+    List<String> tabComplete(final CommandSender sender, final String[] args) {
+        return this.spark.tabComplete(sender, args);
+    }
+
+    public static boolean isPluginPreferred() {
+        return Boolean.getBoolean(PREFER_SPARK_PLUGIN_PROPERTY);
+    }
+
+    private static boolean isPluginEnabled(final Server server) {
+        return server.getPluginManager().isPluginEnabled(ID);
+    }
+
+    private static boolean shouldEnableImmediately() {
+        return GlobalConfiguration.get().spark.enableImmediately;
+    }
+
+    public static final class CommandImpl extends Command {
+        CommandImpl(final String name) {
+            super(name);
+            this.setPermission("spark");
+        }
+
+        @Override
+        public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) {
+            final SparksFly spark = ((CraftServer) sender.getServer()).spark;
+            if (spark.enabled) {
+                spark.executeCommand(sender, args);
+            } else {
+                sender.sendMessage(Component.text("The spark profiler is currently disabled.", TextColor.color(SPARK_YELLOW)));
+            }
+            return true;
+        }
+
+        @Override
+        public List<String> tabComplete(final CommandSender sender, final String alias, final String[] args) throws IllegalArgumentException {
+            final SparksFly spark = ((CraftServer) sender.getServer()).spark;
+            if (spark.enabled) {
+                return spark.tabComplete(sender, args);
+            }
+            return List.of();
+        }
+    }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java
index 6b8ed8a0baaf4a57d20e57cec3400af5561ddd79..48604e7f96adc9e226e034054c5e2bad0b024eb5 100644
--- a/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java
+++ b/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java
@@ -1,6 +1,9 @@
 package io.papermc.paper.plugin.provider.source;
 
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.SparksFly;
 import io.papermc.paper.plugin.PluginInitializerManager;
+import io.papermc.paper.plugin.configuration.PluginMeta;
 import io.papermc.paper.plugin.entrypoint.EntrypointHandler;
 import io.papermc.paper.plugin.provider.type.PluginFileType;
 import org.bukkit.plugin.InvalidPluginException;
@@ -17,12 +20,14 @@ import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.jar.JarFile;
+import org.slf4j.Logger;
 
 /**
  * Loads a plugin provider at the given plugin jar file path.
  */
 public class FileProviderSource implements ProviderSource<Path, Path> {
 
+    private static final Logger LOGGER = LogUtils.getClassLogger();
     private final Function<Path, String> contextChecker;
     private final boolean applyRemap;
 
@@ -82,6 +87,12 @@ public class FileProviderSource implements ProviderSource<Path, Path> {
             );
         }
 
+        final PluginMeta config = type.getConfig(file);
+        if ((config.getName().equals("spark") && config.getMainClass().equals("me.lucko.spark.bukkit.BukkitSparkPlugin")) && !SparksFly.isPluginPreferred()) {
+            LOGGER.info("The spark plugin will not be loaded as this server bundles the spark profiler.");
+            return;
+        }
+
         type.register(entrypointHandler, file, context);
     }
 
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 45695abbeb0a6d47b31b23ba6c464f17e99d7898..23ddd26af762c1cd7fb3920669abb96b3213ab37 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -752,6 +752,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
         // Paper end - Configurable player collision
 
         this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
+        this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
+        this.server.spark.enableAfterPlugins(this.server); // Paper - spark
         if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
         io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // Paper - reset invalid state for event fire below
         io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - call commands event for regular plugins
@@ -1038,6 +1040,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
         MinecraftServer.LOGGER.info("Stopping server");
         Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Perf: Async command map building; Shutdown and don't bother finishing
         MinecraftTimings.stopServer(); // Paper
+        this.server.spark.disable(); // Paper - spark
         // CraftBukkit start
         if (this.server != null) {
             this.server.disablePlugins();
@@ -1227,6 +1230,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
             // tasks are default scheduled at -1 + delay, and first tick will tick at 1
             final long actualDoneTimeMs = System.currentTimeMillis() - org.bukkit.craftbukkit.Main.BOOT_TIME.toEpochMilli(); // Paper - Add total time
             LOGGER.info("Done ({})! For help, type \"help\"", String.format(java.util.Locale.ROOT, "%.3fs", actualDoneTimeMs / 1000.00D)); // Paper - Add total time
+            this.server.spark.enableBeforePlugins(); // Paper - spark
             org.spigotmc.WatchdogThread.tick();
             // Paper end - Improved Watchdog Support
             org.spigotmc.WatchdogThread.hasStarted = true; // Paper
@@ -1586,6 +1590,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
         });
         isOversleep = false;MinecraftTimings.serverOversleep.stopTiming();
         // Paper end
+        this.server.spark.tickStart(); // Paper - spark
         new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.tickCount+1).callEvent(); // Paper - Server Tick Events
 
         ++this.tickCount;
@@ -1628,6 +1633,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
         long endTime = System.nanoTime();
         long remaining = (TICK_TIME - (endTime - lastTick)) - catchupTime;
         new com.destroystokyo.paper.event.server.ServerTickEndEvent(this.tickCount, ((double)(endTime - lastTick) / 1000000D), remaining).callEvent();
+        this.server.spark.tickEnd(((double)(endTime - lastTick) / 1000000D)); // Paper - spark
         // Paper end - Server Tick Events
         this.profiler.push("tallying");
         long j = Util.getNanos() - i;
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index d43b98bdfcb00603737a309c0fb7793d42289b8c..dd56c8e041116ef3602a9f89c998c8208ab89b51 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -226,6 +226,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
         this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
         this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
         // Paper end - initialize global and world-defaults configuration
+        this.server.spark.enableEarlyIfRequested(); // Paper - spark
         // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
         if (this.convertOldUsers()) {
             this.getProfileCache().save(false); // Paper
@@ -235,6 +236,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
         org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); // Paper - start watchdog thread
         thread.start(); // Paper - Enhance console tab completions for brigadier commands; start console thread after MinecraftServer.console & PaperConfig are initialized
         io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
+        this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
         com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
         com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
 
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 05001bb637d55aaf36359b37a42ef33e4557b9f2..1b36e94617d4e777c419660936460d5cf8a4b3e8 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -309,6 +309,7 @@ public final class CraftServer implements Server {
     public static Exception excessiveVelEx; // Paper - Velocity warnings
     private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper
     private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; // Paper - Custom Potion Mixes
+    public final io.papermc.paper.SparksFly spark; // Paper - spark
 
     // Paper start - Folia region threading API
     private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler();
@@ -474,6 +475,7 @@ public final class CraftServer implements Server {
         }
         this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes
         datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper
+        this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark
     }
 
     public boolean getCommandBlockOverride(String command) {
@@ -1099,6 +1101,7 @@ public final class CraftServer implements Server {
         this.reloadData();
         org.spigotmc.SpigotConfig.registerCommands(); // Spigot
         io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper
+        this.spark.registerCommandBeforePlugins(this); // Paper - spark
         this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
         this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
 
@@ -1127,6 +1130,7 @@ public final class CraftServer implements Server {
         this.loadPlugins();
         this.enablePlugins(PluginLoadOrder.STARTUP);
         this.enablePlugins(PluginLoadOrder.POSTWORLD);
+        this.spark.registerCommandAfterPlugins(this); // Paper - spark
         if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
         // Paper start - brigadier command API
         io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // to clear invalid state for event fire below