11package org .embeddedt .modernfix .common .mixin .bugfix .paper_chunk_patches ;
22
3+ import com .llamalad7 .mixinextras .injector .wrapoperation .Operation ;
4+ import com .llamalad7 .mixinextras .injector .wrapoperation .WrapOperation ;
35import com .mojang .datafixers .util .Either ;
46import net .minecraft .server .level .*;
5- import net .minecraft .server .level .progress .ChunkProgressListener ;
67import net .minecraft .util .thread .BlockableEventLoop ;
7- import net .minecraft .world .level .ChunkPos ;
88import net .minecraft .world .level .chunk .ChunkAccess ;
99import net .minecraft .world .level .chunk .ChunkStatus ;
10- import net .minecraft .world .level .levelgen .structure .templatesystem .StructureTemplateManager ;
1110import org .spongepowered .asm .mixin .Final ;
1211import org .spongepowered .asm .mixin .Mixin ;
1312import org .spongepowered .asm .mixin .Shadow ;
1413import org .spongepowered .asm .mixin .injection .At ;
15- import org .spongepowered .asm .mixin .injection .Inject ;
1614import org .spongepowered .asm .mixin .injection .ModifyArg ;
17- import org .spongepowered .asm .mixin .injection .callback .CallbackInfoReturnable ;
1815
19- import java .util .Optional ;
2016import java .util .concurrent .CompletableFuture ;
2117import java .util .concurrent .Executor ;
2218
2521public abstract class ChunkMapMixin {
2622 @ Shadow @ Final private BlockableEventLoop <Runnable > mainThreadExecutor ;
2723
28- @ Shadow @ Final private ChunkMap .DistanceManager distanceManager ;
29-
30- @ Shadow protected abstract CompletableFuture <Either <ChunkAccess , ChunkHolder .ChunkLoadingFailure >> protoChunkToFullChunk (ChunkHolder arg );
31-
32- @ Shadow @ Final private ServerLevel level ;
33- @ Shadow @ Final private ThreadedLevelLightEngine lightEngine ;
34- @ Shadow @ Final private ChunkProgressListener progressListener ;
35-
36- @ Shadow protected abstract CompletableFuture <Either <ChunkAccess , ChunkHolder .ChunkLoadingFailure >> scheduleChunkGeneration (ChunkHolder chunkHolder , ChunkStatus chunkStatus );
37-
38- @ Shadow @ Final private StructureTemplateManager structureTemplateManager ;
39-
4024 /* https://github.com/PaperMC/Paper/blob/ver/1.17.1/patches/server/0752-Fix-chunks-refusing-to-unload-at-low-TPS.patch */
4125 @ 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 )
4226 private Executor useMainThreadExecutor (Executor executor ) {
@@ -45,31 +29,24 @@ private Executor useMainThreadExecutor(Executor executor) {
4529
4630 /**
4731 * @author embeddedt
48- * @reason revert 1.17 chunk system changes, significantly reduces time and RAM needed to load chunks
32+ * @reason 1.17+ uses getNow to check if the parent future is ready, and calls scheduleChunkGeneration as soon as
33+ * it is found to not be ready. In the latter scenario, a massive number of extra CompletableFutures are allocated
34+ * even if they are not actually necessary if the future is waited for. To prevent this, if the parent future
35+ * is not done, we wait for it to complete and then retry schedule(). This will either detect an adequate
36+ * status and return a loading future, or re-enter this injector with the parent future completed, in which case
37+ * we proceed to schedule generation as originally requested.
4938 */
50- @ Inject (method = "schedule" , at = @ At ("HEAD" ), cancellable = true )
51- private void useLegacySchedulingLogic (ChunkHolder holder , ChunkStatus requiredStatus , CallbackInfoReturnable <CompletableFuture <Either <ChunkAccess , ChunkHolder .ChunkLoadingFailure >>> cir ) {
52- if (requiredStatus != ChunkStatus .EMPTY && !requiredStatus .hasLoadDependencies ()) {
53- ChunkPos chunkpos = holder .getPos ();
54- CompletableFuture <Either <ChunkAccess , ChunkHolder .ChunkLoadingFailure >> future = holder .getOrScheduleFuture (requiredStatus .getParent (), (ChunkMap )(Object )this );
55- cir .setReturnValue (future .thenComposeAsync ((either ) -> {
56- Optional <ChunkAccess > optional = either .left ();
57-
58- if (requiredStatus == ChunkStatus .LIGHT ) {
59- this .distanceManager .addTicket (TicketType .LIGHT , chunkpos , 33 + ChunkStatus .getDistance (ChunkStatus .LIGHT ), chunkpos );
60- }
61-
62- // from original method
63- if (optional .isPresent () && optional .get ().getStatus ().isOrAfter (requiredStatus )) {
64- CompletableFuture <Either <ChunkAccess , ChunkHolder .ChunkLoadingFailure >> completablefuture = requiredStatus .load (this .level , this .structureTemplateManager , this .lightEngine , (arg2 ) -> {
65- return this .protoChunkToFullChunk (holder );
66- }, (ChunkAccess )optional .get ());
67- this .progressListener .onStatusChange (chunkpos , requiredStatus );
68- return completablefuture ;
69- } else {
70- return this .scheduleChunkGeneration (holder , requiredStatus );
71- }
72- }, this .mainThreadExecutor ).thenComposeAsync (CompletableFuture ::completedFuture , this .mainThreadExecutor ));
39+ @ WrapOperation (method = "schedule" , at = @ At (value = "INVOKE" , target = "Lnet/minecraft/server/level/ChunkMap;scheduleChunkGeneration(Lnet/minecraft/server/level/ChunkHolder;Lnet/minecraft/world/level/chunk/ChunkStatus;)Ljava/util/concurrent/CompletableFuture;" ))
40+ private CompletableFuture <Either <ChunkAccess , ChunkHolder .ChunkLoadingFailure >> mfix$avoidSchedulingGenerationPrematurely (ChunkMap map , ChunkHolder holder , ChunkStatus status , Operation <CompletableFuture <Either <ChunkAccess , ChunkHolder .ChunkLoadingFailure >>> original ) {
41+ if (!status .hasLoadDependencies ()) {
42+ var parentFuture = holder .getOrScheduleFuture (status .getParent (), map );
43+ if (!parentFuture .isDone ()) {
44+ return parentFuture .thenComposeAsync (
45+ either -> map .schedule (holder , status ),
46+ this .mainThreadExecutor
47+ );
48+ }
7349 }
50+ return original .call (map , holder , status );
7451 }
7552}
0 commit comments