diff --git a/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/RunningWork.java b/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/RunningWork.java new file mode 100644 index 000000000..37848ff0d --- /dev/null +++ b/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/RunningWork.java @@ -0,0 +1,7 @@ +package com.ishland.c2me.base.common.threadstate; + +public interface RunningWork { + + String toString(); + +} diff --git a/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/SyncLoadWork.java b/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/SyncLoadWork.java new file mode 100644 index 000000000..96d72c3e8 --- /dev/null +++ b/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/SyncLoadWork.java @@ -0,0 +1,14 @@ +package com.ishland.c2me.base.common.threadstate; + +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.chunk.ChunkStatus; + +public record SyncLoadWork(ServerWorld world, ChunkPos chunkPos, ChunkStatus targetStatus, boolean create) implements RunningWork { + + @Override + public String toString() { + return String.format("Sync load chunk %s to status %s in world %s (create=%s)", chunkPos, targetStatus, world.getRegistryKey().getValue(), create); + } + +} diff --git a/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/ThreadInstrumentation.java b/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/ThreadInstrumentation.java new file mode 100644 index 000000000..368570008 --- /dev/null +++ b/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/ThreadInstrumentation.java @@ -0,0 +1,80 @@ +package com.ishland.c2me.base.common.threadstate; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import java.lang.management.ThreadInfo; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class ThreadInstrumentation { + + private static final ScheduledExecutorService CLEANER = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder() + .setNameFormat("ThreadStateHolderCleaner") + .setDaemon(true) + .build() + ); + + private static final ConcurrentHashMap threadStateMap = new ConcurrentHashMap<>(); + + private static final ThreadLocal threadStateThreadLocal = ThreadLocal.withInitial(() -> getOrCreate(Thread.currentThread())); + + static { + CLEANER.scheduleAtFixedRate( + () -> threadStateMap.entrySet().removeIf(entry -> !entry.getKey().isAlive()), + 30, + 30, + TimeUnit.SECONDS + ); + } + + public static ThreadState getOrCreate(Thread thread) { + return threadStateMap.computeIfAbsent(thread, unused -> new ThreadState()); + } + + public static ThreadState get(Thread thread) { + return threadStateMap.get(thread); + } + + public static ThreadState getCurrent() { + return threadStateThreadLocal.get(); + } + + public static String printState(ThreadInfo threadInfo) { + return printState(threadInfo.getThreadName(), threadInfo.getThreadId(), findFromTid(threadInfo.getThreadId())); + } + + public static Set> entrySet() { + return Collections.unmodifiableSet(threadStateMap.entrySet()); + } + + public static String printState(String name, long tid, ThreadState state) { + if (state != null) { + RunningWork[] runningWorks = state.toArray(); + if (runningWorks.length != 0) { + StringBuilder builder = new StringBuilder(); + builder.append("Task trace for thread \"").append(name).append("\" Id=").append(tid).append(" (obtained on a best-effort basis)\n"); + for (RunningWork runningWork : runningWorks) { + builder.append(runningWork.toString().indent(4)).append("\n"); + } + return builder.toString(); + } + } + return null; + } + + private static ThreadState findFromTid(long tid) { + for (Map.Entry entry : threadStateMap.entrySet()) { + if (entry.getKey().threadId() == tid) { + return entry.getValue(); + } + } + return null; + } + +} diff --git a/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/ThreadState.java b/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/ThreadState.java new file mode 100644 index 000000000..8359eb6ab --- /dev/null +++ b/c2me-base/src/main/java/com/ishland/c2me/base/common/threadstate/ThreadState.java @@ -0,0 +1,62 @@ +package com.ishland.c2me.base.common.threadstate; + +import java.io.Closeable; +import java.util.ArrayDeque; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class ThreadState { + + private final ArrayDeque runningWorks = new ArrayDeque<>(); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + public void push(RunningWork runningWork) { + lock.writeLock().lock(); + try { + runningWorks.push(runningWork); + } finally { + lock.writeLock().unlock(); + } + } + + public void pop(RunningWork runningWork) { + lock.writeLock().lock(); + try { + RunningWork popped = runningWorks.peek(); + if (popped != runningWork) { + IllegalArgumentException exception = new IllegalArgumentException("Corrupt ThreadState"); + exception.printStackTrace(); + throw exception; + } + runningWorks.pop(); + } finally { + lock.writeLock().unlock(); + } + } + + public WorkClosable begin(RunningWork runningWork) { + lock.writeLock().lock(); + try { + runningWorks.push(runningWork); + return new WorkClosable(this, runningWork); + } finally { + lock.writeLock().unlock(); + } + } + + public RunningWork[] toArray() { + lock.readLock().lock(); + try { + return runningWorks.toArray(RunningWork[]::new); + } finally { + lock.readLock().unlock(); + } + } + + public static record WorkClosable(ThreadState state, RunningWork work) implements Closeable { + @Override + public void close() { + state.pop(work); + } + } + +} diff --git a/c2me-base/src/main/java/com/ishland/c2me/base/mixin/priority/MixinServerChunkManager.java b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/instrumentation/MixinServerChunkManager.java similarity index 53% rename from c2me-base/src/main/java/com/ishland/c2me/base/mixin/priority/MixinServerChunkManager.java rename to c2me-base/src/main/java/com/ishland/c2me/base/mixin/instrumentation/MixinServerChunkManager.java index 59b9fa39a..c0b9cfe01 100644 --- a/c2me-base/src/main/java/com/ishland/c2me/base/mixin/priority/MixinServerChunkManager.java +++ b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/instrumentation/MixinServerChunkManager.java @@ -1,10 +1,16 @@ -package com.ishland.c2me.base.mixin.priority; +package com.ishland.c2me.base.mixin.instrumentation; import com.ishland.c2me.base.common.scheduler.ISyncLoadManager; import com.ishland.c2me.base.common.scheduler.IVanillaChunkManager; +import com.ishland.c2me.base.common.threadstate.SyncLoadWork; +import com.ishland.c2me.base.common.threadstate.ThreadInstrumentation; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.server.world.ChunkHolder; import net.minecraft.server.world.ServerChunkLoadingManager; import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkStatus; @@ -15,9 +21,6 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; 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.CallbackInfoReturnable; import java.util.function.BooleanSupplier; @@ -36,26 +39,41 @@ public abstract class MixinServerChunkManager implements ISyncLoadManager { protected abstract ChunkHolder getChunkHolder(long pos); @Shadow @Final public ServerChunkLoadingManager chunkLoadingManager; + @Shadow @Final private ServerWorld world; @Unique private volatile ChunkPos currentSyncLoadChunk = null; @Unique private volatile long syncLoadNanos = 0; @Dynamic - @Redirect(method = { + @WrapOperation(method = { "getChunk(IILnet/minecraft/world/chunk/ChunkStatus;Z)Lnet/minecraft/world/chunk/Chunk;", "getChunkBlocking(IILnet/minecraft/world/chunk/ChunkStatus;Z)Lnet/minecraft/world/chunk/Chunk;", }, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkManager$MainThreadExecutor;runTasks(Ljava/util/function/BooleanSupplier;)V"), require = 0) - private void beforeAwaitChunk(ServerChunkManager.MainThreadExecutor instance, BooleanSupplier supplier, int x, int z, ChunkStatus leastStatus, boolean create) { - if (Thread.currentThread() != this.serverThread || supplier.getAsBoolean()) return; + private void instrumentAwaitChunk(ServerChunkManager.MainThreadExecutor instance, BooleanSupplier stopCondition, Operation original, int x, int z, ChunkStatus leastStatus, boolean create) { + if (Thread.currentThread() != this.serverThread || stopCondition.getAsBoolean()) return; this.currentSyncLoadChunk = new ChunkPos(x, z); syncLoadNanos = System.nanoTime(); ((IVanillaChunkManager) this.chunkLoadingManager).c2me$getSchedulingManager().setCurrentSyncLoad(this.currentSyncLoadChunk); - instance.runTasks(supplier); - ((IVanillaChunkManager) this.chunkLoadingManager).c2me$getSchedulingManager().setCurrentSyncLoad(null); - this.currentSyncLoadChunk = null; + try (var ignored = ThreadInstrumentation.getCurrent().begin(new SyncLoadWork(this.world, new ChunkPos(x, z), leastStatus, create))) { + original.call(instance, stopCondition); + } finally { + ((IVanillaChunkManager) this.chunkLoadingManager).c2me$getSchedulingManager().setCurrentSyncLoad(null); + this.currentSyncLoadChunk = null; + } + } + + @WrapMethod(method = "getChunk(IILnet/minecraft/world/chunk/ChunkStatus;Z)Lnet/minecraft/world/chunk/Chunk;") + private Chunk instrumentGetChunk(int x, int z, ChunkStatus leastStatus, boolean create, Operation original) { + if (Thread.currentThread() != this.serverThread) { + try (var ignored = ThreadInstrumentation.getCurrent().begin(new SyncLoadWork(this.world, new ChunkPos(x, z), leastStatus, create))) { + return original.call(x, z, leastStatus, create); + } + } else { + return original.call(x, z, leastStatus, create); + } } @Override diff --git a/c2me-base/src/main/java/com/ishland/c2me/base/mixin/report/MixinDedicatedServerWatchdog.java b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/report/MixinDedicatedServerWatchdog.java new file mode 100644 index 000000000..d49f808dd --- /dev/null +++ b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/report/MixinDedicatedServerWatchdog.java @@ -0,0 +1,60 @@ +package com.ishland.c2me.base.mixin.report; + +import com.ishland.c2me.base.common.threadstate.ThreadInstrumentation; +import com.ishland.c2me.base.common.threadstate.ThreadState; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.server.dedicated.DedicatedServerWatchdog; +import net.minecraft.util.crash.CrashReport; +import net.minecraft.util.crash.CrashReportSection; +import org.slf4j.Logger; +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.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.Map; + +@Mixin(DedicatedServerWatchdog.class) +public class MixinDedicatedServerWatchdog { + + @Shadow @Final private static Logger LOGGER; + + @Inject(method = "createCrashReport", at = @At(value = "INVOKE", target = "Ljava/lang/management/ThreadInfo;getThreadId()J")) + private static void prependThreadInstrumentation(String message, long threadId, CallbackInfoReturnable cir, @Local StringBuilder stringBuilder, @Local ThreadInfo threadInfo) { + String state = null; + try { + state = ThreadInstrumentation.printState(threadInfo); + } catch (Throwable t) { + LOGGER.error("Failed to fetch state for thread {}", threadInfo); + } + if (state != null) { + stringBuilder.append(state); + } + } + + @Inject(method = "createCrashReport", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/crash/CrashReport;addElement(Ljava/lang/String;)Lnet/minecraft/util/crash/CrashReportSection;", ordinal = 0), locals = LocalCapture.CAPTURE_FAILHARD) + private static void addInstrumentationData(String message, long threadId, CallbackInfoReturnable cir, ThreadMXBean threadMXBean, ThreadInfo[] threadInfos, StringBuilder stringBuilder, Error error, CrashReport crashReport) { + CrashReportSection section = crashReport.addElement("Thread trace dump (obtained on a best-effort basis)"); + try { + for (Map.Entry entry : ThreadInstrumentation.entrySet()) { + try { + Thread thread = entry.getKey(); + String state = ThreadInstrumentation.printState(thread.getName(), thread.threadId(), entry.getValue()); + if (state != null) { + section.add(thread.getName(), state); + } + } catch (Throwable t) { + LOGGER.error("Failed to dumping state for thread {}", entry.getKey(), t); + } + } + } catch (Throwable t) { + LOGGER.error("Failed to dump all known thread states", t); + } + } + +} diff --git a/c2me-base/src/main/resources/c2me-base.mixins.json b/c2me-base/src/main/resources/c2me-base.mixins.json index 94b2178c4..18f814343 100644 --- a/c2me-base/src/main/resources/c2me-base.mixins.json +++ b/c2me-base/src/main/resources/c2me-base.mixins.json @@ -50,7 +50,8 @@ "access.IXoroshiro128PlusPlusRandom", "access.IXoroshiro128PlusPlusRandomDeriver", "access.IXoroshiro128PlusPlusRandomImpl", - "priority.MixinServerChunkManager", + "instrumentation.MixinServerChunkManager", + "report.MixinDedicatedServerWatchdog", "scheduler.MixinThreadedAnvilChunkStorage", "theinterface.MixinStorageIoWorker", "util.log4j2shutdownhookisnomore.MixinMain", diff --git a/c2me-opts-chunk-access/build.gradle b/c2me-opts-chunk-access/build.gradle deleted file mode 100644 index 11104c696..000000000 --- a/c2me-opts-chunk-access/build.gradle +++ /dev/null @@ -1,4 +0,0 @@ -moduleDependencies(project, [ - "c2me-base", -]) - diff --git a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/MixinPlugin.java b/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/MixinPlugin.java deleted file mode 100644 index 877718780..000000000 --- a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/MixinPlugin.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ishland.c2me.opts.chunk_access; - -import com.ishland.c2me.base.common.ModuleMixinPlugin; -import org.objectweb.asm.tree.ClassNode; -import org.spongepowered.asm.mixin.extensibility.IMixinInfo; - -public class MixinPlugin extends ModuleMixinPlugin { - - @Override - public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - super.postApply(targetClassName, targetClass, mixinClassName, mixinInfo); - } - -} diff --git a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/ModuleEntryPoint.java b/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/ModuleEntryPoint.java deleted file mode 100644 index 6954303cc..000000000 --- a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/ModuleEntryPoint.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ishland.c2me.opts.chunk_access; - -import com.ishland.c2me.base.common.config.ConfigSystem; - -public class ModuleEntryPoint { - - public static final boolean enabled = new ConfigSystem.ConfigAccessor() - .key("generalOptimizations.optimizeAsyncChunkRequest") - .comment("Whether to let async chunk request no longer block server thread\n" + - "(may cause incompatibility with other mods)") - .getBoolean(true, false); - -} diff --git a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/common/CurrentWorldGenState.java b/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/common/CurrentWorldGenState.java deleted file mode 100644 index 862b7d805..000000000 --- a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/common/CurrentWorldGenState.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.ishland.c2me.opts.chunk_access.common; - -import net.minecraft.world.ChunkRegion; - -public class CurrentWorldGenState { - - private static final ThreadLocal currentRegion = new ThreadLocal<>(); - - public static ChunkRegion getCurrentRegion() { - return currentRegion.get(); - } - - public static void setCurrentRegion(ChunkRegion region) { - currentRegion.set(region); - } - - public static void clearCurrentRegion() { - currentRegion.remove(); - } - -} diff --git a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/mixin/region_capture/MixinChunkGenerationStep.java b/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/mixin/region_capture/MixinChunkGenerationStep.java deleted file mode 100644 index 5de39afe2..000000000 --- a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/mixin/region_capture/MixinChunkGenerationStep.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.ishland.c2me.opts.chunk_access.mixin.region_capture; - -import com.ishland.c2me.opts.chunk_access.common.CurrentWorldGenState; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.util.collection.BoundedRegionArray; -import net.minecraft.world.ChunkRegion; -import net.minecraft.world.chunk.AbstractChunkHolder; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkGenerationContext; -import net.minecraft.world.chunk.ChunkGenerationStep; -import net.minecraft.world.chunk.GenerationTask; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; - -import java.util.concurrent.CompletableFuture; - -@Mixin(value = ChunkGenerationStep.class, priority = 1200) -public abstract class MixinChunkGenerationStep { - - @WrapOperation(method = "run", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/GenerationTask;doWork(Lnet/minecraft/world/chunk/ChunkGenerationContext;Lnet/minecraft/world/chunk/ChunkGenerationStep;Lnet/minecraft/util/collection/BoundedRegionArray;Lnet/minecraft/world/chunk/Chunk;)Ljava/util/concurrent/CompletableFuture;")) - public CompletableFuture runGenerationTask(GenerationTask instance, ChunkGenerationContext context, ChunkGenerationStep step, BoundedRegionArray chunks, Chunk chunk, Operation> original) { - final ChunkRegion chunkRegion = new ChunkRegion(context.world(), chunks, step, chunk); - try { - CurrentWorldGenState.setCurrentRegion(chunkRegion); - return original.call(instance, context, step, chunks, chunk); - } finally { - CurrentWorldGenState.clearCurrentRegion(); - } - } - -} diff --git a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/package-info.java b/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/package-info.java deleted file mode 100644 index bbd6c6a56..000000000 --- a/c2me-opts-chunk-access/src/main/java/com/ishland/c2me/opts/chunk_access/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package com.ishland.c2me.opts.chunk_access; \ No newline at end of file diff --git a/c2me-opts-chunk-access/src/main/resources/c2me-opts-chunk-access.mixins.json b/c2me-opts-chunk-access/src/main/resources/c2me-opts-chunk-access.mixins.json deleted file mode 100644 index 042ec52bf..000000000 --- a/c2me-opts-chunk-access/src/main/resources/c2me-opts-chunk-access.mixins.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "parent": "c2me.mixins.json", - "required": true, - "package": "com.ishland.c2me.opts.chunk_access.mixin", - "plugin": "com.ishland.c2me.opts.chunk_access.MixinPlugin", - "mixins": [ - "region_capture.MixinChunkGenerationStep" - ] -} diff --git a/c2me-opts-chunk-access/src/main/resources/fabric.mod.json b/c2me-opts-chunk-access/src/main/resources/fabric.mod.json deleted file mode 100644 index 5bb0596df..000000000 --- a/c2me-opts-chunk-access/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "schemaVersion": 1, - "id": "c2me-opts-chunk-access", - "version": "${version}", - "name": "Concurrent Chunk Management Engine (Optimizations/Chunk Access)", - "description": "Optimizations related to chunk access", - "environment": "*", - "mixins": [ - "c2me-opts-chunk-access.mixins.json" - ], - "depends": { - "minecraft": "*", - "fabricloader": "*", - "c2me-base": "*" - }, - - "authors": [ - "RelativityMC", - "ishland" - ], - "contact": { - "sources": "https://github.com/RelativityMC/C2ME-fabric", - "issues": "https://github.com/RelativityMC/C2ME-fabric/issues", - "discord": "https://discord.gg/Kdy8NM5HW4", - "homepage": "https://modrinth.com/mod/c2me-fabric" - }, - "license": "MIT", - "custom": { - "modmenu": { - "parent": "c2me" - } - } -} diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ReadFromDisk.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ReadFromDisk.java index 3482c0c8f..6ea33f3c1 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ReadFromDisk.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ReadFromDisk.java @@ -4,6 +4,7 @@ import com.ishland.c2me.base.common.config.ModStatuses; import com.ishland.c2me.base.common.registry.SerializerAccess; import com.ishland.c2me.base.common.theinterface.IDirectStorage; +import com.ishland.c2me.base.common.threadstate.ThreadInstrumentation; import com.ishland.c2me.base.common.util.RxJavaUtils; import com.ishland.c2me.base.mixin.access.IServerLightingProvider; import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; @@ -17,6 +18,7 @@ import com.ishland.c2me.rewrites.chunksystem.common.async_chunkio.ProtoChunkExtension; import com.ishland.c2me.rewrites.chunksystem.common.ducks.IPOIUnloading; import com.ishland.c2me.rewrites.chunksystem.common.fapi.LifecycleEventInvoker; +import com.ishland.c2me.rewrites.chunksystem.common.threadstate.ChunkTaskWork; import com.ishland.flowsched.scheduler.Cancellable; import com.ishland.flowsched.scheduler.ItemHolder; import com.ishland.flowsched.scheduler.KeyStatusPair; @@ -26,7 +28,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers; import net.minecraft.nbt.NbtCompound; import net.minecraft.registry.RegistryKeys; -import net.minecraft.server.world.ServerChunkLoadingManager; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.chunk.Chunk; @@ -63,7 +64,7 @@ public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancella return finalizeLoading(context, single); } - protected static @NotNull CompletionStage finalizeLoading(ChunkLoadingContext context, Single single) { + protected @NotNull CompletionStage finalizeLoading(ChunkLoadingContext context, Single single) { return single .doOnError(throwable -> ((IThreadedAnvilChunkStorage) context.tacs()).getWorld().getServer().onChunkLoadFailure(throwable, ((IVersionedChunkStorage) context.tacs()).invokeGetStorageKey(), context.holder().getKey())) .onErrorResumeNext(throwable -> { @@ -79,49 +80,55 @@ public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancella .toCompletionStage(null); } - protected static @NonNull Single invokeVanillaLoad(ChunkLoadingContext context) { + protected @NonNull Single invokeVanillaLoad(ChunkLoadingContext context) { return invokeInitialChunkRead(context) .observeOn(Schedulers.from(((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor())) .map(chunkSerializer -> { - if (chunkSerializer.isPresent()) { - return chunkSerializer.get().convert( - ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(), - ((IThreadedAnvilChunkStorage) context.tacs()).getPointOfInterestStorage(), - ((IVersionedChunkStorage) context.tacs()).invokeGetStorageKey(), - context.holder().getKey() - ); - } else { - return createEmptyProtoChunk(context); + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + if (chunkSerializer.isPresent()) { + return chunkSerializer.get().convert( + ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(), + ((IThreadedAnvilChunkStorage) context.tacs()).getPointOfInterestStorage(), + ((IVersionedChunkStorage) context.tacs()).invokeGetStorageKey(), + context.holder().getKey() + ); + } else { + return createEmptyProtoChunk(context); + } } }) .flatMap(protoChunk -> postChunkLoading(context, protoChunk).toSingleDefault(protoChunk)); } - protected static @NotNull Completable postChunkLoading(ChunkLoadingContext context, ProtoChunk protoChunk) { - final ServerWorld world = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); - // blending - final ChunkPos pos = context.holder().getKey(); - protoChunk = protoChunk != null ? protoChunk : new ProtoChunk(pos, UpgradeData.NO_UPGRADE_DATA, world, world.getRegistryManager().getOrThrow(RegistryKeys.BIOME), null); - if (protoChunk.getBelowZeroRetrogen() != null || protoChunk.getStatus().getChunkType() == ChunkType.PROTOCHUNK) { - ProtoChunk finalProtoChunk = protoChunk; - return Single.defer(() -> Single.fromCompletionStage(BlendingInfoUtil.getBlendingInfos(((IVersionedChunkStorage) context.tacs()).getWorker(), pos))) - .doOnSuccess(bitSets -> ((ProtoChunkExtension) finalProtoChunk).setBlendingInfo(pos, bitSets)) - .ignoreElement(); - } else { - return Completable.complete(); + protected @NotNull Completable postChunkLoading(ChunkLoadingContext context, ProtoChunk protoChunk) { + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + final ServerWorld world = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); + // blending + final ChunkPos pos = context.holder().getKey(); + protoChunk = protoChunk != null ? protoChunk : new ProtoChunk(pos, UpgradeData.NO_UPGRADE_DATA, world, world.getRegistryManager().getOrThrow(RegistryKeys.BIOME), null); + if (protoChunk.getBelowZeroRetrogen() != null || protoChunk.getStatus().getChunkType() == ChunkType.PROTOCHUNK) { + ProtoChunk finalProtoChunk = protoChunk; + return Single.defer(() -> Single.fromCompletionStage(BlendingInfoUtil.getBlendingInfos(((IVersionedChunkStorage) context.tacs()).getWorker(), pos))) + .doOnSuccess(bitSets -> ((ProtoChunkExtension) finalProtoChunk).setBlendingInfo(pos, bitSets)) + .ignoreElement(); + } else { + return Completable.complete(); + } } } - protected static @NotNull Single> invokeInitialChunkRead(ChunkLoadingContext context) { + protected @NotNull Single> invokeInitialChunkRead(ChunkLoadingContext context) { return Single.defer(() -> Single.fromCompletionStage(((IThreadedAnvilChunkStorage) context.tacs()).invokeGetUpdatedChunkNbt(context.holder().getKey()))) .map(optional -> optional.map(nbtCompound -> { - ServerWorld world = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); - SerializedChunk chunkSerializer = SerializedChunk.fromNbt(world, world.getRegistryManager(), nbtCompound); - if (chunkSerializer == null) { - LOGGER.error("Chunk file at {} is missing level data, skipping", context.holder().getKey()); - } + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + ServerWorld world = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); + SerializedChunk chunkSerializer = SerializedChunk.fromNbt(world, world.getRegistryManager(), nbtCompound); + if (chunkSerializer == null) { + LOGGER.error("Chunk file at {} is missing level data, skipping", context.holder().getKey()); + } - return chunkSerializer; + return chunkSerializer; + } })) .zipWith( Completable.defer(() -> Completable.fromCompletionStage(((IThreadedAnvilChunkStorage) context.tacs()).getPointOfInterestStorage().load(context.holder().getKey()))).toSingleDefault(ReadFromDisk.class), @@ -129,81 +136,88 @@ public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancella ); } - protected static @NotNull ProtoChunk createEmptyProtoChunk(ChunkLoadingContext context) { - final ServerWorld world = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); - return new ProtoChunk(context.holder().getKey(), UpgradeData.NO_UPGRADE_DATA, world, world.getRegistryManager().getOrThrow(RegistryKeys.BIOME), null); + protected @NotNull ProtoChunk createEmptyProtoChunk(ChunkLoadingContext context) { + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + final ServerWorld world = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); + return new ProtoChunk(context.holder().getKey(), UpgradeData.NO_UPGRADE_DATA, world, world.getRegistryManager().getOrThrow(RegistryKeys.BIOME), null); + } } @Override public CompletionStage downgradeFromThis(ChunkLoadingContext context, Cancellable cancellable) { final AtomicBoolean loadedToWorld = new AtomicBoolean(false); return syncWithLightEngine(context).thenApplyAsync(unused -> { - if (context.holder().getTargetStatus().ordinal() > this.ordinal()) { // saving cancelled -// LOGGER.info("Cancelling unload of {}", context.holder().getKey()); - cancellable.cancel(); - return CompletableFuture.failedFuture(new CancellationException()); - } - - final ChunkState chunkState = context.holder().getItem().get(); - Chunk chunk = chunkState.chunk(); - if (chunk instanceof WrapperProtoChunk protoChunk) chunk = protoChunk.getWrappedChunk(); + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, false))) { + if (context.holder().getTargetStatus().ordinal() > this.ordinal()) { // saving cancelled +// LOGGER.info("Cancelling unload of {}", context.holder().getKey()); + cancellable.cancel(); + return CompletableFuture.failedFuture(new CancellationException()); + } + final ChunkState chunkState = context.holder().getItem().get(); + Chunk chunk = chunkState.chunk(); + if (chunk instanceof WrapperProtoChunk protoChunk) chunk = protoChunk.getWrappedChunk(); - if (chunk instanceof WorldChunk worldChunk) { - loadedToWorld.set(((IWorldChunk) worldChunk).isLoadedToWorld()); - worldChunk.setLoadedToWorld(false); - } + if (chunk instanceof WorldChunk worldChunk) { + loadedToWorld.set(((IWorldChunk) worldChunk).isLoadedToWorld()); + worldChunk.setLoadedToWorld(false); + } - if (loadedToWorld.get() && ModStatuses.fabric_lifecycle_events_v1 && chunk instanceof WorldChunk worldChunk) { - LifecycleEventInvoker.invokeChunkUnload(((IThreadedAnvilChunkStorage) context.tacs()).getWorld(), worldChunk); - } + if (loadedToWorld.get() && ModStatuses.fabric_lifecycle_events_v1 && chunk instanceof WorldChunk worldChunk) { + LifecycleEventInvoker.invokeChunkUnload(((IThreadedAnvilChunkStorage) context.tacs()).getWorld(), worldChunk); + } - if ((context.holder().getFlags() & ItemHolder.FLAG_BROKEN) != 0 && chunk instanceof ProtoChunk) { // do not save broken ProtoChunks - LOGGER.warn("Not saving partially generated broken chunk {}", context.holder().getKey()); - return CompletableFuture.completedStage((Void) null); - } else if (chunk instanceof WorldChunk && !chunkState.reachedStatus().isAtLeast(ChunkStatus.FULL)) { - // do not save WorldChunks that doesn't reach full status: Vanilla behavior - // If saved, block entities will be lost - return CompletableFuture.completedStage((Void) null); - } else { - return asyncSave(context.tacs(), chunk); + if ((context.holder().getFlags() & ItemHolder.FLAG_BROKEN) != 0 && chunk instanceof ProtoChunk) { // do not save broken ProtoChunks + LOGGER.warn("Not saving partially generated broken chunk {}", context.holder().getKey()); + return CompletableFuture.completedStage((Void) null); + } else if (chunk instanceof WorldChunk && !chunkState.reachedStatus().isAtLeast(ChunkStatus.FULL)) { + // do not save WorldChunks that doesn't reach full status: Vanilla behavior + // If saved, block entities will be lost + return CompletableFuture.completedStage((Void) null); + } else { + return asyncSave(context, chunk); + } } }, ((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor()) .thenCompose(Function.identity()) .thenAcceptAsync(unused -> { - Chunk chunk = context.holder().getItem().get().chunk(); - if (chunk instanceof WrapperProtoChunk protoChunk) chunk = protoChunk.getWrappedChunk(); - if (loadedToWorld.get() && chunk instanceof WorldChunk worldChunk) { - ((IThreadedAnvilChunkStorage) context.tacs()).getWorld().unloadEntities(worldChunk); - } + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, false))) { + Chunk chunk = context.holder().getItem().get().chunk(); + if (chunk instanceof WrapperProtoChunk protoChunk) chunk = protoChunk.getWrappedChunk(); + if (loadedToWorld.get() && chunk instanceof WorldChunk worldChunk) { + ((IThreadedAnvilChunkStorage) context.tacs()).getWorld().unloadEntities(worldChunk); + } - ((IServerLightingProvider) ((IThreadedAnvilChunkStorage) context.tacs()).getLightingProvider()).invokeUpdateChunkStatus(chunk.getPos()); - ((IThreadedAnvilChunkStorage) context.tacs()).getLightingProvider().tick(); - ((IThreadedAnvilChunkStorage) context.tacs()).getWorldGenerationProgressListener().setChunkStatus(chunk.getPos(), null); - ((IThreadedAnvilChunkStorage) context.tacs()).getChunkToNextSaveTimeMs().remove(chunk.getPos().toLong()); + ((IServerLightingProvider) ((IThreadedAnvilChunkStorage) context.tacs()).getLightingProvider()).invokeUpdateChunkStatus(chunk.getPos()); + ((IThreadedAnvilChunkStorage) context.tacs()).getLightingProvider().tick(); + ((IThreadedAnvilChunkStorage) context.tacs()).getWorldGenerationProgressListener().setChunkStatus(chunk.getPos(), null); + ((IThreadedAnvilChunkStorage) context.tacs()).getChunkToNextSaveTimeMs().remove(chunk.getPos().toLong()); - ((IPOIUnloading) ((IThreadedAnvilChunkStorage) context.tacs()).getPointOfInterestStorage()).c2me$unloadPoi(context.holder().getKey()); + ((IPOIUnloading) ((IThreadedAnvilChunkStorage) context.tacs()).getPointOfInterestStorage()).c2me$unloadPoi(context.holder().getKey()); - context.holder().getItem().set(new ChunkState(null, null, null)); + context.holder().getItem().set(new ChunkState(null, null, null)); + } }, ((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor()); } - private CompletionStage asyncSave(ServerChunkLoadingManager tacs, Chunk chunk) { - ((IThreadedAnvilChunkStorage) tacs).getPointOfInterestStorage().saveChunk(chunk.getPos()); + private CompletionStage asyncSave(ChunkLoadingContext context, Chunk chunk) { + ((IThreadedAnvilChunkStorage) context.tacs()).getPointOfInterestStorage().saveChunk(chunk.getPos()); if (!chunk.tryMarkSaved()) { return CompletableFuture.completedStage(null); } else { ChunkPos chunkPos = chunk.getPos(); - SerializedChunk serializer = SerializedChunk.fromChunk(((IThreadedAnvilChunkStorage) tacs).getWorld(), chunk); + SerializedChunk serializer = SerializedChunk.fromChunk(((IThreadedAnvilChunkStorage) context.tacs()).getWorld(), chunk); return CompletableFuture.supplyAsync(() -> { - return SerializerAccess.getSerializer().serialize(serializer); + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, false))) { + return SerializerAccess.getSerializer().serialize(serializer); + } }, GlobalExecutors.prioritizedScheduler.executor(16) /* boost priority as we are serializing an unloaded chunk */) .thenCompose((either) -> { if (either.left().isPresent()) { NbtCompound nbtCompound = either.left().get(); - return tacs.setNbt(chunkPos, () -> nbtCompound); + return context.tacs().setNbt(chunkPos, () -> nbtCompound); } else { - return ((IDirectStorage) ((IVersionedChunkStorage) tacs).getWorker()).setRawChunkData(chunkPos, either.right().get()); + return ((IDirectStorage) ((IVersionedChunkStorage) context.tacs()).getWorker()).setRawChunkData(chunkPos, either.right().get()); } }); } diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ReadFromDiskAsync.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ReadFromDiskAsync.java index 3e1221cb1..14119f800 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ReadFromDiskAsync.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ReadFromDiskAsync.java @@ -1,11 +1,13 @@ package com.ishland.c2me.rewrites.chunksystem.common.statuses; +import com.ishland.c2me.base.common.threadstate.ThreadInstrumentation; import com.ishland.c2me.base.common.util.RxJavaUtils; import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; import com.ishland.c2me.base.mixin.access.IVersionedChunkStorage; import com.ishland.c2me.rewrites.chunksystem.common.ChunkLoadingContext; import com.ishland.c2me.rewrites.chunksystem.common.ChunkState; import com.ishland.c2me.rewrites.chunksystem.common.async_chunkio.ChunkIoMainThreadTaskUtils; +import com.ishland.c2me.rewrites.chunksystem.common.threadstate.ChunkTaskWork; import com.ishland.flowsched.scheduler.Cancellable; import com.ishland.flowsched.scheduler.ItemHolder; import com.ishland.flowsched.scheduler.KeyStatusPair; @@ -41,33 +43,37 @@ public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancella return finalizeLoading(context, single); } - protected static @NonNull Single invokeAsyncLoad(ChunkLoadingContext context) { + protected @NonNull Single invokeAsyncLoad(ChunkLoadingContext context) { return invokeInitialChunkRead(context) .map(chunkSerializer -> { - final ReferenceArrayList mainThreadQueue = new ReferenceArrayList<>(); - if (chunkSerializer.isPresent()) { - ChunkIoMainThreadTaskUtils.push(mainThreadQueue); - try { - return Pair.of( - chunkSerializer.get().convert( - ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(), - ((IThreadedAnvilChunkStorage) context.tacs()).getPointOfInterestStorage(), - ((IVersionedChunkStorage) context.tacs()).invokeGetStorageKey(), - context.holder().getKey() - ), - mainThreadQueue - ); - } finally { - ChunkIoMainThreadTaskUtils.pop(mainThreadQueue); + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + final ReferenceArrayList mainThreadQueue = new ReferenceArrayList<>(); + if (chunkSerializer.isPresent()) { + ChunkIoMainThreadTaskUtils.push(mainThreadQueue); + try { + return Pair.of( + chunkSerializer.get().convert( + ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(), + ((IThreadedAnvilChunkStorage) context.tacs()).getPointOfInterestStorage(), + ((IVersionedChunkStorage) context.tacs()).invokeGetStorageKey(), + context.holder().getKey() + ), + mainThreadQueue + ); + } finally { + ChunkIoMainThreadTaskUtils.pop(mainThreadQueue); + } + } else { + return Pair.of(createEmptyProtoChunk(context), mainThreadQueue); } - } else { - return Pair.of(createEmptyProtoChunk(context), mainThreadQueue); } }) .flatMap(pair -> postChunkLoading(context, pair.first()).toSingleDefault(pair)) .observeOn(Schedulers.from(((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor())) .map(pair -> { - ChunkIoMainThreadTaskUtils.drainQueue(pair.second()); + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + ChunkIoMainThreadTaskUtils.drainQueue(pair.second()); + } return pair.first(); }); } diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessible.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessible.java index fa4a6efcb..f88d67a11 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessible.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessible.java @@ -2,6 +2,7 @@ import com.google.common.base.Preconditions; import com.ishland.c2me.base.common.config.ModStatuses; +import com.ishland.c2me.base.common.threadstate.ThreadInstrumentation; import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; import com.ishland.c2me.base.mixin.access.IWorldChunk; import com.ishland.c2me.rewrites.chunksystem.common.ChunkLoadingContext; @@ -10,6 +11,7 @@ import com.ishland.c2me.rewrites.chunksystem.common.NewChunkHolderVanillaInterface; import com.ishland.c2me.rewrites.chunksystem.common.NewChunkStatus; import com.ishland.c2me.rewrites.chunksystem.common.fapi.LifecycleEventInvoker; +import com.ishland.c2me.rewrites.chunksystem.common.threadstate.ChunkTaskWork; import com.ishland.flowsched.scheduler.Cancellable; import com.ishland.flowsched.scheduler.ItemHolder; import com.ishland.flowsched.scheduler.KeyStatusPair; @@ -90,11 +92,12 @@ public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancella } return CompletableFuture.runAsync(() -> { - ServerWorld serverWorld = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); - final WorldChunk worldChunk = toFullChunk(protoChunk, serverWorld); + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + ServerWorld serverWorld = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); + final WorldChunk worldChunk = toFullChunk(protoChunk, serverWorld); - worldChunk.setLevelTypeProvider(context.holder().getUserData().get()::getLevelType); - worldChunk.setUnsavedListener(((IThreadedAnvilChunkStorage) context.tacs()).getGenerationContext().unsavedListener()); + worldChunk.setLevelTypeProvider(context.holder().getUserData().get()::getLevelType); + worldChunk.setUnsavedListener(((IThreadedAnvilChunkStorage) context.tacs()).getGenerationContext().unsavedListener()); context.holder().getItem().set(new ChunkState(worldChunk, new WrapperProtoChunk(worldChunk, false), ChunkStatus.FULL)); if (!((IWorldChunk) worldChunk).isLoadedToWorld()) { worldChunk.loadEntities(); @@ -106,11 +109,12 @@ public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancella } } - ((IThreadedAnvilChunkStorage) context.tacs()).getCurrentChunkHolders().put(context.holder().getKey().toLong(), context.holder().getUserData().get()); - ((IThreadedAnvilChunkStorage) context.tacs()).setChunkHolderListDirty(true); + ((IThreadedAnvilChunkStorage) context.tacs()).getCurrentChunkHolders().put(context.holder().getKey().toLong(), context.holder().getUserData().get()); + ((IThreadedAnvilChunkStorage) context.tacs()).setChunkHolderListDirty(true); - if (needSendChunks()) { - sendChunkToPlayer(context.tacs(), context.holder()); + if (needSendChunks()) { + sendChunkToPlayer(context.tacs(), context.holder()); + } } }, ((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor()); } diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerBlockTicking.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerBlockTicking.java index e409da5f8..ca581a4a9 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerBlockTicking.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerBlockTicking.java @@ -1,10 +1,12 @@ package com.ishland.c2me.rewrites.chunksystem.common.statuses; +import com.ishland.c2me.base.common.threadstate.ThreadInstrumentation; import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; import com.ishland.c2me.rewrites.chunksystem.common.ChunkLoadingContext; import com.ishland.c2me.rewrites.chunksystem.common.ChunkState; import com.ishland.c2me.rewrites.chunksystem.common.NewChunkHolderVanillaInterface; import com.ishland.c2me.rewrites.chunksystem.common.NewChunkStatus; +import com.ishland.c2me.rewrites.chunksystem.common.threadstate.ChunkTaskWork; import com.ishland.flowsched.scheduler.Cancellable; import com.ishland.flowsched.scheduler.ItemHolder; import com.ishland.flowsched.scheduler.KeyStatusPair; @@ -39,11 +41,13 @@ public ServerBlockTicking(int ordinal) { @Override public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancellable cancellable) { return CompletableFuture.runAsync(() -> { - final WorldChunk chunk = (WorldChunk) context.holder().getItem().get().chunk(); - chunk.runPostProcessing(((IThreadedAnvilChunkStorage) context.tacs()).getWorld()); - ((IThreadedAnvilChunkStorage) context.tacs()).getWorld().disableTickSchedulers(chunk); - sendChunkToPlayer(context); - ((IThreadedAnvilChunkStorage) context.tacs()).getTotalChunksLoadedCount().incrementAndGet(); // never decremented in vanilla + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + final WorldChunk chunk = (WorldChunk) context.holder().getItem().get().chunk(); + chunk.runPostProcessing(((IThreadedAnvilChunkStorage) context.tacs()).getWorld()); + ((IThreadedAnvilChunkStorage) context.tacs()).getWorld().disableTickSchedulers(chunk); + sendChunkToPlayer(context); + ((IThreadedAnvilChunkStorage) context.tacs()).getTotalChunksLoadedCount().incrementAndGet(); // never decremented in vanilla + } }, ((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor()); } diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/VanillaWorldGenerationDelegate.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/VanillaWorldGenerationDelegate.java index 4dac08890..b1ba07ca6 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/VanillaWorldGenerationDelegate.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/VanillaWorldGenerationDelegate.java @@ -3,10 +3,12 @@ import com.ishland.c2me.base.common.scheduler.LockTokenImpl; import com.ishland.c2me.base.common.scheduler.ScheduledTask; import com.ishland.c2me.base.common.scheduler.SchedulingManager; +import com.ishland.c2me.base.common.threadstate.ThreadInstrumentation; import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; import com.ishland.c2me.rewrites.chunksystem.common.ChunkLoadingContext; import com.ishland.c2me.rewrites.chunksystem.common.ChunkState; import com.ishland.c2me.rewrites.chunksystem.common.NewChunkStatus; +import com.ishland.c2me.rewrites.chunksystem.common.threadstate.ChunkTaskWork; import com.ishland.flowsched.executor.LockToken; import com.ishland.flowsched.scheduler.Cancellable; import com.ishland.flowsched.scheduler.ItemHolder; @@ -14,7 +16,13 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.minecraft.util.math.ChunkPos; -import net.minecraft.world.chunk.*; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkGenerationContext; +import net.minecraft.world.chunk.ChunkGenerationStep; +import net.minecraft.world.chunk.ChunkGenerationSteps; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.GenerationDependencies; +import net.minecraft.world.chunk.ProtoChunk; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -109,24 +117,32 @@ public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancella final ChunkGenerationContext chunkGenerationContext = ((IThreadedAnvilChunkStorage) context.tacs()).getGenerationContext(); Chunk chunk = state.chunk(); if (chunk.getStatus().isAtLeast(status)) { - return ChunkGenerationSteps.LOADING.get(status) - .run(((IThreadedAnvilChunkStorage) context.tacs()).getGenerationContext(), context.chunks(), chunk) - .whenComplete((chunk1, throwable) -> { - if (chunk1 != null) { - context.holder().getItem().set(new ChunkState(chunk1, (ProtoChunk) chunk1, this.status)); - } - }).thenAccept(__ -> {}); + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + return ChunkGenerationSteps.LOADING.get(status) + .run(((IThreadedAnvilChunkStorage) context.tacs()).getGenerationContext(), context.chunks(), chunk) + .whenComplete((chunk1, throwable) -> { + if (chunk1 != null) { + context.holder().getItem().set(new ChunkState(chunk1, (ProtoChunk) chunk1, this.status)); + } + }).thenAccept(__ -> { + }); + } } else { final ChunkGenerationStep step = ChunkGenerationSteps.GENERATION.get(status); int radius = Math.max(0, step.blockStateWriteRadius()); return runTaskWithLock(chunk.getPos(), radius, context.schedulingManager(), - () -> step.run(chunkGenerationContext, context.chunks(), chunk) - .whenComplete((chunk1, throwable) -> { - if (chunk1 != null) { - context.holder().getItem().set(new ChunkState(chunk1, (ProtoChunk) chunk1, this.status)); - } - }).thenAccept(__ -> {}) + () -> { + try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, this, true))) { + return step.run(chunkGenerationContext, context.chunks(), chunk) + .whenComplete((chunk1, throwable) -> { + if (chunk1 != null) { + context.holder().getItem().set(new ChunkState(chunk1, (ProtoChunk) chunk1, this.status)); + } + }).thenAccept(__ -> { + }); + } + } ); } } diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/threadstate/ChunkTaskWork.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/threadstate/ChunkTaskWork.java new file mode 100644 index 000000000..b04b141fe --- /dev/null +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/threadstate/ChunkTaskWork.java @@ -0,0 +1,39 @@ +package com.ishland.c2me.rewrites.chunksystem.common.threadstate; + +import com.ishland.c2me.base.common.threadstate.RunningWork; +import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; +import com.ishland.c2me.rewrites.chunksystem.common.ChunkLoadingContext; +import com.ishland.c2me.rewrites.chunksystem.common.NewChunkStatus; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.ChunkPos; + +public record ChunkTaskWork(ServerWorld world, ChunkPos chunkPos, NewChunkStatus status, boolean isUpgrade) implements RunningWork { + + public ChunkTaskWork(ChunkLoadingContext context, NewChunkStatus status, boolean isUpgrade) { + this( + ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(), + context.holder().getKey(), + status, + isUpgrade + ); + } + + @Override + public String toString() { + if (isUpgrade) { + return String.format( + "Upgrading chunk %s to %s in world %s", + chunkPos, + status, + world.getRegistryKey().getValue() + ); + } else { + return String.format( + "Downgrading chunk %s from %s in world %s", + chunkPos, + status, + world.getRegistryKey().getValue() + ); + } + } +} diff --git a/settings.gradle b/settings.gradle index 260fc98c0..66f6d433b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -46,7 +46,6 @@ include 'c2me-opts-allocs' include 'c2me-opts-math' include 'c2me-opts-scheduling' include 'c2me-opts-chunkio' -include 'c2me-opts-chunk-access' include 'c2me-rewrites-chunk-serializer' include 'c2me-rewrites-chunkio' include 'c2me-rewrites-chunk-system'