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 ba22bea0..2efffc7e 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -63,6 +63,7 @@ public class ModernFixEarlyConfig { this.addMixinRule("perf.fast_forge_dummies", true); this.addMixinRule("perf.dynamic_structure_manager", true); this.addMixinRule("bugfix.chunk_deadlock", true); + this.addMixinRule("bugfix.paper_chunk_patches", true); this.addMixinRule("perf.thread_priorities", true); this.addMixinRule("perf.scan_cache", true); this.addMixinRule("perf.kubejs", modPresent("kubejs")); diff --git a/src/main/java/org/embeddedt/modernfix/duck/IPaperChunkHolder.java b/src/main/java/org/embeddedt/modernfix/duck/IPaperChunkHolder.java new file mode 100644 index 00000000..0d83c721 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/duck/IPaperChunkHolder.java @@ -0,0 +1,5 @@ +package org.embeddedt.modernfix.duck; + +public interface IPaperChunkHolder { + boolean mfix$canAdvanceStatus(); +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/bugfix/paper_chunk_patches/ChunkHolderMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/paper_chunk_patches/ChunkHolderMixin.java new file mode 100644 index 00000000..0a51f6d7 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/paper_chunk_patches/ChunkHolderMixin.java @@ -0,0 +1,61 @@ +package org.embeddedt.modernfix.mixin.bugfix.paper_chunk_patches; + +import com.mojang.datafixers.util.Either; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import org.embeddedt.modernfix.duck.IPaperChunkHolder; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Mixin(ChunkHolder.class) +public abstract class ChunkHolderMixin implements IPaperChunkHolder { + + @Shadow public abstract CompletableFuture> getFutureIfPresentUnchecked(ChunkStatus arg); + + @Shadow @Final private static List CHUNK_STATUSES; + + public ChunkStatus mfix$getChunkHolderStatus() { + for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) { + CompletableFuture> future = this.getFutureIfPresentUnchecked(curr); + Either either = future.getNow(null); + if (either == null || !either.left().isPresent()) { + continue; + } + return curr; + } + + return null; + } + + public ChunkAccess mfix$getAvailableChunkNow() { + // TODO can we just getStatusFuture(EMPTY)? + for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getParent(); curr != next; curr = next, next = next.getParent()) { + CompletableFuture> future = this.getFutureIfPresentUnchecked(curr); + Either either = future.getNow(null); + if (either == null || !either.left().isPresent()) { + continue; + } + return either.left().get(); + } + return null; + } + + private static ChunkStatus mfix$getNextStatus(ChunkStatus status) { + if (status == ChunkStatus.FULL) { + return status; + } + return CHUNK_STATUSES.get(status.getIndex() + 1); + } + + @Override + public boolean mfix$canAdvanceStatus() { + ChunkStatus status = mfix$getChunkHolderStatus(); + ChunkAccess chunk = mfix$getAvailableChunkNow(); + return chunk != null && (status == null || chunk.getStatus().isOrAfter(mfix$getNextStatus(status))); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/bugfix/paper_chunk_patches/ChunkMapMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/paper_chunk_patches/ChunkMapMixin.java new file mode 100644 index 00000000..e147a76e --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/paper_chunk_patches/ChunkMapMixin.java @@ -0,0 +1,115 @@ +package org.embeddedt.modernfix.mixin.bugfix.paper_chunk_patches; + +import com.mojang.datafixers.util.Either; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.*; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.thread.BlockableEventLoop; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import net.minecraftforge.server.ServerLifecycleHooks; +import org.embeddedt.modernfix.duck.IPaperChunkHolder; +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.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Function; + + +@Mixin(ChunkMap.class) +public abstract class ChunkMapMixin { + @Shadow @Final private BlockableEventLoop mainThreadExecutor; + + @Shadow @Final private ChunkMap.DistanceManager distanceManager; + + @Shadow protected abstract CompletableFuture> protoChunkToFullChunk(ChunkHolder arg); + + @Shadow @Final private ServerLevel level; + @Shadow @Final private ThreadedLevelLightEngine lightEngine; + @Shadow @Final private ChunkProgressListener progressListener; + + @Shadow protected abstract CompletableFuture> scheduleChunkGeneration(ChunkHolder chunkHolder, ChunkStatus chunkStatus); + + @Shadow @Final private StructureTemplateManager structureTemplateManager; + private Executor mainInvokingExecutor; + + @Inject(method = "", at = @At("RETURN"), cancellable = true) + private void setup(CallbackInfo ci) { + this.mainInvokingExecutor = (runnable) -> { + if(ServerLifecycleHooks.getCurrentServer().isSameThread()) + runnable.run(); + else + this.mainThreadExecutor.execute(runnable); + }; + } + + + /* https://github.com/PaperMC/Paper/blob/ver/1.17.1/patches/server/0752-Fix-chunks-refusing-to-unload-at-low-TPS.patch */ + @ModifyArg(method = "prepareAccessibleChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1) + private Executor useMainThreadExecutor(Executor executor) { + return this.mainThreadExecutor; + } + + /* https://github.com/PaperMC/Paper/blob/master/patches/removed/1.19.2-legacy-chunksystem/0482-Improve-Chunk-Status-Transition-Speed.patch */ + @ModifyArg(method = "prepareEntityTickingChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1) + private Executor useMainInvokingExecutor(Executor executor) { + return this.mainInvokingExecutor; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Redirect(method = "scheduleChunkGeneration", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenComposeAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;")) + private CompletableFuture skipWorkerIfPossible(CompletableFuture inputFuture, Function function, Executor executor, ChunkHolder holder) { + Executor targetExecutor = (runnable) -> { + if(((IPaperChunkHolder)holder).mfix$canAdvanceStatus()) { + this.mainInvokingExecutor.execute(runnable); + return; + } + executor.execute(runnable); + }; + return inputFuture.thenComposeAsync(function, targetExecutor); + } + + /** + * @author embeddedt + * @reason revert 1.17 chunk system changes, significantly reduces time and RAM needed to load chunks + */ + @Inject(method = "schedule", at = @At("HEAD"), cancellable = true) + private void useLegacySchedulingLogic(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable>> cir) { + if(requiredStatus != ChunkStatus.EMPTY) { + ChunkPos chunkpos = holder.getPos(); + CompletableFuture> future = holder.getOrScheduleFuture(requiredStatus.getParent(), (ChunkMap)(Object)this); + cir.setReturnValue(future.thenComposeAsync((either) -> { + Optional optional = either.left(); + if(!optional.isPresent()) + return CompletableFuture.completedFuture(either); + + if (requiredStatus == ChunkStatus.LIGHT) { + this.distanceManager.addTicket(TicketType.LIGHT, chunkpos, 33 + ChunkStatus.getDistance(ChunkStatus.LIGHT), chunkpos); + } + + // from original method + if (optional.get().getStatus().isOrAfter(requiredStatus)) { + CompletableFuture> completablefuture = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (arg2) -> { + return this.protoChunkToFullChunk(holder); + }, (ChunkAccess)optional.get()); + this.progressListener.onStatusChange(chunkpos, requiredStatus); + return completablefuture; + } else { + return this.scheduleChunkGeneration(holder, requiredStatus); + } + }, this.mainThreadExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainThreadExecutor)); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/bugfix/paper_chunk_patches/SortedArraySetMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/paper_chunk_patches/SortedArraySetMixin.java new file mode 100644 index 00000000..ea9e9167 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/bugfix/paper_chunk_patches/SortedArraySetMixin.java @@ -0,0 +1,51 @@ +package org.embeddedt.modernfix.mixin.bugfix.paper_chunk_patches; + +import net.minecraft.util.SortedArraySet; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.function.Predicate; + +@Mixin(SortedArraySet.class) +public abstract class SortedArraySetMixin extends AbstractSet { + @Shadow private int size; + + @Shadow private T[] contents; + + // Paper start - optimise removeIf + @Override + public boolean removeIf(Predicate filter) { + // prev. impl used an iterator, which could be n^2 and creates garbage + int i = 0, len = this.size; + T[] backingArray = this.contents; + + for (;;) { + if (i >= len) { + return false; + } + if (!filter.test(backingArray[i])) { + ++i; + continue; + } + break; + } + + // we only want to write back to backingArray if we really need to + + int lastIndex = i; // this is where new elements are shifted to + + for (; i < len; ++i) { + T curr = backingArray[i]; + if (!filter.test(curr)) { // if test throws we're screwed + backingArray[lastIndex++] = curr; + } + } + + // cleanup end + Arrays.fill(backingArray, lastIndex, len, null); + this.size = lastIndex; + return true; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_font_loading/LegacyUnicodeBitmapsProviderMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_font_loading/LegacyUnicodeBitmapsProviderMixin.java index beb327b7..53dfe159 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_font_loading/LegacyUnicodeBitmapsProviderMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/faster_font_loading/LegacyUnicodeBitmapsProviderMixin.java @@ -43,6 +43,11 @@ public abstract class LegacyUnicodeBitmapsProviderMixin { return image; } + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;close()V")) + private void skipCloseNativeImage(NativeImage image) { + /* we can't close here, as the image has been stored for use later */ + } + @Inject(method = "", at = @At("RETURN")) private void clearLocation(CallbackInfo ci) { currentCharIdx = null; diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 983580f6..ca563f66 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -27,3 +27,5 @@ public net.minecraft.world.level.block.state.BlockBehaviour$Properties f_60888_ public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor public net.minecraft.nbt.CompoundTag (Ljava/util/Map;)V # public net.minecraft.client.renderer.texture.TextureAtlasSprite (Lnet/minecraft/client/renderer/texture/TextureAtlas;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite$Info;IIIIILcom/mojang/blaze3d/platform/NativeImage;)V # +public net.minecraft.server.level.DistanceManager m_140792_(Lnet/minecraft/server/level/TicketType;Lnet/minecraft/world/level/ChunkPos;ILjava/lang/Object;)V # addTicket +public net.minecraft.server.level.ChunkMap$DistanceManager \ No newline at end of file diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index c78c1783..84cf424d 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -11,6 +11,9 @@ "perf.modern_resourcepacks.VanillaPackResourcesMixin", "perf.modern_resourcepacks.PathPackResourcesMixin", "perf.modern_resourcepacks.ResourceCacheManagerMixin", + "bugfix.paper_chunk_patches.ChunkMapMixin", + "bugfix.paper_chunk_patches.ChunkHolderMixin", + "bugfix.paper_chunk_patches.SortedArraySetMixin", "perf.dynamic_structure_manager.StructureManagerMixin", "bugfix.chunk_deadlock.ServerChunkCacheMixin", "perf.dedicated_reload_executor.MinecraftServerMixin",