diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9e5672dd..873cb766 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: '8' + java-version: '17' - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build the mod diff --git a/build.gradle b/build.gradle index cb8ace92..9b8e08a6 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17 group = 'org.embeddedt' -version = '1.6.0-beta3' +version = '1.7.0' java { archivesBaseName = 'modernfix-mc' + minecraft_version @@ -43,6 +43,24 @@ repositories { name = 'ParchmentMC' url = 'https://maven.parchmentmc.org' } + maven { + // Shedaniel's maven (Architectury API) + url = "https://maven.architectury.dev" + content { + includeGroup "me.shedaniel" + } + } + + maven { + // saps.dev Maven (KubeJS and Rhino) + url = "https://maven.saps.dev/minecraft" + content { + includeGroup "dev.latvian.mods" + } + } + maven { // CTM + url "https://maven.tterrag.com/" + } } dependencies { @@ -141,6 +159,7 @@ curseforge { releaseType = "release" addGameVersion "Forge" addGameVersion minecraft_version + mainArtifact remapJar } } } diff --git a/src/main/java/org/embeddedt/modernfix/ModernFix.java b/src/main/java/org/embeddedt/modernfix/ModernFix.java index c7a297aa..6e0d7274 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFix.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFix.java @@ -6,6 +6,7 @@ import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.IExtensionPoint; +import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.config.ModConfig; @@ -17,6 +18,10 @@ import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.core.config.ModernFixConfig; import java.lang.management.ManagementFactory; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; // The value here should match an entry in the META-INF/mods.toml file @Mod(ModernFix.MODID) @@ -32,6 +37,26 @@ public class ModernFix { // Used to skip computing the blockstate caches twice public static boolean runningFirstInjection = false; + public static CountDownLatch worldLoadSemaphore = null; + + /** + * Simple mechanism used to delay some background processes until the client is actually in-game, to reduce + * launch time. + */ + public static void waitForWorldLoad(BooleanSupplier exitEarly) { + CountDownLatch latch = worldLoadSemaphore; + if(latch != null) { + try { + while(!latch.await(100, TimeUnit.MILLISECONDS)) { + if(exitEarly.getAsBoolean()) + return; + } + } catch(InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + public ModernFix() { INSTANCE = this; diff --git a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java index e9d58f1d..58834f63 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java @@ -4,19 +4,28 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.ConnectScreen; import net.minecraft.client.gui.screens.TitleScreen; import net.minecraftforge.client.event.ScreenOpenEvent; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; +import org.embeddedt.modernfix.core.ModernFixMixinPlugin; +import org.embeddedt.modernfix.load.LoadEvents; +import org.embeddedt.modernfix.screen.DeferredLevelLoadingScreen; import java.lang.management.ManagementFactory; public class ModernFixClient { - public static long worldLoadStartTime; private static int numRenderTicks; public static float gameStartTimeSeconds = -1; + public ModernFixClient() { + if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.faster_singleplayer_load.ClientEvents")) { + MinecraftForge.EVENT_BUS.register(new LoadEvents()); + } + } + public void resetWorldLoadStateMachine() { numRenderTicks = 0; worldLoadStartTime = -1; @@ -34,12 +43,13 @@ public class ModernFixClient { @SubscribeEvent public void onRenderTickEnd(TickEvent.RenderTickEvent event) { - if(event.phase == TickEvent.Phase.END && worldLoadStartTime != -1 && Minecraft.getInstance().player != null && numRenderTicks++ >= 10) { + if(event.phase == TickEvent.Phase.END && !(Minecraft.getInstance().screen instanceof DeferredLevelLoadingScreen) && worldLoadStartTime != -1 && Minecraft.getInstance().player != null && numRenderTicks++ >= 10) { float timeSpentLoading = ((float)(System.nanoTime() - worldLoadStartTime) / 1000000000f); ModernFix.LOGGER.warn("Time from main menu to in-game was " + timeSpentLoading + " seconds"); ModernFix.LOGGER.warn("Total time to load game and open world was " + (timeSpentLoading + gameStartTimeSeconds) + " seconds"); resetWorldLoadStateMachine(); + if(ModernFix.worldLoadSemaphore != null) + ModernFix.worldLoadSemaphore.countDown(); } } - } diff --git a/src/main/java/org/embeddedt/modernfix/blockstate/BlockStateCacheHandler.java b/src/main/java/org/embeddedt/modernfix/blockstate/BlockStateCacheHandler.java index 2076f136..e9bd7c9f 100644 --- a/src/main/java/org/embeddedt/modernfix/blockstate/BlockStateCacheHandler.java +++ b/src/main/java/org/embeddedt/modernfix/blockstate/BlockStateCacheHandler.java @@ -1,13 +1,8 @@ package org.embeddedt.modernfix.blockstate; import com.google.common.base.Stopwatch; -import com.google.common.collect.ImmutableSet; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.Util; -import net.minecraft.core.BlockPos; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.level.EmptyBlockGetter; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.fml.loading.FMLLoader; import org.embeddedt.modernfix.ModernFix; @@ -17,18 +12,9 @@ import org.embeddedt.modernfix.util.BakeReason; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; public class BlockStateCacheHandler { - private static final Set PRECACHED_COLLISION_SHAPES = ImmutableSet.builder() - .add("refinedstorage") - .add("cabletiers") - .add("extrastorage") - .build(); - private static RebuildThread currentRebuildThread = null; private static boolean needToBake() { @@ -88,25 +74,21 @@ public class BlockStateCacheHandler { private void rebuildCache() { Iterator stateIterator = blockStateList.iterator(); while(!stopRebuild && stateIterator.hasNext()) { - stateIterator.next().initCache(); + BlockState state = stateIterator.next(); + try { + state.initCache(); + } catch(Exception e) { + ModernFix.LOGGER.warn("Exception encountered while initializing cache", e); + } } } @Override - @SuppressWarnings("deprecation") public void run() { + ModernFix.waitForWorldLoad(() -> stopRebuild); + if(stopRebuild) + return; Stopwatch realtimeStopwatch = Stopwatch.createStarted(); - /* Run some special sauce for Refined Storage since it has very slow collision shapes */ - List specialStates = blockStateList.stream() - .filter(state -> PRECACHED_COLLISION_SHAPES.contains(state.getBlock().getRegistryName().getNamespace())).collect(Collectors.toList()); - CompletableFuture.runAsync(() -> { - specialStates.parallelStream() - .forEach(state -> { - /* Force these blocks to compute their shapes ahead of time on worker threads */ - state.getBlock().getCollisionShape(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO, CollisionContext.empty()); - state.getBlock().getOcclusionShape(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO); - }); - }, Util.backgroundExecutor()).join(); rebuildCache(); realtimeStopwatch.stop(); if(!stopRebuild) diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index 09064e3f..06d25ef3 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -4,7 +4,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.core.config.ModernFixEarlyConfig; import org.embeddedt.modernfix.core.config.Option; -import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.*; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; @@ -16,8 +16,10 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { private final Logger logger = LogManager.getLogger("ModernFix"); public static ModernFixEarlyConfig config = null; + public static ModernFixMixinPlugin instance; public ModernFixMixinPlugin() { + instance = this; try { config = ModernFixEarlyConfig.load(new File("./config/modernfix-mixins.properties")); } catch (Exception e) { diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java index 3dbbc3f6..2b4dc2af 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java @@ -24,7 +24,7 @@ public class ModernFixConfig { public static ForgeConfigSpec.ConfigValue> BLACKLIST_ASYNC_JEI_PLUGINS; public static ForgeConfigSpec.IntValue INTEGRATED_SERVER_PRIORITY; - public static ForgeConfigSpec.IntValue BACKGROUND_WORKER_PRIORITY; + public static ForgeConfigSpec.BooleanValue ENABLE_DEBUG_RELOADER; public static ForgeConfigSpec.BooleanValue REBUILD_BLOCKSTATES_ASYNC; @@ -40,6 +40,9 @@ public class ModernFixConfig { "jepb:jei_plugin" ), locationValidator); INTEGRATED_SERVER_PRIORITY = COMMON_BUILDER.comment("Thread priority to use for the integrated server. By default this is one less than the client thread, to help prevent the server from lowering FPS.").defineInRange("integratedServerPriority", 4, 1, 10); + ENABLE_DEBUG_RELOADER = COMMON_BUILDER + .comment("Whether Minecraft's built-in profiling logic should be enabled for resource reloading. Can help with diagnosing world load times.") + .define("enable_debug_reloader", false); REBUILD_BLOCKSTATES_ASYNC = COMMON_BUILDER .comment("Rebuild blockstate cache asynchronously. Should work with most mods, but can be disabled.") .define("rebuild_blockstate_cache_async", true); diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 0d7d0f65..3d957100 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -34,6 +34,9 @@ public class ModernFixEarlyConfig { this.addMixinRule("perf.faster_baking", true); this.addMixinRule("perf.cache_model_materials", true); this.addMixinRule("perf.datapack_reload_exceptions", true); + this.addMixinRule("perf.faster_texture_stitching", true); + /* off by default in 1.18 because it doesn't work as well */ + this.addMixinRule("perf.faster_singleplayer_load", false); /* Keep this off if JEI isn't installed to prevent breaking vanilla gameplay */ this.addMixinRule("perf.blast_search_trees", FMLLoader.getLoadingModList().getModFileById("jei") != null); this.addMixinRule("safety", true); diff --git a/src/main/java/org/embeddedt/modernfix/load/LoadEvents.java b/src/main/java/org/embeddedt/modernfix/load/LoadEvents.java new file mode 100644 index 00000000..cb42c3ae --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/load/LoadEvents.java @@ -0,0 +1,113 @@ +package org.embeddedt.modernfix.load; + +import it.unimi.dsi.fastutil.longs.LongIterator; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.LevelLoadingScreen; +import net.minecraft.client.gui.screens.ProgressScreen; +import net.minecraft.client.server.IntegratedServer; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.Unit; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.ForcedChunksSavedData; +import net.minecraftforge.client.event.ScreenOpenEvent; +import net.minecraftforge.common.world.ForgeChunkManager; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.event.server.ServerAboutToStartEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.server.ServerLifecycleHooks; +import org.embeddedt.modernfix.core.ModernFixMixinPlugin; +import org.embeddedt.modernfix.screen.DeferredLevelLoadingScreen; + +import java.util.concurrent.locks.LockSupport; +import java.util.function.BooleanSupplier; + +/** + * Handles deferring the world load screen. + *

+ * TODO: The vanilla check that at least 441 chunks have been loaded does not check whether they are spawn chunks + * or chunks loaded by the player. Consequently it is possible for loading to finish before every spawn chunk has + * been loaded. However the chunk system has at least been warmed up by this point so the remaining chunks load + * reasonably quickly. + */ +public class LoadEvents { + private boolean hasFirstPlayerJoined = false; + + @SubscribeEvent + public void serverWillStart(ServerAboutToStartEvent event) { + hasFirstPlayerJoined = false; + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) { + if(!hasFirstPlayerJoined && ModernFixMixinPlugin.instance.isOptionEnabled("perf.faster_singleplayer_load.ClientEvents")) { + hasFirstPlayerJoined = true; + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if(server instanceof IntegratedServer) { + handleInitialChunkLoad(); + } + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onWorldShow(ScreenOpenEvent event) { + if(ServerLifecycleHooks.getCurrentServer() instanceof IntegratedServer) { + if(event.getScreen() == null && Minecraft.getInstance().level != null && integratedWorldLoadListener != null) { + /* this means the world is about to be displayed, check if 441 initialized */ + ServerChunkCache provider = ServerLifecycleHooks.getCurrentServer().overworld().getChunkSource(); + BooleanSupplier worldLoadDone = () -> provider.getTickingGenerated() >= 441; + if(!worldLoadDone.getAsBoolean()) { + DeferredLevelLoadingScreen newScreen = new DeferredLevelLoadingScreen(Minecraft.getInstance().progressListener.get(), worldLoadDone); + event.setScreen(newScreen); + } + } else if(event.getScreen() instanceof LevelLoadingScreen && Minecraft.getInstance().level == null && ModernFixMixinPlugin.instance.isOptionEnabled("perf.faster_singleplayer_load.ClientEvents")) { + ProgressScreen loadscreen = new ProgressScreen(false); + loadscreen.progressStartNoAbort(new TranslatableComponent("connect.joining")); + event.setScreen(loadscreen); + } + } + } + + public static ChunkProgressListener integratedWorldLoadListener; + + private void handleInitialChunkLoad() { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + ServerLevel overworld = server.overworld(); + ServerChunkCache provider = overworld.getChunkSource(); + provider.getLightEngine().setTaskPerBatch(500); + provider.addRegionTicket(TicketType.START, new ChunkPos(overworld.getSharedSpawnPos()), 11, Unit.INSTANCE); + while(provider.getTickingGenerated() < 441) { + server.runAllTasks(); + Thread.yield(); + LockSupport.parkNanos("waiting for world load", 100000L); + server.nextTickTime = Util.getMillis() + 10; + } + for(ServerLevel serverworld1 : server.getAllLevels()) { + ForcedChunksSavedData forcedchunkssavedata = serverworld1.getDataStorage().get(ForcedChunksSavedData::load, "chunks"); + if (forcedchunkssavedata != null) { + LongIterator longiterator = forcedchunkssavedata.getChunks().iterator(); + + while(longiterator.hasNext()) { + long i = longiterator.nextLong(); + ChunkPos chunkpos = new ChunkPos(i); + serverworld1.getChunkSource().updateChunkForced(chunkpos, true); + } + + ForgeChunkManager.reinstatePersistentChunks(serverworld1, forcedchunkssavedata); + } + } + server.runAllTasks(); + server.nextTickTime = Util.getMillis() + 10; + provider.getLightEngine().setTaskPerBatch(5); + if(integratedWorldLoadListener != null) { + integratedWorldLoadListener.stop(); + integratedWorldLoadListener = null; + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/core/MinecraftMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/core/MinecraftMixin.java new file mode 100644 index 00000000..a288f8c5 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/core/MinecraftMixin.java @@ -0,0 +1,21 @@ +package org.embeddedt.modernfix.mixin.core; + +import net.minecraft.client.Minecraft; +import net.minecraft.server.WorldStem; +import net.minecraft.world.level.storage.LevelStorageSource; +import org.embeddedt.modernfix.ModernFix; +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; + +import java.util.concurrent.CountDownLatch; +import java.util.function.Function; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + @Inject(method = "doLoadLevel", at = @At("HEAD"), remap = false) + private void setLatch(String string, Function function, Function function2, boolean bl, Minecraft.ExperimentalDialogType arg, boolean creating, CallbackInfo ci) { + ModernFix.worldLoadSemaphore = new CountDownLatch(1); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/feature/measure_time/ProfiledReloadInstanceMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/feature/measure_time/ProfiledReloadInstanceMixin.java new file mode 100644 index 00000000..9d93fc83 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/feature/measure_time/ProfiledReloadInstanceMixin.java @@ -0,0 +1,24 @@ +package org.embeddedt.modernfix.mixin.feature.measure_time; + +import net.minecraft.server.packs.resources.PreparableReloadListener; +import net.minecraft.server.packs.resources.ProfiledReloadInstance; +import org.embeddedt.modernfix.util.NamedPreparableResourceListener; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import java.util.ArrayList; +import java.util.List; + +@Mixin(ProfiledReloadInstance.class) +public class ProfiledReloadInstanceMixin { + @ModifyVariable(method = "", at = @At("HEAD"), argsOnly = true, ordinal = 0) + private static List getWrappedListeners(List listeners) { + List newList = new ArrayList<>(listeners.size()); + for(PreparableReloadListener listener : listeners) { + newList.add(new NamedPreparableResourceListener(listener)); + } + return newList; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/feature/measure_time/SimpleReloadableResourceManagerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/feature/measure_time/SimpleReloadableResourceManagerMixin.java new file mode 100644 index 00000000..fa28df39 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/feature/measure_time/SimpleReloadableResourceManagerMixin.java @@ -0,0 +1,22 @@ +package org.embeddedt.modernfix.mixin.feature.measure_time; + +import net.minecraft.server.packs.resources.ReloadableResourceManager; +import org.embeddedt.modernfix.core.config.ModernFixConfig; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(ReloadableResourceManager.class) +public class SimpleReloadableResourceManagerMixin { + + /** + * @author embeddedt + * @reason add ability to use this feature in modpacks + */ + @ModifyArg(method = "createReload", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/resources/SimpleReloadInstance;create(Lnet/minecraft/server/packs/resources/ResourceManager;Ljava/util/List;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;Ljava/util/concurrent/CompletableFuture;Z)Lnet/minecraft/server/packs/resources/ReloadInstance;"), index = 5) + private boolean enableDebugReloader(boolean bl) { + return bl || ModernFixConfig.ENABLE_DEBUG_RELOADER.get(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_baking/ModelBakeryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_baking/ModelBakeryMixin.java index e7eeafe5..36c76913 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_baking/ModelBakeryMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_baking/ModelBakeryMixin.java @@ -3,6 +3,8 @@ package org.embeddedt.modernfix.mixin.perf.faster_baking; import com.google.common.collect.ImmutableSet; import com.mojang.datafixers.util.Pair; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.MultiVariant; +import net.minecraft.client.renderer.block.model.multipart.MultiPart; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.AtlasSet; import net.minecraft.client.renderer.texture.TextureAtlasSprite; @@ -105,22 +107,32 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { this.bakeIfPossible(location); } }); + List multiparts = new ArrayList<>(); /* Then store them as top-level models if needed, and set up the lazy models */ this.topLevelModels.forEach((location, value) -> { - if (incompatibleLazyBakedModels.contains(location.getNamespace()) || requiresBake(value)) { + if (requiresBake(value) || incompatibleLazyBakedModels.contains(location.getNamespace())) { BakedModel model = this.bakeIfPossible(location); if (model != null) this.bakedTopLevelModels.put(location, model); } else { - this.bakedTopLevelModels.put(location, new LazyBakedModel(() -> { - synchronized (this.bakedCache) { - BakedModel ibakedmodel = this.bakeIfPossible(location); + if(value instanceof MultiPart || value instanceof MultiVariant) { + multiparts.add(location); + } else { + this.bakedTopLevelModels.put(location, new LazyBakedModel(() -> { + synchronized (this.bakedCache) { + BakedModel ibakedmodel = this.bakeIfPossible(location); - return ibakedmodel != null ? ibakedmodel : missingModel; - } - })); + return ibakedmodel != null ? ibakedmodel : missingModel; + } + })); + } } }); + multiparts.forEach(location -> { + BakedModel model = this.bakeIfPossible(location); + if (model != null) + this.bakedTopLevelModels.put(location, model); + }); } /** diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_singleplayer_load/MinecraftServerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_singleplayer_load/MinecraftServerMixin.java new file mode 100644 index 00000000..ad0b11a0 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_singleplayer_load/MinecraftServerMixin.java @@ -0,0 +1,41 @@ +package org.embeddedt.modernfix.mixin.perf.faster_singleplayer_load; + +import net.minecraft.Util; +import net.minecraft.client.server.IntegratedServer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; +import org.embeddedt.modernfix.ModernFixClient; +import org.embeddedt.modernfix.load.LoadEvents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MinecraftServer.class) +public abstract class MinecraftServerMixin { + @Shadow protected long nextTickTime; + + @Shadow public abstract ServerLevel overworld(); + + @Shadow protected abstract void updateMobSpawningFlags(); + + /** + * @author embeddedt + * @reason defer the 441 chunk load until *after* join game packets are sent to the client, in order to allow + * mods that process advancements, etc. to work on that at the same time + */ + @Inject(method = "prepareLevels", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;getChunkSource()Lnet/minecraft/server/level/ServerChunkCache;", ordinal = 0), cancellable = true) + private void skipInitialChunkLoad(ChunkProgressListener arg, CallbackInfo ci) { + if(((Object)this) instanceof IntegratedServer) { + ci.cancel(); + LoadEvents.integratedWorldLoadListener = arg; + this.nextTickTime = Util.getMillis(); + this.overworld().getChunkSource().getLightEngine().setTaskPerBatch(5); + this.updateMobSpawningFlags(); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_texture_stitching/StitcherMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_texture_stitching/StitcherMixin.java new file mode 100644 index 00000000..247f96ce --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_texture_stitching/StitcherMixin.java @@ -0,0 +1,54 @@ +package org.embeddedt.modernfix.mixin.perf.faster_texture_stitching; + +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.client.renderer.texture.Stitcher; +import org.embeddedt.modernfix.textures.StbStitcher; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +@Mixin(Stitcher.class) +public class StitcherMixin { + @Shadow @Final private Set texturesToBeStitched; + + @Shadow private int storageX; + + @Shadow private int storageY; + + @Shadow @Final private static Comparator HOLDER_COMPARATOR; + private List loadableSpriteInfos; + + /** + * @author embeddedt, SuperCoder79 + * @reason Use improved STB stitcher instead of the vanilla implementation, for performance + */ + @Overwrite + public void stitch() { + ObjectArrayList holderList = new ObjectArrayList<>(this.texturesToBeStitched); + holderList.sort(HOLDER_COMPARATOR); + Stitcher.Holder[] aholder = holderList.toArray(new Stitcher.Holder[0]); + + Pair, List> packingInfo = StbStitcher.packRects(aholder); + this.storageX = packingInfo.getFirst().getFirst(); + this.storageY = packingInfo.getFirst().getSecond(); + this.loadableSpriteInfos = packingInfo.getSecond(); + } + + /** + * @author embeddedt, SuperCoder79 + * @reason We setup the image ourselves in the StbStitcher, so we just feed this information back into the vanilla code + */ + @Overwrite + public void gatherSprites(Stitcher.SpriteLoader spriteLoader) { + for(StbStitcher.LoadableSpriteInfo info : loadableSpriteInfos) { + spriteLoader.load(info.info, info.width, info.height, info.x, info.y); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/ModelBakeryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/ModelBakeryMixin.java index 9e3c73a6..e696f92f 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/ModelBakeryMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/parallelize_model_loading/ModelBakeryMixin.java @@ -54,7 +54,10 @@ public abstract class ModelBakeryMixin { ResourceLocation blockStateJSON = new ResourceLocation(blockLocation.getNamespace(), "blockstates/" + blockLocation.getPath() + ".json"); List blockStates; try { - blockStates = this.resourceManager.getResources(blockStateJSON); + /* Some mods' custom resource pack implementations don't seem to like concurrency here */ + synchronized(this.resourceManager) { + blockStates = this.resourceManager.getResources(blockStateJSON); + } } catch(IOException e) { ModernFix.LOGGER.warn("Exception loading blockstate definition: {}: {}", blockLocation, e); return; diff --git a/src/main/java/org/embeddedt/modernfix/screen/DeferredLevelLoadingScreen.java b/src/main/java/org/embeddedt/modernfix/screen/DeferredLevelLoadingScreen.java new file mode 100644 index 00000000..cd49687a --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/screen/DeferredLevelLoadingScreen.java @@ -0,0 +1,27 @@ +package org.embeddedt.modernfix.screen; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.screens.LevelLoadingScreen; +import net.minecraft.server.level.progress.StoringChunkProgressListener; + +import java.util.function.BooleanSupplier; + +public class DeferredLevelLoadingScreen extends LevelLoadingScreen { + private final BooleanSupplier worldLoadFinished; + public DeferredLevelLoadingScreen(StoringChunkProgressListener arg, BooleanSupplier worldLoadFinished) { + super(arg); + this.worldLoadFinished = worldLoadFinished; + } + + @Override + public void tick() { + super.tick(); + if(this.worldLoadFinished.getAsBoolean()) + this.onClose(); + } + + @Override + public void renderBackground(PoseStack matrixStack, int vOffset) { + renderDirtBackground(vOffset); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/textures/StbStitcher.java b/src/main/java/org/embeddedt/modernfix/textures/StbStitcher.java new file mode 100644 index 00000000..cc386233 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/textures/StbStitcher.java @@ -0,0 +1,90 @@ +package org.embeddedt.modernfix.textures; + +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Pair; +import net.minecraft.client.renderer.texture.Stitcher; +import net.minecraft.client.renderer.texture.StitcherException; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.util.Mth; +import org.lwjgl.stb.STBRPContext; +import org.lwjgl.stb.STBRPNode; +import org.lwjgl.stb.STBRPRect; +import org.lwjgl.stb.STBRectPack; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +/* Source: https://github.com/GTNewHorizons/lwjgl3ify/blob/f21364cd3d178aef863458a2faa1f5718a4e350d/src/main/java/me/eigenraven/lwjgl3ify/textures/StbStitcher.java */ +public class StbStitcher { + public static Pair, List> packRects(Stitcher.Holder[] holders) { + int holderSize = holders.length; + + List infoList = new ArrayList<>(); + + // Allocate memory for the rectangles and the context + try (STBRPRect.Buffer rectBuf = STBRPRect.malloc(holderSize); + STBRPContext ctx = STBRPContext.malloc(); ) { + + // Initialize the rectangles that we'll be using in the calculation + // While that's happening, sum up the area needed to fit all of the images + int sqSize = 0; + for (int j = 0; j < holderSize; ++j) { + Stitcher.Holder holder = holders[j]; + + int width = holder.width; + int height = holder.height; + + // The ID here is just the array index, for easy lookup later + rectBuf.get(j).set(j, (short)width, (short)height, (short)0, (short)0, false); + + sqSize += (width * height); + } + + int size = Mth.smallestEncompassingPowerOfTwo((int) Math.sqrt(sqSize)); + int width = size * 2; // needed to fix weirdness in 1.16 + int height = size; + + // Internal node structure needed for STB + try (STBRPNode.Buffer nodes = STBRPNode.malloc(width + 10)) { + // Initialize the rect packer + STBRectPack.stbrp_init_target(ctx, width, height, nodes); + + // Perform rectangle packing + STBRectPack.stbrp_pack_rects(ctx, rectBuf); + + for (STBRPRect rect : rectBuf) { + Stitcher.Holder holder = holders[rect.id()]; + + // Ensure that everything is properly packed! + if (!rect.was_packed()) { + throw new StitcherException(holder.spriteInfo, + Stream.of(holders).map(arg -> arg.spriteInfo).collect(ImmutableList.toImmutableList())); + } + + // Initialize the sprite now with the position and size that we've calculated so far + infoList.add(new LoadableSpriteInfo(holder.spriteInfo, width, height, rect.x(), rect.y())); + //holder.spriteInfo.initSprite(size, size, rect.x(), rect.y(), false); + } + + return Pair.of(Pair.of(width, height), infoList); + } + } + } + + public static class LoadableSpriteInfo { + public final TextureAtlasSprite.Info info; + public final int width; + public final int height; + public final int x; + public final int y; + + LoadableSpriteInfo(TextureAtlasSprite.Info info, int width, int height, int x, int y) { + this.info = info; + this.width = width; + this.height = height; + this.x = x; + this.y = y; + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/util/ModUtil.java b/src/main/java/org/embeddedt/modernfix/util/ModUtil.java index 8e09802e..be77b525 100644 --- a/src/main/java/org/embeddedt/modernfix/util/ModUtil.java +++ b/src/main/java/org/embeddedt/modernfix/util/ModUtil.java @@ -9,6 +9,8 @@ import net.minecraftforge.fml.util.ObfuscationReflectionHelper; import org.embeddedt.modernfix.ModernFix; import java.util.*; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; import java.util.function.Supplier; public class ModUtil { @@ -44,4 +46,21 @@ public class ModUtil { }); return modsListening; } + + private static final ClassLoader targetClassLoader = Thread.currentThread().getContextClassLoader(); + + private static class ModernFixForkJoinWorkerThread extends ForkJoinWorkerThread { + ModernFixForkJoinWorkerThread(ForkJoinPool pool) { + super(pool); + /* Ensure that the context class loader is set correctly */ + this.setContextClassLoader(targetClassLoader); + } + } + + public static ForkJoinPool commonPool = new ForkJoinPool( + ForkJoinPool.getCommonPoolParallelism(), + ModernFixForkJoinWorkerThread::new, + null, + false + ); } diff --git a/src/main/java/org/embeddedt/modernfix/util/NamedPreparableResourceListener.java b/src/main/java/org/embeddedt/modernfix/util/NamedPreparableResourceListener.java new file mode 100644 index 00000000..a8729114 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/util/NamedPreparableResourceListener.java @@ -0,0 +1,25 @@ +package org.embeddedt.modernfix.util; + +import net.minecraft.server.packs.resources.PreparableReloadListener; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.profiling.ProfilerFiller; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +public class NamedPreparableResourceListener implements PreparableReloadListener { + private final PreparableReloadListener delegate; + public NamedPreparableResourceListener(PreparableReloadListener delegate) { + this.delegate = delegate; + } + + @Override + public CompletableFuture reload(PreparationBarrier stage, ResourceManager resourceManager, ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, Executor backgroundExecutor, Executor gameExecutor) { + return this.delegate.reload(stage, resourceManager, preparationsProfiler, reloadProfiler, backgroundExecutor, gameExecutor); + } + + @Override + public String getName() { + return this.delegate.getName() + " [" + this.delegate.getClass().getName() + "]"; + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index aa68abed..84ad721f 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -4,4 +4,8 @@ public net.minecraft.client.renderer.RenderType$CompositeRenderType (Ljava public net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase$Cache public net.minecraft.world.phys.shapes.VoxelShape (Lnet/minecraft/util/math/shapes/VoxelShapePart;)V # public net.minecraft.client.resources.model.ModelBakery$BlockStateDefinitionException -public net.minecraft.client.renderer.model.ModelBakery field_217849_F # unbakedCache \ No newline at end of file +public net.minecraft.client.renderer.model.ModelBakery field_217849_F # unbakedCache +public net.minecraft.client.renderer.texture.Stitcher$Holder +public net.minecraft.util.thread.BlockableEventLoop m_18699_()V # runAllTasks +public net.minecraft.server.MinecraftServer f_129726_ # nextTickTime +public net.minecraft.client.Minecraft f_90999_ # progressListener \ No newline at end of file diff --git a/src/main/resources/assets/modernfix/lang/en_us.json b/src/main/resources/assets/modernfix/lang/en_us.json index d12ff605..8b1b7ad4 100644 --- a/src/main/resources/assets/modernfix/lang/en_us.json +++ b/src/main/resources/assets/modernfix/lang/en_us.json @@ -1,5 +1,5 @@ { - "modernfix.jei_load": "Loading JEI...", + "modernfix.jei_load": "Loading JEI, this may take a while", "asynclocator.map.locating": "Map (Locating...)", "asynclocator.map.none": "Map (No nearby feature found)" -} \ No newline at end of file +} diff --git a/src/main/resources/assets/modernfix/lang/zh_cn.json b/src/main/resources/assets/modernfix/lang/zh_cn.json new file mode 100644 index 00000000..0c9c92a5 --- /dev/null +++ b/src/main/resources/assets/modernfix/lang/zh_cn.json @@ -0,0 +1,5 @@ +{ + "modernfix.jei_load": "正在加载JEI,这需要一点时间", + "asynclocator.map.locating": "地图(正在定位...)", + "asynclocator.map.none": "地图(未能找到相关地物)" +} diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index e59a39ad..0a9d3b6c 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -19,9 +19,12 @@ "perf.cache_blockstate_cache_arrays.AbstractBlockStateCacheMixin", "perf.datapack_reload_exceptions.LootTableManagerMixin", "perf.datapack_reload_exceptions.RecipeManagerMixin", - "feature.measure_time.BootstrapMixin" + "feature.measure_time.BootstrapMixin", + "feature.measure_time.SimpleReloadableResourceManagerMixin", + "feature.measure_time.ProfiledReloadInstanceMixin" ], "client": [ + "core.MinecraftMixin", "feature.measure_time.MinecraftMixin", "feature.reduce_loading_screen_freezes.ModelBakeryMixin", "bugfix.concurrency.MinecraftMixin", @@ -44,7 +47,9 @@ "perf.faster_baking.BlockModelShapesMixin", "perf.faster_baking.ModelManagerMixin", "perf.cache_model_materials.VanillaModelMixin", - "perf.cache_model_materials.MultipartMixin" + "perf.cache_model_materials.MultipartMixin", + "perf.faster_texture_stitching.StitcherMixin", + "perf.faster_singleplayer_load.MinecraftServerMixin" ], "injectors": { "defaultRequire": 1