From 60525ad594a991f1597e706c16847c07bba14529 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:58:12 -0400 Subject: [PATCH] Add option to profile launch using Spark --- .../org/embeddedt/modernfix/ModernFix.java | 1 + .../embeddedt/modernfix/ModernFixClient.java | 1 + .../core/config/ModernFixEarlyConfig.java | 1 + .../platform/ModernFixPlatformHooks.java | 5 + .../modernfix/util/CommonModUtil.java | 17 ++ fabric/build.gradle | 1 + .../modernfix/ModernFixPreLaunchFabric.java | 15 ++ .../fabric/spark/SparkLaunchProfiler.java | 174 +++++++++++++++++ .../fabric/ModernFixPlatformHooksImpl.java | 9 + fabric/src/main/resources/fabric.mod.json | 3 + forge/build.gradle | 1 + .../forge/spark/SparkLaunchProfiler.java | 177 ++++++++++++++++++ .../forge/ModernFixPlatformHooksImpl.java | 12 ++ gradle.properties | 5 +- 14 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 common/src/main/java/org/embeddedt/modernfix/util/CommonModUtil.java create mode 100644 fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java create mode 100644 fabric/src/main/java/org/embeddedt/modernfix/fabric/spark/SparkLaunchProfiler.java create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/spark/SparkLaunchProfiler.java diff --git a/common/src/main/java/org/embeddedt/modernfix/ModernFix.java b/common/src/main/java/org/embeddedt/modernfix/ModernFix.java index 73dd1ac1..e863e54e 100644 --- a/common/src/main/java/org/embeddedt/modernfix/ModernFix.java +++ b/common/src/main/java/org/embeddedt/modernfix/ModernFix.java @@ -71,6 +71,7 @@ public class ModernFix { if(ModernFixPlatformHooks.isDedicatedServer()) { float gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f; ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load"); + ModernFixPlatformHooks.onLaunchComplete(); } ClassInfoManager.clear(); } diff --git a/common/src/main/java/org/embeddedt/modernfix/ModernFixClient.java b/common/src/main/java/org/embeddedt/modernfix/ModernFixClient.java index 1c1c74dc..928bf061 100644 --- a/common/src/main/java/org/embeddedt/modernfix/ModernFixClient.java +++ b/common/src/main/java/org/embeddedt/modernfix/ModernFixClient.java @@ -65,6 +65,7 @@ public class ModernFixClient { } else if (openingScreen instanceof TitleScreen && gameStartTimeSeconds < 0) { gameStartTimeSeconds = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f; ModernFix.LOGGER.warn("Game took " + gameStartTimeSeconds + " seconds to start"); + ModernFixPlatformHooks.onLaunchComplete(); } } diff --git a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 5c3b5cf0..dd260480 100644 --- a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -153,6 +153,7 @@ public class ModernFixEarlyConfig { .put("mixin.perf.faster_item_rendering", false) .put("mixin.feature.spam_thread_dump", false) .put("mixin.feature.snapshot_easter_egg", true) + .put("mixin.feature.spark_profile_launch", false) .put("mixin.perf.blast_search_trees", shouldReplaceSearchTrees) .put("mixin.devenv", isDevEnv) .put("mixin.perf.remove_spawn_chunks", isDevEnv) diff --git a/common/src/main/java/org/embeddedt/modernfix/platform/ModernFixPlatformHooks.java b/common/src/main/java/org/embeddedt/modernfix/platform/ModernFixPlatformHooks.java index ee199cb2..8e7ec640 100644 --- a/common/src/main/java/org/embeddedt/modernfix/platform/ModernFixPlatformHooks.java +++ b/common/src/main/java/org/embeddedt/modernfix/platform/ModernFixPlatformHooks.java @@ -91,4 +91,9 @@ public class ModernFixPlatformHooks { public static Multimap getCustomModOptions() { throw new AssertionError(); } + + @ExpectPlatform + public static void onLaunchComplete() { + + } } diff --git a/common/src/main/java/org/embeddedt/modernfix/util/CommonModUtil.java b/common/src/main/java/org/embeddedt/modernfix/util/CommonModUtil.java new file mode 100644 index 00000000..003d638d --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/util/CommonModUtil.java @@ -0,0 +1,17 @@ +package org.embeddedt.modernfix.util; + +import org.embeddedt.modernfix.core.ModernFixMixinPlugin; + +public class CommonModUtil { + /** + * Avoid using this, it's bad practice but cleanest way of suppressing errors for nonessential mod-dependent + * functionality. + */ + public static void runWithoutCrash(Runnable r, String errorMsg) { + try { + r.run(); + } catch(Throwable e) { + ModernFixMixinPlugin.instance.logger.error(errorMsg, e); + } + } +} diff --git a/fabric/build.gradle b/fabric/build.gradle index 38437162..2472d9f3 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -33,6 +33,7 @@ dependencies { modIncludeImplementation(fabricApi.module("fabric-models-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modImplementation(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false } + modImplementation "curse.maven:spark-361579:${rootProject.spark_fabric_version}" modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}") { exclude group: 'net.fabricmc', module: 'fabric-loader' } // Remove the next line if you don't want to depend on the API // modApi "me.shedaniel:architectury-fabric:${rootProject.architectury_version}" diff --git a/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java b/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java new file mode 100644 index 00000000..6d1137b4 --- /dev/null +++ b/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java @@ -0,0 +1,15 @@ +package org.embeddedt.modernfix; + +import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint; +import org.embeddedt.modernfix.core.ModernFixMixinPlugin; +import org.embeddedt.modernfix.fabric.spark.SparkLaunchProfiler; +import org.embeddedt.modernfix.util.CommonModUtil; + +public class ModernFixPreLaunchFabric implements PreLaunchEntrypoint { + @Override + public void onPreLaunch() { + if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) { + CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.start("launch"), "Failed to start profiler"); + } + } +} diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/spark/SparkLaunchProfiler.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/spark/SparkLaunchProfiler.java new file mode 100644 index 00000000..9ab55b35 --- /dev/null +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/spark/SparkLaunchProfiler.java @@ -0,0 +1,174 @@ +package org.embeddedt.modernfix.fabric.spark; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import me.lucko.spark.common.SparkPlatform; +import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.command.sender.CommandSender; +import me.lucko.spark.common.platform.AbstractPlatformInfo; +import me.lucko.spark.common.platform.PlatformInfo; +import me.lucko.spark.common.sampler.Sampler; +import me.lucko.spark.common.sampler.ThreadDumper; +import me.lucko.spark.common.sampler.ThreadGrouper; +import me.lucko.spark.common.sampler.ThreadNodeOrder; +import me.lucko.spark.common.sampler.async.AsyncProfilerAccess; +import me.lucko.spark.common.sampler.async.AsyncSampler; +import me.lucko.spark.common.sampler.java.JavaSampler; +import me.lucko.spark.common.sampler.node.MergeMode; +import me.lucko.spark.common.util.MethodDisambiguator; +import me.lucko.spark.lib.adventure.text.Component; +import me.lucko.spark.lib.okhttp3.MediaType; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import org.embeddedt.modernfix.core.ModernFixMixinPlugin; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Stream; + +/* Inspired by CensoredASM */ +public class SparkLaunchProfiler { + private static PlatformInfo platformInfo = new ModernFixPlatformInfo(); + private static CommandSender commandSender = new ModernFixCommandSender(); + private static Map ongoingSamplers = new Object2ReferenceOpenHashMap<>(); + private static MediaType mediaType = MediaType.parse("application/x-spark-sampler"); + private static ExecutorService executor = Executors.newSingleThreadScheduledExecutor((new ThreadFactoryBuilder()).setNameFormat("spark-modernfix-async-worker").build()); + private static final SparkPlatform platform = new SparkPlatform(new ModernFixSparkPlugin()); + + private static final boolean USE_JAVA_SAMPLER_FOR_LAUNCH = true; //Boolean.getBoolean("modernfix.profileLaunchWithJavaSampler"); + + public static void start(String key) { + if (!ongoingSamplers.containsKey(key)) { + Sampler sampler; + try { + if(USE_JAVA_SAMPLER_FOR_LAUNCH) { + throw new UnsupportedOperationException(); + } + AsyncProfilerAccess.INSTANCE.getProfiler(); + sampler = new AsyncSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME); + } catch (UnsupportedOperationException e) { + sampler = new JavaSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME, -1, true, true); + } + ongoingSamplers.put(key, sampler); + ModernFixMixinPlugin.instance.logger.warn("Profiler has started for stage [{}]...", key); + sampler.start(); + } + } + + public static void stop(String key) { + Sampler sampler = ongoingSamplers.remove(key); + if (sampler != null) { + sampler.stop(); + output(key, sampler); + } + } + + private static void output(String key, Sampler sampler) { + executor.execute(() -> { + ModernFixMixinPlugin.instance.logger.warn("Stage [{}] profiler has stopped! Uploading results...", key); + byte[] output = sampler.formCompressedDataPayload(new Sampler.ExportProps(platformInfo, commandSender, ThreadNodeOrder.BY_TIME, "Stage: " + key, MergeMode.separateParentCalls(new MethodDisambiguator()), platform.getClassSourceLookup())); + try { + String urlKey = SparkPlatform.BYTEBIN_CLIENT.postContent(output, mediaType).key(); + String url = "https://spark.lucko.me/" + urlKey; + ModernFixMixinPlugin.instance.logger.warn("Profiler results for Stage [{}]: {}", key, url); + } catch (Exception e) { + ModernFixMixinPlugin.instance.logger.fatal("An error occurred whilst uploading the results.", e); + } + }); + } + + static class ModernFixPlatformInfo extends AbstractPlatformInfo { + + @Override + public Type getType() { + return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT ? Type.CLIENT : Type.SERVER; + } + + @Override + public String getName() { + return "ModernFix"; + } + + private static String versionOfModContainer(Optional containerOpt) { + return containerOpt.map(container -> container.getMetadata().getVersion().toString()).orElse("unknown"); + } + + @Override + public String getVersion() { + return versionOfModContainer(FabricLoader.getInstance().getModContainer("modernfix")); + } + + @Override + public String getMinecraftVersion() { + return versionOfModContainer(FabricLoader.getInstance().getModContainer("minecraft")); + } + } + + public static class ModernFixCommandSender implements CommandSender { + + private final UUID uuid = UUID.randomUUID(); + private final String name; + + public ModernFixCommandSender() { + this.name = "ModernFix"; + } + + @Override + public String getName() { + return name; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + + @Override + public boolean hasPermission(String s) { + return true; + } + + @Override + public void sendMessage(Component component) { + + } + } + + static class ModernFixSparkPlugin implements SparkPlugin { + + @Override + public String getVersion() { + return "1.0"; + } + + @Override + public Path getPluginDirectory() { + return FabricLoader.getInstance().getGameDir().resolve("spark-modernfix"); + } + + @Override + public String getCommandName() { + return "spark-modernfix"; + } + + @Override + public Stream getCommandSenders() { + return Stream.of(); + } + + @Override + public void executeAsync(Runnable runnable) { + executor.execute(runnable); + } + + @Override + public PlatformInfo getPlatformInfo() { + return platformInfo; + } + } +} diff --git a/fabric/src/main/java/org/embeddedt/modernfix/platform/fabric/ModernFixPlatformHooksImpl.java b/fabric/src/main/java/org/embeddedt/modernfix/platform/fabric/ModernFixPlatformHooksImpl.java index a3cae473..cfd314a6 100644 --- a/fabric/src/main/java/org/embeddedt/modernfix/platform/fabric/ModernFixPlatformHooksImpl.java +++ b/fabric/src/main/java/org/embeddedt/modernfix/platform/fabric/ModernFixPlatformHooksImpl.java @@ -19,6 +19,9 @@ import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; import org.embeddedt.modernfix.ModernFixFabric; import org.embeddedt.modernfix.api.constants.IntegrationConstants; +import org.embeddedt.modernfix.core.ModernFixMixinPlugin; +import org.embeddedt.modernfix.fabric.spark.SparkLaunchProfiler; +import org.embeddedt.modernfix.util.CommonModUtil; import org.objectweb.asm.tree.*; import java.nio.file.Path; @@ -109,4 +112,10 @@ public class ModernFixPlatformHooksImpl { } return modOptions; } + + public static void onLaunchComplete() { + if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) { + CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.stop("launch"), "Failed to stop profiler"); + } + } } diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 57145e29..336d98bb 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -22,6 +22,9 @@ "client": [ "org.embeddedt.modernfix.ModernFixClientFabric" ], + "preLaunch": [ + "org.embeddedt.modernfix.ModernFixPreLaunchFabric" + ], "modmenu": [ "org.embeddedt.modernfix.fabric.modmenu.ModernFixModMenuApiImpl" ] }, "mixins": [ diff --git a/forge/build.gradle b/forge/build.gradle index 14db4984..3534c738 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -50,6 +50,7 @@ dependencies { modCompileOnly files("deps/starlight-1.2.jar") modCompileOnly("appeng:appliedenergistics2:8.4.7") modCompileOnly("vazkii.patchouli:Patchouli:1.16.4-53.3") + modImplementation "curse.maven:spark-361579:${rootProject.spark_forge_version}" common(project(path: ":common", configuration: "namedElements")) { transitive false } shadowCommon(project(path: ":common", configuration: "transformProductionForge")) { transitive = false } diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/spark/SparkLaunchProfiler.java b/forge/src/main/java/org/embeddedt/modernfix/forge/spark/SparkLaunchProfiler.java new file mode 100644 index 00000000..6e20139c --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/spark/SparkLaunchProfiler.java @@ -0,0 +1,177 @@ +package org.embeddedt.modernfix.forge.spark; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import me.lucko.spark.common.SparkPlatform; +import me.lucko.spark.common.SparkPlugin; +import me.lucko.spark.common.command.sender.CommandSender; +import me.lucko.spark.common.platform.PlatformInfo; +import me.lucko.spark.common.sampler.Sampler; +import me.lucko.spark.common.sampler.ThreadDumper; +import me.lucko.spark.common.sampler.ThreadGrouper; +import me.lucko.spark.common.sampler.ThreadNodeOrder; +import me.lucko.spark.common.sampler.async.AsyncProfilerAccess; +import me.lucko.spark.common.sampler.async.AsyncSampler; +import me.lucko.spark.common.sampler.java.JavaSampler; +import me.lucko.spark.common.sampler.node.MergeMode; +import me.lucko.spark.common.util.MethodDisambiguator; +import me.lucko.spark.lib.adventure.text.Component; +import me.lucko.spark.lib.okhttp3.MediaType; +import me.lucko.spark.proto.SparkSamplerProtos; +import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.loading.FMLPaths; +import net.minecraftforge.fml.loading.LoadingModList; +import net.minecraftforge.versions.mcp.MCPVersion; +import org.embeddedt.modernfix.core.ModernFixMixinPlugin; + +import java.nio.file.Path; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.stream.Stream; + +/* Inspired by CensoredASM */ +public class SparkLaunchProfiler { + private static PlatformInfo platformInfo = new ModernFixPlatformInfo(); + private static CommandSender commandSender = new ModernFixCommandSender(); + private static Map ongoingSamplers = new Object2ReferenceOpenHashMap<>(); + private static MediaType mediaType = MediaType.parse("application/x-spark-sampler"); + private static ExecutorService executor = Executors.newSingleThreadScheduledExecutor((new ThreadFactoryBuilder()).setNameFormat("spark-modernfix-async-worker").build()); + private static final SparkPlatform platform = new SparkPlatform(new ModernFixSparkPlugin()); + + private static final boolean USE_JAVA_SAMPLER_FOR_LAUNCH = true; //Boolean.getBoolean("modernfix.profileLaunchWithJavaSampler"); + + public static void start(String key) { + if (!ongoingSamplers.containsKey(key)) { + Sampler sampler; + try { + if(USE_JAVA_SAMPLER_FOR_LAUNCH) { + throw new UnsupportedOperationException(); + } + AsyncProfilerAccess.INSTANCE.getProfiler(); + sampler = new AsyncSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME, -1); + } catch (UnsupportedOperationException e) { + sampler = new JavaSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME, -1, true, true); + } + ongoingSamplers.put(key, sampler); + ModernFixMixinPlugin.instance.logger.warn("Profiler has started for stage [{}]...", key); + sampler.start(); + } + } + + public static void stop(String key) { + Sampler sampler = ongoingSamplers.remove(key); + if (sampler != null) { + sampler.stop(); + output(key, sampler); + } + } + + private static void output(String key, Sampler sampler) { + executor.execute(() -> { + ModernFixMixinPlugin.instance.logger.warn("Stage [{}] profiler has stopped! Uploading results...", key); + SparkSamplerProtos.SamplerData output = sampler.toProto(platform, commandSender, ThreadNodeOrder.BY_TIME, "Stage: " + key, MergeMode.separateParentCalls(new MethodDisambiguator()), platform.createClassSourceLookup()); + try { + String urlKey = platform.getBytebinClient().postContent(output, mediaType).key(); + String url = "https://spark.lucko.me/" + urlKey; + ModernFixMixinPlugin.instance.logger.warn("Profiler results for Stage [{}]: {}", key, url); + } catch (Exception e) { + ModernFixMixinPlugin.instance.logger.fatal("An error occurred whilst uploading the results.", e); + } + }); + } + + static class ModernFixPlatformInfo implements PlatformInfo { + + @Override + public Type getType() { + return FMLLoader.getDist().isClient() ? Type.CLIENT : Type.SERVER; + } + + @Override + public String getName() { + return "ModernFix"; + } + + @Override + public String getVersion() { + return LoadingModList.get().getModFileById("modernfix").getMods().get(0).getVersion().toString(); + } + + @Override + public String getMinecraftVersion() { + return MCPVersion.getMCVersion(); + } + + } + + public static class ModernFixCommandSender implements CommandSender { + + private final UUID uuid = UUID.randomUUID(); + private final String name; + + public ModernFixCommandSender() { + this.name = "ModernFix"; + } + + @Override + public String getName() { + return name; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + + @Override + public boolean hasPermission(String s) { + return true; + } + + @Override + public void sendMessage(Component component) { + + } + } + + static class ModernFixSparkPlugin implements SparkPlugin { + + @Override + public String getVersion() { + return "1.0"; + } + + @Override + public Path getPluginDirectory() { + return FMLPaths.GAMEDIR.get().resolve("spark-modernfix"); + } + + @Override + public String getCommandName() { + return "spark-modernfix"; + } + + @Override + public Stream getCommandSenders() { + return Stream.of(); + } + + @Override + public void executeAsync(Runnable runnable) { + executor.execute(runnable); + } + + @Override + public void log(Level level, String s) { + ModernFixMixinPlugin.instance.logger.warn(s); + } + + @Override + public PlatformInfo getPlatformInfo() { + return platformInfo; + } + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/platform/forge/ModernFixPlatformHooksImpl.java b/forge/src/main/java/org/embeddedt/modernfix/platform/forge/ModernFixPlatformHooksImpl.java index 41d79846..4dab5922 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/platform/forge/ModernFixPlatformHooksImpl.java +++ b/forge/src/main/java/org/embeddedt/modernfix/platform/forge/ModernFixPlatformHooksImpl.java @@ -30,6 +30,8 @@ import org.embeddedt.modernfix.api.constants.IntegrationConstants; import org.embeddedt.modernfix.forge.classloading.FastAccessTransformerList; import org.embeddedt.modernfix.forge.classloading.ModernFixResourceFinder; import org.embeddedt.modernfix.forge.packet.PacketHandler; +import org.embeddedt.modernfix.forge.spark.SparkLaunchProfiler; +import org.embeddedt.modernfix.util.CommonModUtil; import org.embeddedt.modernfix.util.DummyList; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; @@ -174,6 +176,10 @@ public class ModernFixPlatformHooksImpl { } catch(RuntimeException | ReflectiveOperationException e) { ModernFixMixinPlugin.instance.logger.error("Failed to patch mixin memory leak", e); } + + if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnForge")) { + CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.start("launch"), "Failed to start profiler"); + } } private Method defineClassMethod = null; @@ -245,4 +251,10 @@ public class ModernFixPlatformHooksImpl { } return modOptions; } + + public static void onLaunchComplete() { + if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnForge")) { + CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.stop("launch"), "Failed to stop profiler"); + } + } } diff --git a/gradle.properties b/gradle.properties index b5e807ab..2df38d00 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,4 +17,7 @@ supported_minecraft_versions=1.16.4,1.16.5 fabric_loader_version=0.14.18 fabric_api_version=0.42.0+1.16 -modmenu_version=1.16.23 \ No newline at end of file +modmenu_version=1.16.23 + +spark_forge_version=3767277 +spark_fabric_version=3337642