diff --git a/src/main/java/carpet/fakes/ChunkHolderInterface.java b/src/main/java/carpet/fakes/ChunkHolderInterface.java new file mode 100644 index 00000000..ae28edc9 --- /dev/null +++ b/src/main/java/carpet/fakes/ChunkHolderInterface.java @@ -0,0 +1,14 @@ +package carpet.fakes; + +import com.mojang.datafixers.util.Either; +import net.minecraft.server.world.ChunkHolder; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.thread.ThreadExecutor; +import net.minecraft.world.chunk.Chunk; + +import java.util.concurrent.CompletableFuture; + +public interface ChunkHolderInterface +{ + CompletableFuture> setDefaultProtoChunk(ChunkPos chpos, ThreadExecutor executor); +} diff --git a/src/main/java/carpet/fakes/ThreadedAnvilChunkStorageInterface.java b/src/main/java/carpet/fakes/ThreadedAnvilChunkStorageInterface.java index 87a31092..32e98484 100644 --- a/src/main/java/carpet/fakes/ThreadedAnvilChunkStorageInterface.java +++ b/src/main/java/carpet/fakes/ThreadedAnvilChunkStorageInterface.java @@ -4,7 +4,5 @@ import net.minecraft.util.math.ChunkPos; public interface ThreadedAnvilChunkStorageInterface { - //boolean isChunkLoaded(ChunkPos chpos); - //void forceUnloadChunk(ChunkPos chpos); void regenerateChunk(ChunkPos chpos); } diff --git a/src/main/java/carpet/mixins/ChunkHolder_scarpetChunkCreationMixin.java b/src/main/java/carpet/mixins/ChunkHolder_scarpetChunkCreationMixin.java new file mode 100644 index 00000000..91607c60 --- /dev/null +++ b/src/main/java/carpet/mixins/ChunkHolder_scarpetChunkCreationMixin.java @@ -0,0 +1,38 @@ +package carpet.mixins; + +import carpet.fakes.ChunkHolderInterface; +import com.mojang.datafixers.util.Either; +import net.minecraft.server.world.ChunkHolder; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.thread.ThreadExecutor; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.ProtoChunk; +import net.minecraft.world.chunk.UpgradeData; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReferenceArray; + +@Mixin(ChunkHolder.class) +public abstract class ChunkHolder_scarpetChunkCreationMixin implements ChunkHolderInterface +{ + @Shadow protected abstract void updateFuture(CompletableFuture> newChunkFuture); + + @Shadow @Final private AtomicReferenceArray>> futuresByStatus; + + @Override + public CompletableFuture> setDefaultProtoChunk(ChunkPos chpos, ThreadExecutor executor) + { + int i = ChunkStatus.EMPTY.getIndex(); + CompletableFuture> completableFuture2 = CompletableFuture.supplyAsync( + () -> Either.left(new ProtoChunk(chpos, UpgradeData.NO_UPGRADE_DATA)), + executor + ); + updateFuture(completableFuture2); + futuresByStatus.set(i, completableFuture2); + return completableFuture2; + } +} diff --git a/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java b/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java index e1b8cd2b..357c0444 100644 --- a/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java +++ b/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java @@ -1,16 +1,22 @@ package carpet.mixins; +import carpet.fakes.ChunkHolderInterface; import carpet.fakes.ThreadedAnvilChunkStorageInterface; +import com.mojang.datafixers.util.Either; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.server.WorldGenerationProgressListener; import net.minecraft.server.world.ChunkHolder; import net.minecraft.server.world.ChunkTaskPrioritySystem; import net.minecraft.server.world.ServerLightingProvider; import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ThreadedAnvilChunkStorage; import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.thread.MessageListener; +import net.minecraft.util.thread.ThreadExecutor; import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.WorldChunk; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -20,6 +26,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.Queue; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import static carpet.script.CarpetEventServer.Event.CHUNK_GENERATED; @@ -46,6 +53,18 @@ public abstract class ThreadedAnvilChunkStorage_scarpetChunkCreationMixin implem @Shadow @Final private ChunkTaskPrioritySystem chunkTaskPrioritySystem; + @Shadow protected abstract CompletableFuture> convertToFullChunk(ChunkHolder chunkHolder); + + @Shadow public abstract CompletableFuture> createChunkFuture(ChunkHolder chunkHolder, ChunkStatus chunkStatus); + + @Shadow @Final private ThreadExecutor mainThreadExecutor; + + @Shadow protected abstract boolean save(Chunk chunk); + + @Shadow @Final private WorldGenerationProgressListener worldGenerationProgressListener; + + @Shadow @Final private MessageListener> worldgenExecutor; + //in method_20617 //method_19534(Lnet/minecraft/server/world/ChunkHolder;Lnet/minecraft/world/chunk/Chunk;)Ljava/util/concurrent/CompletableFuture; @Inject(method = "method_19534", at = @At( @@ -63,25 +82,62 @@ public abstract class ThreadedAnvilChunkStorage_scarpetChunkCreationMixin implem public void regenerateChunk(ChunkPos chpos) { Long id = chpos.toLong(); - if (loadedChunks.contains(id)) - { - //unload chunk first - unloadedChunks.remove(id); - ChunkHolder chunkHolder = (ChunkHolder)currentChunkHolders.remove(id); - if (chunkHolder != null) { - field_18807.put(id, chunkHolder); - chunkHolderListDirty = true; - method_20458(id, chunkHolder); + //flushning all tasks on the thread executor so all futures are ready - we don't want any deadlocks + mainThreadExecutor.runTasks(() -> mainThreadExecutor.getTaskCount() == 0); + // save all pending chunks + // hopefully that would flush all chunk writes, including current chunk, if exists + Runnable runnable; + while((runnable = field_19343.poll()) != null) { + runnable.run(); + } + + //chunk is currently in memory in some shape or form / loaded / unloaded / queued / cached + ChunkHolder chunkHolder = currentChunkHolders.remove(id); + if (chunkHolder != null) { + //method_20458(id, chunkHolder); // saving chunk // skipping entities etc + Chunk chunk = null; + try + { + // fingers crossed + chunk = chunkHolder.getFuture().get(); } - Runnable runnable; - while((runnable = (Runnable) field_19343.poll()) != null) { - runnable.run(); + catch (InterruptedException | ExecutionException e) + { } + // if (field_18807.remove(id, chunkHolder) && chunk != null) { + if (chunk != null) { + if (chunk instanceof WorldChunk) { + ((WorldChunk)chunk).setLoadedToWorld(false); + } + save(chunk); + if (this.loadedChunks.remove(id) && chunk instanceof WorldChunk) { + WorldChunk worldChunk = (WorldChunk)chunk; + //apparently that doesn't remove them for the client + this.world.unloadEntities(worldChunk); + } + //this.serverLightingProvider.updateChunkStatus(chunk.getPos()); + serverLightingProvider.tick(); + worldGenerationProgressListener.setChunkStatus(chunk.getPos(), (ChunkStatus)null); } } + //getting rid of all signs + this.chunkHolderListDirty = true; + currentChunkHolders.remove(id); + unloadedChunks.remove(id); + loadedChunks.remove(id); + field_18807.remove(id); + ChunkHolder newHolder = new ChunkHolder(chpos, 0,serverLightingProvider, chunkTaskPrioritySystem, (ChunkHolder.PlayersWatchingChunkProvider) this); this.currentChunkHolders.put(id, newHolder); - this.chunkHolderListDirty = true; - + loadedChunks.add(id); + ((ChunkHolderInterface)newHolder).setDefaultProtoChunk(chpos, mainThreadExecutor); + convertToFullChunk(newHolder); + // running all pending tasks + mainThreadExecutor.runTasks(() -> mainThreadExecutor.getTaskCount() == 0); + // save all pending chunks / potentially only one max - ours + // hopefully that would flush all chunk writes, including current chink + while((runnable = field_19343.poll()) != null) { + runnable.run(); + } } } diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index d0cb3874..f74724a6 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -4,6 +4,7 @@ import carpet.CarpetServer; import carpet.fakes.MinecraftServerInterface; import carpet.fakes.BiomeArrayInterface; import carpet.fakes.StatTypeInterface; +import carpet.fakes.ThreadedAnvilChunkStorageInterface; import carpet.helpers.FeatureGenerator; import carpet.mixins.ChunkTicketManager_scarpetMixin; import carpet.mixins.PointOfInterest_scarpetMixin; @@ -1674,16 +1675,21 @@ public class CarpetExpression return (_c, _t) -> ret; }); - /*this.expr.addLazyFunction("reset_chunk", -1, (c, t, lv) -> + this.expr.addLazyFunction("reset_chunk", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; BlockValue.LocatorResult locator = BlockValue.fromParams(cc, lv, 0); ServerWorld world = cc.s.getWorld(); BlockPos pos = locator.block.getPos(); - ThreadedAnvilChunkStorage - - });*/ + ((CarpetContext)c).s.getMinecraftServer().submitAndJoin( () -> + { + ((ThreadedAnvilChunkStorageInterface) world.getChunkManager().threadedAnvilChunkStorage).regenerateChunk(new ChunkPos(pos)); + world.getChunk(pos); + this.forceChunkUpdate(pos, world); + }); + return LazyValue.NULL; + }); } diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 816fb922..c0d2cb71 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -27,6 +27,7 @@ "ThreadedAnvilChunkStorage_tickMixin", "ThreadedAnvilChunkStorage_scarpetChunkCreationMixin", + "ChunkHolder_scarpetChunkCreationMixin", "MobEntityMixin", "ServerWorld_unloadedEntityMixin",