diff --git a/patches/main/0011-new-scalable-no-tick-view-distance-allowing-usable-3.patch b/patches/main/0011-new-scalable-no-tick-view-distance-allowing-usable-3.patch new file mode 100644 index 0000000..effdba4 --- /dev/null +++ b/patches/main/0011-new-scalable-no-tick-view-distance-allowing-usable-3.patch @@ -0,0 +1,1022 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Fri, 22 Nov 2024 23:33:47 +0800 +Subject: [PATCH] new: scalable no-tick view distance, allowing usable 300+ + render distances + +Backported from 944313690733e282f026551266eb18edfca2ce59 + +diff --git a/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/IChunkTicketManager.java b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/IChunkTicketManager.java +index fb7f2d04..3e53586f 100644 +--- a/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/IChunkTicketManager.java ++++ b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/IChunkTicketManager.java +@@ -7,6 +7,7 @@ import net.minecraft.server.network.ServerPlayerEntity; + import net.minecraft.server.world.ChunkTicket; + import net.minecraft.server.world.ChunkTicketManager; + import net.minecraft.util.collection.SortedArraySet; ++import net.minecraft.world.SimulationDistanceLevelPropagator; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.gen.Accessor; + import org.spongepowered.asm.mixin.gen.Invoker; +@@ -26,4 +27,7 @@ public interface IChunkTicketManager { + @Accessor + ChunkTicketManager.NearbyChunkTicketUpdater getNearbyChunkTicketUpdater(); + ++ @Accessor ++ SimulationDistanceLevelPropagator getSimulationDistanceTracker(); ++ + } +diff --git a/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/ISimulationDistanceLevelPropagator.java b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/ISimulationDistanceLevelPropagator.java +new file mode 100644 +index 00000000..1da26449 +--- /dev/null ++++ b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/ISimulationDistanceLevelPropagator.java +@@ -0,0 +1,14 @@ ++package com.ishland.c2me.base.mixin.access; ++ ++import it.unimi.dsi.fastutil.longs.Long2ByteMap; ++import net.minecraft.world.SimulationDistanceLevelPropagator; ++import org.spongepowered.asm.mixin.Mixin; ++import org.spongepowered.asm.mixin.gen.Accessor; ++ ++@Mixin(SimulationDistanceLevelPropagator.class) ++public interface ISimulationDistanceLevelPropagator { ++ ++ @Accessor ++ Long2ByteMap getLevels(); ++ ++} +diff --git a/c2me-base/src/main/resources/c2me-base.mixins.json b/c2me-base/src/main/resources/c2me-base.mixins.json +index 2197c6f2..b94c3ecd 100644 +--- a/c2me-base/src/main/resources/c2me-base.mixins.json ++++ b/c2me-base/src/main/resources/c2me-base.mixins.json +@@ -36,6 +36,7 @@ + "access.ISimpleRandom", + "access.ISimpleTickScheduler", + "access.ISimplexNoiseSampler", ++ "access.ISimulationDistanceLevelPropagator", + "access.IState", + "access.IStorageIoWorker", + "access.IStructurePiece", +diff --git a/src/main/resources/c2me.mixins.json b/c2me-base/src/main/resources/c2me.mixins.json +similarity index 100% +rename from src/main/resources/c2me.mixins.json +rename to c2me-base/src/main/resources/c2me.mixins.json +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/IChunkTicketManager.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/ChunkTicketManagerExtension.java +similarity index 61% +rename from c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/IChunkTicketManager.java +rename to c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/ChunkTicketManagerExtension.java +index 9452b9ef..4bddda8c 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/IChunkTicketManager.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/ChunkTicketManagerExtension.java +@@ -2,10 +2,10 @@ package com.ishland.c2me.notickvd.common; + + import it.unimi.dsi.fastutil.longs.LongSet; + +-public interface IChunkTicketManager { ++public interface ChunkTicketManagerExtension { + + LongSet getNoTickOnlyChunks(); + +- int getNoTickPendingTicketUpdates(); ++ long getPendingLoadsCount(); + + } +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/Config.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/Config.java +index 822c62b5..64796be8 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/Config.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/Config.java +@@ -32,20 +32,7 @@ public class Config { + " This will send chunks twice increasing network load") + .getBoolean(false, true); + +- public static final long maxViewDistance = new ConfigSystem.ConfigAccessor() +- .key("noTickViewDistance.maxViewDistance") +- .comment(""" +- Maximum view distance for no-tick view distance\s +- +- This allows you to specify the maximum view distance that no-tick view distance can support.\s +- The maximum supported is 1073741823 and the minimum that make sense is 32,\s +- This option is purely to save memory, as it needs to reserve memory for the maximum view distance\s +- +- Note: on the client side, `clientSideConfig.modifyMaxVDConfig.maxViewDistance` should also\s +- be increased to actually expose the view distance in video settings\s +- +- """) +- .getLong(2048, 2048, ConfigSystem.LongChecks.POSITIVE_VALUES_ONLY); ++ public static final int maxViewDistance = 1 << 16; + + public static void init() { + } +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NoTickSystem.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NoTickSystem.java +index 02095a81..7c8fc4e0 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NoTickSystem.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NoTickSystem.java +@@ -1,16 +1,13 @@ + package com.ishland.c2me.notickvd.common; + + import com.ishland.c2me.base.common.GlobalExecutors; +-import it.unimi.dsi.fastutil.longs.LongIterator; +-import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; +-import it.unimi.dsi.fastutil.longs.LongSets; +-import net.minecraft.server.world.ChunkTicket; + import net.minecraft.server.world.ChunkTicketManager; + import net.minecraft.server.world.ServerChunkLoadingManager; + import net.minecraft.util.math.ChunkPos; + + import java.util.ArrayList; ++import java.util.LinkedList; + import java.util.List; + import java.util.concurrent.ConcurrentLinkedQueue; + import java.util.concurrent.Executor; +@@ -18,44 +15,33 @@ import java.util.concurrent.atomic.AtomicBoolean; + + public class NoTickSystem { + +- private final PlayerNoTickDistanceMap playerNoTickDistanceMap; +- private final NormalTicketDistanceMap normalTicketDistanceMap; + private final ChunkTicketManager chunkTicketManager; + ++ private final PlayerNoTickLoader playerNoTickLoader; + private final ConcurrentLinkedQueue pendingActionsOnScheduler = new ConcurrentLinkedQueue<>(); + final ConcurrentLinkedQueue mainBeforeTicketTicks = new ConcurrentLinkedQueue<>(); + final ConcurrentLinkedQueue mainAfterTicketTicks = new ConcurrentLinkedQueue<>(); + + private final AtomicBoolean isTicking = new AtomicBoolean(); + final Executor executor = GlobalExecutors.asyncScheduler; +- private volatile LongSet noTickOnlyChunksSnapshot = LongSets.EMPTY_SET; + private volatile boolean pendingPurge = false; + private volatile long age = 0; + + public NoTickSystem(ChunkTicketManager chunkTicketManager) { + this.chunkTicketManager = chunkTicketManager; +- this.playerNoTickDistanceMap = new PlayerNoTickDistanceMap(chunkTicketManager, this); +- this.normalTicketDistanceMap = new NormalTicketDistanceMap(chunkTicketManager); +- } +- +- public void onTicketAdded(long position, ChunkTicket ticket) { +- this.pendingActionsOnScheduler.add(() -> this.normalTicketDistanceMap.addTicket(position, ticket)); +- } +- +- public void onTicketRemoved(long position, ChunkTicket ticket) { +- this.pendingActionsOnScheduler.add(() -> this.normalTicketDistanceMap.removeTicket(position, ticket)); ++ this.playerNoTickLoader = new PlayerNoTickLoader(chunkTicketManager, this); + } + + public void addPlayerSource(ChunkPos chunkPos) { +- this.pendingActionsOnScheduler.add(() -> this.playerNoTickDistanceMap.addSource(chunkPos)); ++ this.pendingActionsOnScheduler.add(() -> this.playerNoTickLoader.addSource(chunkPos)); + } + + public void removePlayerSource(ChunkPos chunkPos) { +- this.pendingActionsOnScheduler.add(() -> this.playerNoTickDistanceMap.removeSource(chunkPos)); ++ this.pendingActionsOnScheduler.add(() -> this.playerNoTickLoader.removeSource(chunkPos)); + } + + public void setNoTickViewDistance(int viewDistance) { +- this.pendingActionsOnScheduler.add(() -> this.playerNoTickDistanceMap.setViewDistance(viewDistance)); ++ this.pendingActionsOnScheduler.add(() -> this.playerNoTickLoader.setViewDistance(viewDistance)); + } + + public void beforeTicketTicks() { +@@ -80,38 +66,20 @@ public class NoTickSystem { + } + } + executor.execute(() -> { +- for (Runnable task : tasks) { +- try { +- task.run(); +- } catch (Throwable t) { +- t.printStackTrace(); ++ try { ++ for (Runnable task : tasks) { ++ try { ++ task.run(); ++ } catch (Throwable t) { ++ t.printStackTrace(); ++ } + } +- } +- +- boolean hasNoTickTicketUpdates; +- if (pendingPurge) { +- this.normalTicketDistanceMap.purge(this.age); +- hasNoTickTicketUpdates = this.playerNoTickDistanceMap.runPendingTicketUpdates(tacs); +- } else { +- hasNoTickTicketUpdates = false; +- } + +- final boolean hasNormalTicketUpdates = this.normalTicketDistanceMap.update(); +- final boolean hasNoTickUpdates = this.playerNoTickDistanceMap.update(); +- if (hasNormalTicketUpdates || hasNoTickUpdates || hasNoTickTicketUpdates) { +- final LongSet noTickChunks = this.playerNoTickDistanceMap.getChunks(); +- final LongSet normalChunks = this.normalTicketDistanceMap.getChunks(); +- final LongOpenHashSet longs = new LongOpenHashSet(noTickChunks.size() * 3 / 2); +- final LongIterator iterator = noTickChunks.iterator(); +- while (iterator.hasNext()) { +- final long chunk = iterator.nextLong(); +- if (normalChunks.contains(chunk)) continue; +- longs.add(chunk); +- } +- this.noTickOnlyChunksSnapshot = LongSets.unmodifiable(longs); ++ this.playerNoTickLoader.tick(tacs); ++ if (!this.pendingActionsOnScheduler.isEmpty() || !tasks.isEmpty()) scheduleTick(tacs); // run more tasks ++ } finally { ++ this.isTicking.set(false); + } +- this.isTicking.set(false); +- if (hasNormalTicketUpdates || hasNoTickUpdates) scheduleTick(tacs); // run more tasks + }); + } + } +@@ -133,10 +101,10 @@ public class NoTickSystem { + } + + public LongSet getNoTickOnlyChunksSnapshot() { +- return this.noTickOnlyChunksSnapshot; ++ return null; + } + +- public int getPendingNoTickTicketUpdatesCount() { +- return this.playerNoTickDistanceMap.getPendingTicketUpdatesCount(); ++ public long getPendingLoadsCount() { ++ return this.playerNoTickLoader.getPendingLoadsCount(); + } + } +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NormalTicketDistanceMap.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NormalTicketDistanceMap.java +deleted file mode 100644 +index ca70a71b..00000000 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NormalTicketDistanceMap.java ++++ /dev/null +@@ -1,104 +0,0 @@ +-package com.ishland.c2me.notickvd.common; +- +-import com.ishland.c2me.base.mixin.access.IChunkTicket; +-import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +-import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +-import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +-import it.unimi.dsi.fastutil.longs.LongSet; +-import it.unimi.dsi.fastutil.objects.ObjectIterator; +-import net.minecraft.server.world.ChunkTicket; +-import net.minecraft.server.world.ChunkTicketManager; +-import net.minecraft.util.collection.SortedArraySet; +-import net.minecraft.world.ChunkPosDistanceLevelPropagator; +- +-public class NormalTicketDistanceMap extends ChunkPosDistanceLevelPropagator { +- private final ChunkTicketManager chunkTicketManager; +- private final Long2IntOpenHashMap distanceMap = new Long2IntOpenHashMap(); +- private final Long2ObjectOpenHashMap>> ticketsByPosition = new Long2ObjectOpenHashMap<>(); +- +- public NormalTicketDistanceMap(ChunkTicketManager chunkTicketManager) { +- super(33 + 2, 16, 256); +- this.chunkTicketManager = chunkTicketManager; +- distanceMap.defaultReturnValue(33 + 2); +- } +- +- @Override +- protected int getInitialLevel(long id) { +- SortedArraySet> sortedArraySet = ticketsByPosition.get(id); +- if (sortedArraySet != null) { +- if (sortedArraySet.isEmpty()) return Integer.MAX_VALUE; +- for (ChunkTicket next : sortedArraySet) { +- if (next.getType() == PlayerNoTickDistanceMap.TICKET_TYPE) continue; +- return next.getLevel(); +- } +- } +- return Integer.MAX_VALUE; +- } +- +- @Override +- protected int getLevel(long id) { +- return distanceMap.get(id); +- } +- +- @Override +- protected void setLevel(long id, int level) { +- if (level > 33) { +- distanceMap.remove(id); +- } else { +- distanceMap.put(id, level); +- } +- } +- +- private static int getLevel(SortedArraySet> sortedArraySet) { +- return !sortedArraySet.isEmpty() ? sortedArraySet.first().getLevel() : Integer.MAX_VALUE; +- } +- +- public void addTicket(long position, ChunkTicket ticket) { +- if (ticket.getType() == PlayerNoTickDistanceMap.TICKET_TYPE) return; +- SortedArraySet> sortedArraySet = this.getTicketSet(position); +- int i = getLevel(sortedArraySet); +- sortedArraySet.add(ticket); +- if (ticket.getLevel() < i) { +- this.updateLevel(position, ticket.getLevel(), true); +- } +- } +- +- public void removeTicket(long pos, ChunkTicket ticket) { +- if (ticket.getType() == PlayerNoTickDistanceMap.TICKET_TYPE) return; +- SortedArraySet> sortedArraySet = this.getTicketSet(pos); +- sortedArraySet.remove(ticket); +- +- if (sortedArraySet.isEmpty()) { +- this.ticketsByPosition.remove(pos); +- } +- +- this.updateLevel(pos, getLevel(sortedArraySet), false); +- } +- +- public void purge(long age) { +- final ObjectIterator>>> iterator = this.ticketsByPosition.long2ObjectEntrySet().fastIterator(); +- +- while (iterator.hasNext()) { +- final Long2ObjectMap.Entry>> entry = iterator.next(); +- final boolean isModified = entry.getValue().removeIf(chunkTicket -> ((IChunkTicket) (Object) chunkTicket).invokeIsExpired(age)); +- if (isModified) { +- this.updateLevel(entry.getLongKey(), getLevel(entry.getValue()), false); +- } +- if (entry.getValue().isEmpty()) { +- iterator.remove(); +- } +- } +- } +- +- public SortedArraySet> getTicketSet(long pos) { +- return this.ticketsByPosition.computeIfAbsent(pos, (l) -> SortedArraySet.create(4)); +- } +- +- public boolean update() { +- return Integer.MAX_VALUE - this.applyPendingUpdates(Integer.MAX_VALUE) != 0; +- } +- +- public LongSet getChunks() { +- return this.distanceMap.keySet(); +- } +-} +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickDistanceMap.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickDistanceMap.java +deleted file mode 100644 +index 55a9eb4b..00000000 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickDistanceMap.java ++++ /dev/null +@@ -1,191 +0,0 @@ +-package com.ishland.c2me.notickvd.common; +- +-import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; +-import com.ishland.c2me.notickvd.common.modimpl.ChunkPosDistanceLevelPropagatorExtended; +-import com.ishland.c2me.notickvd.common.modimpl.LevelPropagatorExtended; +-import com.ishland.flowsched.structs.DynamicPriorityQueue; +-import com.mojang.logging.LogUtils; +-import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +-import it.unimi.dsi.fastutil.longs.LongIterator; +-import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +-import it.unimi.dsi.fastutil.longs.LongSet; +-import it.unimi.dsi.fastutil.objects.ObjectSet; +-import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +-import net.minecraft.server.network.ServerPlayerEntity; +-import net.minecraft.server.world.ChunkHolder; +-import net.minecraft.server.world.ChunkTicketManager; +-import net.minecraft.server.world.ChunkTicketType; +-import net.minecraft.server.world.OptionalChunk; +-import net.minecraft.server.world.ServerChunkLoadingManager; +-import net.minecraft.util.math.ChunkPos; +-import net.minecraft.util.math.MathHelper; +-import net.minecraft.world.chunk.WorldChunk; +-import org.slf4j.Logger; +- +-import java.util.Comparator; +-import java.util.concurrent.CompletableFuture; +- +-public class PlayerNoTickDistanceMap extends ChunkPosDistanceLevelPropagatorExtended { +- +- private static final Logger LOGGER = LogUtils.getLogger(); +- public static final ChunkTicketType TICKET_TYPE = ChunkTicketType.create("c2me_no_tick_vd", Comparator.comparingLong(ChunkPos::toLong)); +- public static final int MAX_RENDER_DISTANCE = MathHelper.clamp((int) Config.maxViewDistance, 32, LevelPropagatorExtended.MAX_LEVEL - 10); +- +- private final LongSet sourceChunks = new LongOpenHashSet(); +- private final Long2IntOpenHashMap distanceFromNearestPlayer = new Long2IntOpenHashMap(); +- private final DynamicPriorityQueue pendingTicketAdds = new DynamicPriorityQueue<>(MAX_RENDER_DISTANCE + 2); +- private final LongOpenHashSet pendingTicketRemoves = new LongOpenHashSet(); +- private final LongOpenHashSet managedChunkTickets = new LongOpenHashSet(); +- private final ReferenceArrayList> chunkLoadFutures = new ReferenceArrayList<>(); +- +- private final ChunkTicketManager chunkTicketManager; +- private final NoTickSystem noTickSystem; +- private volatile int viewDistance; +- private volatile int pendingTicketUpdatesCount = 0; // for easier access concurrently +- +- public PlayerNoTickDistanceMap(ChunkTicketManager chunkTicketManager, NoTickSystem noTickSystem) { +- super(MAX_RENDER_DISTANCE + 2, 16, 256); +- this.chunkTicketManager = chunkTicketManager; +- this.noTickSystem = noTickSystem; +- this.distanceFromNearestPlayer.defaultReturnValue(MAX_RENDER_DISTANCE + 2); +- this.setViewDistance(12); +- } +- +- @Override +- protected int getInitialLevel(long chunkPos) { +- final ObjectSet players = ((com.ishland.c2me.base.mixin.access.IChunkTicketManager) chunkTicketManager).getPlayersByChunkPos().get(chunkPos); +- return players != null && !players.isEmpty() ? MAX_RENDER_DISTANCE - viewDistance : Integer.MAX_VALUE; +- } +- +- @Override +- protected int getLevel(long chunkPos) { +- return this.distanceFromNearestPlayer.get(chunkPos); +- } +- +- @Override +- protected void setLevel(long chunkPos, int level) { +- if (level > MAX_RENDER_DISTANCE) { +- if (this.distanceFromNearestPlayer.containsKey(chunkPos)) { +- this.pendingTicketRemoves.add(chunkPos); +- this.pendingTicketAdds.remove(new ChunkPos(chunkPos)); +- this.distanceFromNearestPlayer.remove(chunkPos); +- } +- } else { +- if (!this.distanceFromNearestPlayer.containsKey(chunkPos)) { +- pendingTicketRemoves.remove(chunkPos); +- pendingTicketAdds.enqueue(new ChunkPos(chunkPos), level); +- } +- pendingTicketAdds.changePriority(new ChunkPos(chunkPos), level); +- this.distanceFromNearestPlayer.put(chunkPos, level); +- } +- } +- +- public void addSource(ChunkPos chunkPos) { +- this.updateLevel(chunkPos.toLong(), MAX_RENDER_DISTANCE - this.viewDistance, true); +- this.sourceChunks.add(chunkPos.toLong()); +- } +- +- public void removeSource(ChunkPos chunkPos) { +- this.updateLevel(chunkPos.toLong(), Integer.MAX_VALUE, false); +- this.sourceChunks.remove(chunkPos.toLong()); +- } +- +- public boolean update() { +- final boolean hasUpdates = this.applyPendingUpdates(Integer.MAX_VALUE) != Integer.MAX_VALUE; +- this.pendingTicketUpdatesCount = this.pendingTicketAdds.size() + this.pendingTicketRemoves.size(); +- return hasUpdates; +- } +- +- private boolean hasPendingTicketUpdatesAsync = false; +- +- boolean runPendingTicketUpdates(ServerChunkLoadingManager tacs) { +- final boolean hasUpdatesNow = runPendingTicketUpdatesInternal(tacs); +- final boolean hasUpdatesEarlier = hasPendingTicketUpdatesAsync; +- hasPendingTicketUpdatesAsync = false; +- return hasUpdatesNow || hasUpdatesEarlier; +- } +- +- private boolean runPendingTicketUpdatesInternal(ServerChunkLoadingManager tacs) { +- boolean hasUpdates = false; +- // remove old tickets +- { +- final LongIterator it = pendingTicketRemoves.longIterator(); +- while (it.hasNext()) { +- final long chunkPos = it.nextLong(); +- if (this.managedChunkTickets.remove(chunkPos)) { +- removeTicket0(new ChunkPos(chunkPos)); +- hasUpdates = true; +- } +- } +- pendingTicketRemoves.clear(); +- } +- +- // clean up futures +- this.chunkLoadFutures.removeIf(CompletableFuture::isDone); +- +- // add new tickets +- while (this.chunkLoadFutures.size() < Config.maxConcurrentChunkLoads) { +- final ChunkPos pos = this.pendingTicketAdds.dequeue(); +- if (pos == null) break; +- if (this.managedChunkTickets.add(pos.toLong())) { +- final CompletableFuture ticketFuture = this.addTicket0(pos); +- this.chunkLoadFutures.add(getChunkLoadFuture(tacs, pos, ticketFuture)); +- hasUpdates = true; +- } +- } +- +- this.pendingTicketUpdatesCount = this.pendingTicketAdds.size() + this.pendingTicketRemoves.size(); +- return hasUpdates; +- } +- +- private void removeTicket0(ChunkPos pos) { +- this.noTickSystem.mainBeforeTicketTicks.add(() -> this.chunkTicketManager.removeTicketWithLevel(TICKET_TYPE, pos, 33, pos)); +- } +- +- private CompletableFuture addTicket0(ChunkPos pos) { +- return CompletableFuture.runAsync(() -> this.chunkTicketManager.addTicketWithLevel(TICKET_TYPE, pos, 33, pos), this.noTickSystem.mainBeforeTicketTicks::add); +- } +- +- private CompletableFuture getChunkLoadFuture(ServerChunkLoadingManager tacs, ChunkPos pos, CompletableFuture ticketFuture) { +- final CompletableFuture future = ticketFuture.thenComposeAsync(unused -> { +- final ChunkHolder holder = ((IThreadedAnvilChunkStorage) tacs).invokeGetChunkHolder(pos.toLong()); +- if (holder == null) { +- return CompletableFuture.completedFuture(null); +- } else { +- final CompletableFuture> accessibleFuture = holder.getAccessibleFuture(); +- accessibleFuture.whenCompleteAsync((worldChunkOptionalChunk, throwable) -> { +- if (throwable != null) { +- LOGGER.error("Failed to load chunk {}", pos); +- } +- }, ((IThreadedAnvilChunkStorage) tacs).getMainThreadExecutor()); +- return accessibleFuture.exceptionally(throwable -> null).thenAccept(unused1 -> { +- }); +- } +- }, this.noTickSystem.mainAfterTicketTicks::add); +- future.thenRunAsync(() -> { +- this.chunkLoadFutures.remove(future); +- final boolean hasUpdates = this.runPendingTicketUpdatesInternal(tacs); +- if (hasUpdates) { +- hasPendingTicketUpdatesAsync = true; +- } +- }, this.noTickSystem.executor); +- return future; +- } +- +- public void setViewDistance(int viewDistance) { +- this.viewDistance = MathHelper.clamp(viewDistance, 3, MAX_RENDER_DISTANCE); +- sourceChunks.forEach((long value) -> { +- removeSource(new ChunkPos(value)); +- addSource(new ChunkPos(value)); +- }); +- } +- +- public int getPendingTicketUpdatesCount() { +- return this.pendingTicketUpdatesCount; +- } +- +- public LongSet getChunks() { +- return managedChunkTickets; +- } +- +-} +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +new file mode 100644 +index 00000000..6eebb93e +--- /dev/null ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +@@ -0,0 +1,179 @@ ++package com.ishland.c2me.notickvd.common; ++ ++import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; ++import com.ishland.c2me.notickvd.common.iterators.ChunkIterator; ++import com.ishland.c2me.notickvd.common.iterators.SpiralIterator; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.longs.Long2ReferenceLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import it.unimi.dsi.fastutil.longs.LongSet; ++import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; ++import it.unimi.dsi.fastutil.objects.ReferenceArrayList; ++import net.minecraft.server.world.ChunkHolder; ++import net.minecraft.server.world.ChunkTicketManager; ++import net.minecraft.server.world.ChunkTicketType; ++import net.minecraft.server.world.ServerChunkLoadingManager; ++import net.minecraft.util.Unit; ++import net.minecraft.util.math.ChunkPos; ++import org.slf4j.Logger; ++ ++import java.util.concurrent.CompletableFuture; ++import java.util.function.LongFunction; ++ ++public class PlayerNoTickLoader { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ public static final ChunkTicketType TICKET_TYPE = ChunkTicketType.create("c2me_no_tick_vd", (a, b) -> 0); ++ ++ private final ChunkTicketManager ticketManager; ++ private final NoTickSystem noTickSystem; ++ private final Long2ReferenceLinkedOpenHashMap iterators = new Long2ReferenceLinkedOpenHashMap<>(); ++ private final LongSet managedChunks = new LongLinkedOpenHashSet(); ++ private final LongFunction createFunction = pos -> new SpiralIterator(ChunkPos.getPackedX(pos), ChunkPos.getPackedZ(pos), this.viewDistance); ++ private final ReferenceArrayList> chunkLoadFutures = new ReferenceArrayList<>(); ++ ++ private int viewDistance = 12; ++ private boolean dirtyManagedChunks = false; ++ private boolean recreateIterators = false; ++ private volatile long pendingLoadsCountSnapshot = 0L; ++ ++ public PlayerNoTickLoader(ChunkTicketManager ticketManager, NoTickSystem noTickSystem) { ++ this.ticketManager = ticketManager; ++ this.noTickSystem = noTickSystem; ++ } ++ ++ public void addSource(ChunkPos chunkPos) { ++ this.iterators.computeIfAbsent(chunkPos.toLong(), this.createFunction); ++ } ++ ++ public void removeSource(ChunkPos chunkPos) { ++ this.iterators.remove(chunkPos.toLong()); ++ this.dirtyManagedChunks = true; ++ } ++ ++ public void setViewDistance(int viewDistance) { ++ this.viewDistance = viewDistance; ++ this.dirtyManagedChunks = true; ++ this.recreateIterators = true; ++ } ++ ++ public void tick(ServerChunkLoadingManager tacs) { ++ if (this.recreateIterators) { ++ this.dirtyManagedChunks = true; ++ ObjectBidirectionalIterator> iterator = this.iterators.long2ReferenceEntrySet().fastIterator(); ++ while (iterator.hasNext()) { ++ Long2ReferenceMap.Entry entry = iterator.next(); ++ entry.setValue(this.createFunction.apply(entry.getLongKey())); ++ } ++ this.recreateIterators = false; ++ } ++ ++ if (this.dirtyManagedChunks) { ++ LongIterator chunkIterator = this.managedChunks.iterator(); ++ ObjectBidirectionalIterator> iteratorIterator = this.iterators.long2ReferenceEntrySet().fastIterator(); ++ while (chunkIterator.hasNext()) { ++ long pos = chunkIterator.nextLong(); ++ int packedX = ChunkPos.getPackedX(pos); ++ int packedZ = ChunkPos.getPackedZ(pos); ++ ++ boolean isUsed = false; ++ if (iteratorIterator.hasNext()) { ++ while (iteratorIterator.hasNext()) { ++ Long2ReferenceMap.Entry entry = iteratorIterator.next(); ++ isUsed |= entry.getValue().isInRange(packedX, packedZ); ++ } ++ } else if (iteratorIterator.hasPrevious()) { ++ while (iteratorIterator.hasPrevious()) { ++ Long2ReferenceMap.Entry entry = iteratorIterator.previous(); ++ isUsed |= entry.getValue().isInRange(packedX, packedZ); ++ } ++ } ++ ++ if (!isUsed) { ++ this.removeTicket0(packedX, packedZ); ++ chunkIterator.remove(); ++ } ++ } ++ this.dirtyManagedChunks = false; ++ } ++ ++ this.tickFutures(tacs); ++ ++ { ++ long pendingLoadsCount = 0L; ++ ObjectBidirectionalIterator> iterator = this.iterators.long2ReferenceEntrySet().fastIterator(); ++ while (iterator.hasNext()) { ++ Long2ReferenceMap.Entry entry = iterator.next(); ++ pendingLoadsCount += entry.getValue().remaining(); ++ } ++ this.pendingLoadsCountSnapshot = pendingLoadsCount; ++ } ++ } ++ ++ void tickFutures(ServerChunkLoadingManager tacs) { ++ this.chunkLoadFutures.removeIf(CompletableFuture::isDone); ++ ++ while (this.chunkLoadFutures.size() < Config.maxConcurrentChunkLoads && this.addOneTicket(tacs)); ++ } ++ ++ private boolean addOneTicket(ServerChunkLoadingManager tacs) { ++ ObjectBidirectionalIterator> iteratorIterator = this.iterators.long2ReferenceEntrySet().fastIterator(); ++ while (iteratorIterator.hasNext()) { ++ Long2ReferenceMap.Entry entry = iteratorIterator.next(); ++ ChunkIterator iterator = entry.getValue(); ++ while (iterator.hasNext()) { ++ ChunkPos pos = iterator.next(); ++ if (this.managedChunks.add(pos.toLong())) { ++ this.chunkLoadFutures.add(loadChunk(tacs, pos.x, pos.z)); ++ this.iterators.getAndMoveToLast(entry.getLongKey()); ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ private CompletableFuture loadChunk(ServerChunkLoadingManager tacs, int x, int z) { ++ CompletableFuture future = this.addTicket0(x, z) ++ .thenComposeAsync(unused -> { ++ ChunkHolder holder = ((IThreadedAnvilChunkStorage) tacs).invokeGetChunkHolder(ChunkPos.toLong(x, z)); ++ if (holder == null) { ++ LOGGER.warn("No holder created after adding ticket to chunk [{}, {}]", x, z); ++ return CompletableFuture.completedFuture(null); ++ } else { ++ return holder.getAccessibleFuture() ++ .handle((worldChunkOptionalChunk, throwable) -> { ++ if (throwable != null) { ++ LOGGER.error("Failed to load chunk [{}, {}]", x, z); ++ } ++ return null; ++ }); ++ } ++ }, this.noTickSystem.mainAfterTicketTicks::add); ++ future.thenRunAsync(() -> { ++ try { ++ this.chunkLoadFutures.remove(future); ++ this.tickFutures(tacs); ++ } catch (Throwable t) { ++ LOGGER.error("Error while loading chunk [{}, {}]", x, z, t); ++ } ++ }, this.noTickSystem.executor); ++ return future; ++ } ++ ++ private CompletableFuture addTicket0(int x, int z) { ++ return CompletableFuture.runAsync(() -> this.ticketManager.addTicketWithLevel(TICKET_TYPE, new ChunkPos(x, z), 33, Unit.INSTANCE), this.noTickSystem.mainBeforeTicketTicks::add); ++ } ++ ++ private void removeTicket0(int x, int z) { ++ this.noTickSystem.mainBeforeTicketTicks.add(() -> this.ticketManager.removeTicketWithLevel(TICKET_TYPE, new ChunkPos(x, z), 33, Unit.INSTANCE)); ++ } ++ ++ public long getPendingLoadsCount() { ++ return this.pendingLoadsCountSnapshot; ++ } ++} +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/iterators/ChunkIterator.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/iterators/ChunkIterator.java +new file mode 100644 +index 00000000..c43e7090 +--- /dev/null ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/iterators/ChunkIterator.java +@@ -0,0 +1,27 @@ ++package com.ishland.c2me.notickvd.common.iterators; ++ ++import net.minecraft.util.math.ChunkPos; ++ ++import java.util.Iterator; ++ ++public interface ChunkIterator extends Iterator { ++ ++ long remaining(); ++ ++ long total(); ++ ++ int originX(); ++ ++ int originZ(); ++ ++ int radius(); ++ ++ default boolean isInRange(int x, int z) { ++ int originX = this.originX(); ++ int originZ = this.originZ(); ++ int radius = this.radius(); ++ return x >= originX - radius && x <= originX + radius && ++ z >= originZ - radius && z <= originZ + radius; ++ } ++ ++} +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/iterators/SpiralIterator.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/iterators/SpiralIterator.java +new file mode 100644 +index 00000000..861b3e79 +--- /dev/null ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/iterators/SpiralIterator.java +@@ -0,0 +1,91 @@ ++package com.ishland.c2me.notickvd.common.iterators; ++ ++import net.minecraft.util.math.ChunkPos; ++ ++import java.util.Iterator; ++import java.util.NoSuchElementException; ++ ++public class SpiralIterator implements ChunkIterator { ++ ++ private static final int RIGHT = 0, DOWN = 1, LEFT = 2, UP = 3; ++ private final int originX, originZ; ++ private final int radius; ++ private final long total; ++ private boolean needStep = false; ++ private int x, z; ++ private int spanTotal = 1, spanCount = 0, spanProgress = 0; ++ private int direction = RIGHT; ++ private long currentIndex; ++ ++ public SpiralIterator(int originX, int originZ, int radius) { ++ this.originX = originX; ++ this.originZ = originZ; ++ this.radius = radius; ++ this.x = originX; ++ this.z = originZ; ++ this.total = (radius * 2L + 1) * (radius * 2L + 1); ++ } ++ ++ @Override ++ public boolean hasNext() { ++ return x != this.originX + this.radius || z != this.originZ + this.radius; ++ } ++ ++ @Override ++ public ChunkPos next() { ++ if (!hasNext()) { ++ throw new NoSuchElementException(); ++ } ++ ++ if (this.needStep) { ++ switch (this.direction) { ++ case RIGHT -> this.x ++; ++ case DOWN -> this.z --; ++ case LEFT -> this.x --; ++ case UP -> this.z ++; ++ default -> throw new AssertionError(); ++ } ++ this.spanProgress ++; ++ if (this.spanProgress == this.spanTotal) { ++ this.spanProgress = 0; ++ this.spanCount ++; ++ if (this.spanCount >= 2) { ++ this.spanTotal ++; ++ this.spanCount = 0; ++ } ++ this.direction ++; ++ this.direction %= 4; ++ } ++ } ++ this.needStep = true; ++ ++ this.currentIndex ++; ++ ++ return new ChunkPos(this.x, this.z); ++ } ++ ++ @Override ++ public long remaining() { ++ return this.total - this.currentIndex; ++ } ++ ++ @Override ++ public long total() { ++ return this.total; ++ } ++ ++ @Override ++ public int originX() { ++ return this.originX; ++ } ++ ++ @Override ++ public int originZ() { ++ return this.originZ; ++ } ++ ++ @Override ++ public int radius() { ++ return this.radius; ++ } ++} +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java +index c96c3f70..59706a3e 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java +@@ -1,11 +1,9 @@ + package com.ishland.c2me.notickvd.mixin; + +-import com.ishland.c2me.notickvd.common.IChunkTicketManager; +-import com.ishland.c2me.notickvd.common.NoOPTickingMap; ++import com.ishland.c2me.notickvd.common.ChunkTicketManagerExtension; + import com.ishland.c2me.notickvd.common.NoTickSystem; + import it.unimi.dsi.fastutil.longs.LongSet; + import net.minecraft.server.network.ServerPlayerEntity; +-import net.minecraft.server.world.ChunkTicket; + import net.minecraft.server.world.ChunkTicketManager; + import net.minecraft.server.world.ServerChunkLoadingManager; + import net.minecraft.util.math.ChunkSectionPos; +@@ -22,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + + @Mixin(ChunkTicketManager.class) +-public class MixinChunkTicketManager implements IChunkTicketManager { ++public class MixinChunkTicketManager implements ChunkTicketManagerExtension { + + @Shadow private long age; + @Mutable +@@ -66,18 +64,6 @@ public class MixinChunkTicketManager implements IChunkTicketManager { + this.noTickSystem.tick(chunkStorage); + } + +- @Inject(method = "addTicket(JLnet/minecraft/server/world/ChunkTicket;)V", at = @At("RETURN")) +- private void onAddTicket(long position, ChunkTicket ticket, CallbackInfo ci) { +-// if (ticket.getType() != ChunkTicketType.UNKNOWN) System.err.printf("Added ticket (%s) at %s\n", ticket, new ChunkPos(position)); +- this.noTickSystem.onTicketAdded(position, ticket); +- } +- +- @Inject(method = "removeTicket(JLnet/minecraft/server/world/ChunkTicket;)V", at = @At("RETURN")) +- private void onRemoveTicket(long pos, ChunkTicket ticket, CallbackInfo ci) { +-// if (ticket.getType() != ChunkTicketType.UNKNOWN) System.err.printf("Removed ticket (%s) at %s\n", ticket, new ChunkPos(pos)); +- this.noTickSystem.onTicketRemoved(pos, ticket); +- } +- + /** + * @author ishland + * @reason remap setSimulationDistance to the normal one +@@ -104,7 +90,7 @@ public class MixinChunkTicketManager implements IChunkTicketManager { + + @Override + @Unique +- public int getNoTickPendingTicketUpdates() { +- return this.noTickSystem.getPendingNoTickTicketUpdatesCount(); ++ public long getPendingLoadsCount() { ++ return this.noTickSystem.getPendingLoadsCount(); + } + } +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerChunkManager.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerChunkManager.java +index cdff764b..c9a2e554 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerChunkManager.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerChunkManager.java +@@ -2,10 +2,11 @@ package com.ishland.c2me.notickvd.mixin; + + import com.ishland.c2me.base.common.theinterface.IFastChunkHolder; + import com.ishland.c2me.base.common.util.FilteringIterable; +-import com.ishland.c2me.notickvd.common.IChunkTicketManager; ++import com.ishland.c2me.base.mixin.access.IChunkTicketManager; ++import com.ishland.c2me.base.mixin.access.ISimulationDistanceLevelPropagator; + import com.llamalad7.mixinextras.injector.wrapoperation.Operation; + import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +-import it.unimi.dsi.fastutil.longs.LongSet; ++import it.unimi.dsi.fastutil.longs.Long2ByteMap; + import net.minecraft.entity.Entity; + import net.minecraft.server.world.ChunkHolder; + import net.minecraft.server.world.ChunkTicketManager; +@@ -34,9 +35,8 @@ public class MixinServerChunkManager { + + @WrapOperation(method = "tickChunks", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;iterateEntities()Ljava/lang/Iterable;")) + private Iterable redirectIterateEntities(ServerWorld serverWorld, Operation> op) { +- final LongSet noTickOnlyChunks = ((IChunkTicketManager) this.ticketManager).getNoTickOnlyChunks(); +- if (noTickOnlyChunks == null) return op.call(serverWorld); +- return new FilteringIterable<>(op.call(serverWorld), entity -> !noTickOnlyChunks.contains(entity.getChunkPos().toLong())); ++ Long2ByteMap trackedChunks = ((ISimulationDistanceLevelPropagator) ((IChunkTicketManager) this.ticketManager).getSimulationDistanceTracker()).getLevels(); ++ return new FilteringIterable<>(op.call(serverWorld), entity -> trackedChunks.containsKey(entity.getChunkPos().toLong())); + } + + } +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinThreadedAnvilChunkStorage.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinThreadedAnvilChunkStorage.java +index f8019b0b..32021941 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinThreadedAnvilChunkStorage.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinThreadedAnvilChunkStorage.java +@@ -2,8 +2,7 @@ package com.ishland.c2me.notickvd.mixin; + + import com.ishland.c2me.base.mixin.access.IServerChunkManager; + import com.ishland.c2me.notickvd.common.Config; +-import com.ishland.c2me.notickvd.common.IChunkTicketManager; +-import com.ishland.c2me.notickvd.common.PlayerNoTickDistanceMap; ++import com.ishland.c2me.notickvd.common.ChunkTicketManagerExtension; + import com.llamalad7.mixinextras.injector.WrapWithCondition; + import net.minecraft.server.network.ServerPlayerEntity; + import net.minecraft.server.world.ChunkHolder; +@@ -41,7 +40,7 @@ public abstract class MixinThreadedAnvilChunkStorage { + + @ModifyArg(method = "setViewDistance", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/MathHelper;clamp(III)I"), index = 2) + private int modifyMaxVD(int max) { +- return PlayerNoTickDistanceMap.MAX_RENDER_DISTANCE; ++ return Config.maxViewDistance; + } + + @Redirect(method = "getPostProcessedChunk", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ChunkHolder;getPostProcessedChunk()Lnet/minecraft/world/chunk/WorldChunk;")) +@@ -90,8 +89,8 @@ public abstract class MixinThreadedAnvilChunkStorage { + if (chunk instanceof WorldChunk worldChunk) { + final ServerWorld serverWorld = (ServerWorld) worldChunk.getWorld(); + final IServerChunkManager serverChunkManager = (IServerChunkManager) serverWorld.getChunkManager(); +- final IChunkTicketManager ticketManager = +- (IChunkTicketManager) serverChunkManager.getTicketManager(); ++ final ChunkTicketManagerExtension ticketManager = ++ (ChunkTicketManagerExtension) serverChunkManager.getTicketManager(); + cir.setReturnValue(cir.getReturnValueZ() && !ticketManager.getNoTickOnlyChunks().contains(chunk.getPos().toLong())); + } + } +diff --git a/c2me-server-utils/src/main/java/com/ishland/c2me/server/utils/common/C2MECommands.java b/c2me-server-utils/src/main/java/com/ishland/c2me/server/utils/common/C2MECommands.java +index 2aa5b849..2f9d3cc3 100644 +--- a/c2me-server-utils/src/main/java/com/ishland/c2me/server/utils/common/C2MECommands.java ++++ b/c2me-server-utils/src/main/java/com/ishland/c2me/server/utils/common/C2MECommands.java +@@ -1,7 +1,7 @@ + package com.ishland.c2me.server.utils.common; + + import com.ishland.c2me.base.mixin.access.IServerChunkManager; +-import com.ishland.c2me.notickvd.common.IChunkTicketManager; ++import com.ishland.c2me.notickvd.common.ChunkTicketManagerExtension; + import com.mojang.brigadier.CommandDispatcher; + import com.mojang.brigadier.context.CommandContext; + import net.minecraft.server.command.CommandManager; +@@ -36,10 +36,8 @@ public class C2MECommands { + private static int noTickCommand(CommandContext ctx) { + final ServerChunkManager chunkManager = ctx.getSource().getWorld().toServerWorld().getChunkManager(); + final ChunkTicketManager ticketManager = ((IServerChunkManager) chunkManager).getTicketManager(); +- final int noTickOnlyChunks = ((IChunkTicketManager) ticketManager).getNoTickOnlyChunks().size(); +- final int noTickPendingTicketUpdates = ((IChunkTicketManager) ticketManager).getNoTickPendingTicketUpdates(); +- ctx.getSource().sendFeedback(() -> Text.of(String.format("No-tick chunks: %d", noTickOnlyChunks)), true); +- ctx.getSource().sendFeedback(() -> Text.of(String.format("No-tick chunk pending ticket updates: %d", noTickPendingTicketUpdates)), true); ++ final long noTickPendingTicketUpdates = ((ChunkTicketManagerExtension) ticketManager).getPendingLoadsCount(); ++ ctx.getSource().sendFeedback(() -> Text.of(String.format("No-tick chunk pending chunk loads: %d", noTickPendingTicketUpdates)), true); + + return 0; + } +@@ -48,7 +46,7 @@ public class C2MECommands { + // final ServerWorld serverWorld = ctx.getSource().getWorld().toServerWorld(); + // final ServerChunkManager chunkManager = serverWorld.getChunkManager(); + // final ChunkTicketManager ticketManager = ((IServerChunkManager) chunkManager).getTicketManager(); +-// final LongSet noTickOnlyChunks = ((IChunkTicketManager) ticketManager).getNoTickOnlyChunks(); ++// final LongSet noTickOnlyChunks = ((ChunkTicketManagerExtension) ticketManager).getNoTickOnlyChunks(); + // final Iterable iterable; + // if (noTickOnlyChunks == null) { + // iterable = serverWorld.iterateEntities(); diff --git a/patches/main/0012-fix-notickvd-lighting-desync.patch b/patches/main/0012-fix-notickvd-lighting-desync.patch new file mode 100644 index 0000000..419cc9c --- /dev/null +++ b/patches/main/0012-fix-notickvd-lighting-desync.patch @@ -0,0 +1,854 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Thu, 5 Dec 2024 23:34:28 +0800 +Subject: [PATCH] fix: notickvd lighting desync + +Backported from a4e7f29911605dc3de8bd40427f664a0001c7be9 + +diff --git a/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/IThreadedAnvilChunkStorageTicketManager.java b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/IThreadedAnvilChunkStorageTicketManager.java +new file mode 100644 +index 00000000..af1e4d14 +--- /dev/null ++++ b/c2me-base/src/main/java/com/ishland/c2me/base/mixin/access/IThreadedAnvilChunkStorageTicketManager.java +@@ -0,0 +1,13 @@ ++package com.ishland.c2me.base.mixin.access; ++ ++import net.minecraft.server.world.ServerChunkLoadingManager; ++import org.spongepowered.asm.mixin.Mixin; ++import org.spongepowered.asm.mixin.gen.Accessor; ++ ++@Mixin(ServerChunkLoadingManager.TicketManager.class) ++public interface IThreadedAnvilChunkStorageTicketManager { ++ ++ @Accessor("field_17443") ++ ServerChunkLoadingManager c2me$getSuperClass(); ++ ++} +diff --git a/c2me-base/src/main/resources/c2me-base.mixins.json b/c2me-base/src/main/resources/c2me-base.mixins.json +index b94c3ecd..2f5d0395 100644 +--- a/c2me-base/src/main/resources/c2me-base.mixins.json ++++ b/c2me-base/src/main/resources/c2me-base.mixins.json +@@ -45,6 +45,7 @@ + "access.ISyncedClientOptions", + "access.ITACSTicketManager", + "access.IThreadedAnvilChunkStorage", ++ "access.IThreadedAnvilChunkStorageTicketManager", + "access.IUpgradeData", + "access.IVersionedChunkStorage", + "access.IWeightedList", +diff --git a/c2me-notickvd/build.gradle b/c2me-notickvd/build.gradle +index cf7e746b..2fda1926 100644 +--- a/c2me-notickvd/build.gradle ++++ b/c2me-notickvd/build.gradle +@@ -1,5 +1,6 @@ + moduleDependencies(project, [ + "c2me-base", ++ "c2me-rewrites-chunk-system", + ]) + + jar { +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/ChunkTicketManagerExtension.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/ChunkTicketManagerExtension.java +index 4bddda8c..18387ee8 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/ChunkTicketManagerExtension.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/ChunkTicketManagerExtension.java +@@ -1,11 +1,9 @@ + package com.ishland.c2me.notickvd.common; + +-import it.unimi.dsi.fastutil.longs.LongSet; +- + public interface ChunkTicketManagerExtension { + +- LongSet getNoTickOnlyChunks(); ++ long c2me$getPendingLoadsCount(); + +- long getPendingLoadsCount(); ++ void c2me$closeNoTickVD(); + + } +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NoTickSystem.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NoTickSystem.java +index 7c8fc4e0..db576cc6 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NoTickSystem.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/NoTickSystem.java +@@ -2,12 +2,10 @@ package com.ishland.c2me.notickvd.common; + + import com.ishland.c2me.base.common.GlobalExecutors; + import it.unimi.dsi.fastutil.longs.LongSet; +-import net.minecraft.server.world.ChunkTicketManager; + import net.minecraft.server.world.ServerChunkLoadingManager; + import net.minecraft.util.math.ChunkPos; + + import java.util.ArrayList; +-import java.util.LinkedList; + import java.util.List; + import java.util.concurrent.ConcurrentLinkedQueue; + import java.util.concurrent.Executor; +@@ -15,8 +13,6 @@ import java.util.concurrent.atomic.AtomicBoolean; + + public class NoTickSystem { + +- private final ChunkTicketManager chunkTicketManager; +- + private final PlayerNoTickLoader playerNoTickLoader; + private final ConcurrentLinkedQueue pendingActionsOnScheduler = new ConcurrentLinkedQueue<>(); + final ConcurrentLinkedQueue mainBeforeTicketTicks = new ConcurrentLinkedQueue<>(); +@@ -27,9 +23,8 @@ public class NoTickSystem { + private volatile boolean pendingPurge = false; + private volatile long age = 0; + +- public NoTickSystem(ChunkTicketManager chunkTicketManager) { +- this.chunkTicketManager = chunkTicketManager; +- this.playerNoTickLoader = new PlayerNoTickLoader(chunkTicketManager, this); ++ public NoTickSystem(ServerChunkLoadingManager tacs) { ++ this.playerNoTickLoader = new PlayerNoTickLoader(tacs, this); + } + + public void addPlayerSource(ChunkPos chunkPos) { +@@ -52,11 +47,11 @@ public class NoTickSystem { + drainQueue(this.mainAfterTicketTicks); + } + +- public void tick(ServerChunkLoadingManager tacs) { +- scheduleTick(tacs); ++ public void tick() { ++ scheduleTick(); + } + +- private void scheduleTick(ServerChunkLoadingManager tacs) { ++ private void scheduleTick() { + if (!this.pendingActionsOnScheduler.isEmpty() && this.isTicking.compareAndSet(false, true)) { + List tasks = new ArrayList<>(this.pendingActionsOnScheduler.size() + 3); + { +@@ -75,8 +70,8 @@ public class NoTickSystem { + } + } + +- this.playerNoTickLoader.tick(tacs); +- if (!this.pendingActionsOnScheduler.isEmpty() || !tasks.isEmpty()) scheduleTick(tacs); // run more tasks ++ this.playerNoTickLoader.tick(); ++ if (!this.pendingActionsOnScheduler.isEmpty() || !tasks.isEmpty()) scheduleTick(); // run more tasks + } finally { + this.isTicking.set(false); + } +@@ -100,11 +95,18 @@ public class NoTickSystem { + this.pendingPurge = true; + } + +- public LongSet getNoTickOnlyChunksSnapshot() { +- return null; +- } +- + public long getPendingLoadsCount() { + return this.playerNoTickLoader.getPendingLoadsCount(); + } ++ ++ public void close() { ++ this.playerNoTickLoader.close(); ++ executor.execute(() -> { ++ try { ++ this.playerNoTickLoader.tick(); ++ } catch (Throwable t) { ++ t.printStackTrace(); ++ } ++ }); ++ } + } +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +index 6eebb93e..319e00c9 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +@@ -1,8 +1,15 @@ + package com.ishland.c2me.notickvd.common; + +-import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; + import com.ishland.c2me.notickvd.common.iterators.ChunkIterator; + import com.ishland.c2me.notickvd.common.iterators.SpiralIterator; ++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.ducks.IChunkSystemAccess; ++import com.ishland.flowsched.scheduler.ItemHolder; ++import com.ishland.flowsched.scheduler.ItemTicket; ++import com.ishland.flowsched.scheduler.StatusAdvancingScheduler; + import com.mojang.logging.LogUtils; + import it.unimi.dsi.fastutil.longs.Long2ReferenceLinkedOpenHashMap; + import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; +@@ -11,37 +18,35 @@ import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; + import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; + import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +-import net.minecraft.server.world.ChunkHolder; +-import net.minecraft.server.world.ChunkTicketManager; +-import net.minecraft.server.world.ChunkTicketType; + import net.minecraft.server.world.ServerChunkLoadingManager; +-import net.minecraft.util.Unit; + import net.minecraft.util.math.ChunkPos; + import org.slf4j.Logger; + + import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.atomic.AtomicBoolean; + import java.util.function.LongFunction; + + public class PlayerNoTickLoader { + + private static final Logger LOGGER = LogUtils.getLogger(); + +- public static final ChunkTicketType TICKET_TYPE = ChunkTicketType.create("c2me_no_tick_vd", (a, b) -> 0); ++ public static final ItemTicket.TicketType TICKET_TYPE = new ItemTicket.TicketType("c2me:notickvd"); + +- private final ChunkTicketManager ticketManager; ++ private final ServerChunkLoadingManager tacs; + private final NoTickSystem noTickSystem; + private final Long2ReferenceLinkedOpenHashMap iterators = new Long2ReferenceLinkedOpenHashMap<>(); + private final LongSet managedChunks = new LongLinkedOpenHashSet(); + private final LongFunction createFunction = pos -> new SpiralIterator(ChunkPos.getPackedX(pos), ChunkPos.getPackedZ(pos), this.viewDistance); + private final ReferenceArrayList> chunkLoadFutures = new ReferenceArrayList<>(); ++ private final AtomicBoolean closing = new AtomicBoolean(false); + + private int viewDistance = 12; + private boolean dirtyManagedChunks = false; + private boolean recreateIterators = false; + private volatile long pendingLoadsCountSnapshot = 0L; + +- public PlayerNoTickLoader(ChunkTicketManager ticketManager, NoTickSystem noTickSystem) { +- this.ticketManager = ticketManager; ++ public PlayerNoTickLoader(ServerChunkLoadingManager tacs, NoTickSystem noTickSystem) { ++ this.tacs = tacs; + this.noTickSystem = noTickSystem; + } + +@@ -60,7 +65,12 @@ public class PlayerNoTickLoader { + this.recreateIterators = true; + } + +- public void tick(ServerChunkLoadingManager tacs) { ++ public void tick() { ++ if (this.closing.get()) { ++ clearTickets(); ++ return; ++ } ++ + if (this.recreateIterators) { + this.dirtyManagedChunks = true; + ObjectBidirectionalIterator> iterator = this.iterators.long2ReferenceEntrySet().fastIterator(); +@@ -100,7 +110,7 @@ public class PlayerNoTickLoader { + this.dirtyManagedChunks = false; + } + +- this.tickFutures(tacs); ++ this.tickFutures(); + + { + long pendingLoadsCount = 0L; +@@ -113,13 +123,25 @@ public class PlayerNoTickLoader { + } + } + +- void tickFutures(ServerChunkLoadingManager tacs) { ++ private void clearTickets() { ++ LongIterator iterator = this.managedChunks.iterator(); ++ while (iterator.hasNext()) { ++ long pos = iterator.nextLong(); ++ ++ this.removeTicket0(ChunkPos.getPackedX(pos), ChunkPos.getPackedZ(pos)); ++ ++ iterator.remove(); ++ } ++ } ++ ++ void tickFutures() { + this.chunkLoadFutures.removeIf(CompletableFuture::isDone); + +- while (this.chunkLoadFutures.size() < Config.maxConcurrentChunkLoads && this.addOneTicket(tacs)); ++ if (this.closing.get()) return; ++ while (this.chunkLoadFutures.size() < Config.maxConcurrentChunkLoads && this.addOneTicket()); + } + +- private boolean addOneTicket(ServerChunkLoadingManager tacs) { ++ private boolean addOneTicket() { + ObjectBidirectionalIterator> iteratorIterator = this.iterators.long2ReferenceEntrySet().fastIterator(); + while (iteratorIterator.hasNext()) { + Long2ReferenceMap.Entry entry = iteratorIterator.next(); +@@ -127,7 +149,7 @@ public class PlayerNoTickLoader { + while (iterator.hasNext()) { + ChunkPos pos = iterator.next(); + if (this.managedChunks.add(pos.toLong())) { +- this.chunkLoadFutures.add(loadChunk(tacs, pos.x, pos.z)); ++ this.chunkLoadFutures.add(loadChunk(pos.x, pos.z)); + this.iterators.getAndMoveToLast(entry.getLongKey()); + return true; + } +@@ -137,27 +159,12 @@ public class PlayerNoTickLoader { + return false; + } + +- private CompletableFuture loadChunk(ServerChunkLoadingManager tacs, int x, int z) { +- CompletableFuture future = this.addTicket0(x, z) +- .thenComposeAsync(unused -> { +- ChunkHolder holder = ((IThreadedAnvilChunkStorage) tacs).invokeGetChunkHolder(ChunkPos.toLong(x, z)); +- if (holder == null) { +- LOGGER.warn("No holder created after adding ticket to chunk [{}, {}]", x, z); +- return CompletableFuture.completedFuture(null); +- } else { +- return holder.getAccessibleFuture() +- .handle((worldChunkOptionalChunk, throwable) -> { +- if (throwable != null) { +- LOGGER.error("Failed to load chunk [{}, {}]", x, z); +- } +- return null; +- }); +- } +- }, this.noTickSystem.mainAfterTicketTicks::add); ++ private CompletableFuture loadChunk(int x, int z) { ++ CompletableFuture future = this.loadChunk0(x, z); + future.thenRunAsync(() -> { + try { + this.chunkLoadFutures.remove(future); +- this.tickFutures(tacs); ++ this.tickFutures(); + } catch (Throwable t) { + LOGGER.error("Error while loading chunk [{}, {}]", x, z, t); + } +@@ -165,15 +172,33 @@ public class PlayerNoTickLoader { + return future; + } + +- private CompletableFuture addTicket0(int x, int z) { +- return CompletableFuture.runAsync(() -> this.ticketManager.addTicketWithLevel(TICKET_TYPE, new ChunkPos(x, z), 33, Unit.INSTANCE), this.noTickSystem.mainBeforeTicketTicks::add); ++ private CompletableFuture loadChunk0(int x, int z) { ++ ChunkPos pos = new ChunkPos(x, z); ++ ItemHolder holder = ((IChunkSystemAccess) this.tacs).c2me$getTheChunkSystem().addTicket( ++ pos, ++ TICKET_TYPE, ++ pos, ++ NewChunkStatus.SERVER_ACCESSIBLE_CHUNK_SENDING, ++ StatusAdvancingScheduler.NO_OP ++ ); ++ return holder.getFutureForStatus(NewChunkStatus.SERVER_ACCESSIBLE); + } + + private void removeTicket0(int x, int z) { +- this.noTickSystem.mainBeforeTicketTicks.add(() -> this.ticketManager.removeTicketWithLevel(TICKET_TYPE, new ChunkPos(x, z), 33, Unit.INSTANCE)); ++ ChunkPos pos = new ChunkPos(x, z); ++ ((IChunkSystemAccess) this.tacs).c2me$getTheChunkSystem().removeTicket( ++ pos, ++ TICKET_TYPE, ++ pos, ++ NewChunkStatus.SERVER_ACCESSIBLE_CHUNK_SENDING ++ ); + } + + public long getPendingLoadsCount() { + return this.pendingLoadsCountSnapshot; + } ++ ++ public void close() { ++ this.closing.set(true); ++ } + } +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkGenerationSteps.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkGenerationSteps.java +deleted file mode 100644 +index 139e5d5f..00000000 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkGenerationSteps.java ++++ /dev/null +@@ -1,18 +0,0 @@ +-package com.ishland.c2me.notickvd.mixin; +- +-import com.llamalad7.mixinextras.injector.ModifyReturnValue; +-import net.minecraft.world.chunk.ChunkGenerationStep; +-import net.minecraft.world.chunk.ChunkGenerationSteps; +-import net.minecraft.world.chunk.ChunkStatus; +-import org.spongepowered.asm.mixin.Mixin; +-import org.spongepowered.asm.mixin.injection.At; +- +-@Mixin(ChunkGenerationSteps.class) +-public class MixinChunkGenerationSteps { +- +- @ModifyReturnValue(method = {"method_60531", "method_60519"}, at = @At("RETURN"), require = 2) +- private static ChunkGenerationStep.Builder requireNeighborsLit(ChunkGenerationStep.Builder original) { +- return original.dependsOn(ChunkStatus.LIGHT, 1); +- } +- +-} +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java +index 59706a3e..1d683e6d 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java +@@ -1,8 +1,8 @@ + package com.ishland.c2me.notickvd.mixin; + ++import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorageTicketManager; + import com.ishland.c2me.notickvd.common.ChunkTicketManagerExtension; + import com.ishland.c2me.notickvd.common.NoTickSystem; +-import it.unimi.dsi.fastutil.longs.LongSet; + import net.minecraft.server.network.ServerPlayerEntity; + import net.minecraft.server.world.ChunkTicketManager; + import net.minecraft.server.world.ServerChunkLoadingManager; +@@ -35,7 +35,7 @@ public class MixinChunkTicketManager implements ChunkTicketManagerExtension { + + @Inject(method = "", at = @At("RETURN")) + private void onInit(CallbackInfo ci) { +- this.noTickSystem = new NoTickSystem((ChunkTicketManager) (Object) this); ++ this.noTickSystem = new NoTickSystem(((IThreadedAnvilChunkStorageTicketManager) this).c2me$getSuperClass()); + } + + @Inject(method = "handleChunkEnter", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ChunkTicketManager$DistanceFromNearestPlayerTracker;updateLevel(JIZ)V", ordinal = 0, shift = At.Shift.AFTER)) +@@ -61,7 +61,7 @@ public class MixinChunkTicketManager implements ChunkTicketManagerExtension { + @Inject(method = "update", at = @At("RETURN")) + private void onTick(ServerChunkLoadingManager chunkStorage, CallbackInfoReturnable cir) { + this.noTickSystem.afterTicketTicks(); +- this.noTickSystem.tick(chunkStorage); ++ this.noTickSystem.tick(); + } + + /** +@@ -84,13 +84,12 @@ public class MixinChunkTicketManager implements ChunkTicketManagerExtension { + + @Override + @Unique +- public LongSet getNoTickOnlyChunks() { +- return this.noTickSystem.getNoTickOnlyChunksSnapshot(); ++ public long c2me$getPendingLoadsCount() { ++ return this.noTickSystem.getPendingLoadsCount(); + } + + @Override +- @Unique +- public long getPendingLoadsCount() { +- return this.noTickSystem.getPendingLoadsCount(); ++ public void c2me$closeNoTickVD() { ++ this.noTickSystem.close(); + } + } +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinMinecraftServer.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinMinecraftServer.java +new file mode 100644 +index 00000000..7fe56bb4 +--- /dev/null ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinMinecraftServer.java +@@ -0,0 +1,25 @@ ++package com.ishland.c2me.notickvd.mixin; ++ ++import com.ishland.c2me.base.mixin.access.IServerChunkManager; ++import com.ishland.c2me.notickvd.common.ChunkTicketManagerExtension; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.world.ServerWorld; ++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.CallbackInfo; ++ ++@Mixin(MinecraftServer.class) ++public abstract class MixinMinecraftServer { ++ ++ @Shadow public abstract Iterable getWorlds(); ++ ++ @Inject(method = "shutdown", at = @At(value = "INVOKE_STRING", target = "Lorg/slf4j/Logger;info(Ljava/lang/String;)V", args = "ldc=Saving worlds")) ++ private void stopNoTickVD(CallbackInfo ci) { ++ for (ServerWorld world : this.getWorlds()) { ++ ((ChunkTicketManagerExtension) ((IServerChunkManager) world.getChunkManager()).getTicketManager()).c2me$closeNoTickVD(); ++ } ++ } ++ ++} +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessible.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessible.java +deleted file mode 100644 +index aae07fe4..00000000 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessible.java ++++ /dev/null +@@ -1,40 +0,0 @@ +-package com.ishland.c2me.notickvd.mixin; +- +-import com.ishland.c2me.base.mixin.access.IThreadedAnvilChunkStorage; +-import com.ishland.c2me.notickvd.common.Config; +-import com.ishland.flowsched.scheduler.ItemHolder; +-import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +-import net.minecraft.server.world.ServerChunkLoadingManager; +-import net.minecraft.util.math.ChunkPos; +-import org.spongepowered.asm.mixin.Dynamic; +-import org.spongepowered.asm.mixin.Mixin; +-import org.spongepowered.asm.mixin.Overwrite; +-import org.spongepowered.asm.mixin.Pseudo; +-import org.spongepowered.asm.mixin.Unique; +-import org.spongepowered.asm.mixin.injection.At; +- +-@Pseudo +-@Mixin(targets = "com.ishland.c2me.rewrites.chunksystem.common.statuses.ServerAccessible") +-public class MixinServerAccessible { +- +- /** +- * @author ishland +- * @reason send chunks +- */ +- @Overwrite(remap = false) +- private static boolean needSendChunks() { +- return true; +- } +- +- @Dynamic +- @WrapOperation(method = "lambda$upgradeToThis$0", at = @At(value = "INVOKE", target = "Lcom/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessible;sendChunkToPlayer(Lnet/minecraft/server/world/ServerChunkLoadingManager;Lcom/ishland/flowsched/scheduler/ItemHolder;)V", remap = true), remap = false) +- private static void wrapSendChunks(ServerChunkLoadingManager tacs, ItemHolder holder, Operation original) { +- if (Config.compatibilityMode) { +- ((IThreadedAnvilChunkStorage) tacs).getMainThreadExecutor().submit(() -> original.call(tacs, holder)); +- } else { +- original.call(tacs, holder); +- } +- } +- +-} +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessibleChunkSending.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessibleChunkSending.java +new file mode 100644 +index 00000000..4bb24b66 +--- /dev/null ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessibleChunkSending.java +@@ -0,0 +1,75 @@ ++package com.ishland.c2me.notickvd.mixin; ++ ++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.statuses.ServerAccessibleChunkSending; ++import com.ishland.flowsched.scheduler.Cancellable; ++import com.ishland.flowsched.scheduler.ItemHolder; ++import com.ishland.flowsched.scheduler.KeyStatusPair; ++import net.minecraft.server.world.ServerChunkLoadingManager; ++import net.minecraft.util.math.ChunkPos; ++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.Mutable; ++import org.spongepowered.asm.mixin.Overwrite; ++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.callback.CallbackInfo; ++ ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionStage; ++ ++@Mixin(ServerAccessibleChunkSending.class) ++public class MixinServerAccessibleChunkSending { ++ ++ @Mutable ++ @Shadow(remap = false) ++ @Final ++ private static KeyStatusPair[] deps; ++ ++ @Inject(method = "", at = @At("RETURN")) ++ private static void onCLInit(CallbackInfo ci) { ++ NewChunkStatus depStatus = NewChunkStatus.fromVanillaStatus(ChunkStatus.LIGHT); ++ deps = new KeyStatusPair[]{ ++ new KeyStatusPair<>(new ChunkPos(-1, -1), depStatus), ++ new KeyStatusPair<>(new ChunkPos(-1, 0), depStatus), ++ new KeyStatusPair<>(new ChunkPos(-1, 1), depStatus), ++ new KeyStatusPair<>(new ChunkPos(0, -1), depStatus), ++ new KeyStatusPair<>(new ChunkPos(0, 1), depStatus), ++ new KeyStatusPair<>(new ChunkPos(1, -1), depStatus), ++ new KeyStatusPair<>(new ChunkPos(1, 0), depStatus), ++ new KeyStatusPair<>(new ChunkPos(1, 1), depStatus), ++ }; ++ } ++ ++ /** ++ * @author ishland ++ * @reason do chunk sending ++ */ ++ @Overwrite(remap = false) ++ public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancellable cancellable) { ++ return CompletableFuture.runAsync(() -> sendChunkToPlayer(context.tacs(), context.holder()), ((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor()); ++ } ++ ++ @Unique ++ private static void sendChunkToPlayer(ServerChunkLoadingManager tacs, ItemHolder holder) { ++ final Chunk chunk = holder.getItem().get().chunk(); ++ if (chunk instanceof WorldChunk worldChunk) { ++ CompletableFuture completableFuturexx = holder.getUserData().get().getPostProcessingFuture(); ++ if (completableFuturexx.isDone()) { ++ ((IThreadedAnvilChunkStorage) tacs).invokeSendToPlayers(worldChunk); ++ } else { ++ completableFuturexx.thenAcceptAsync(v -> ((IThreadedAnvilChunkStorage) tacs).invokeSendToPlayers(worldChunk), ((IThreadedAnvilChunkStorage) tacs).getMainThreadExecutor()); ++ } ++ } ++ } ++ ++} +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerBlockTicking.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerBlockTicking.java +index f2e21e2f..c0533c52 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerBlockTicking.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerBlockTicking.java +@@ -1,7 +1,7 @@ + package com.ishland.c2me.notickvd.mixin; + + import com.ishland.c2me.notickvd.common.Config; +-import org.spongepowered.asm.mixin.Dynamic; ++import com.ishland.c2me.rewrites.chunksystem.common.statuses.ServerBlockTicking; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.Pseudo; + import org.spongepowered.asm.mixin.injection.At; +@@ -9,10 +9,9 @@ import org.spongepowered.asm.mixin.injection.Inject; + import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + + @Pseudo +-@Mixin(targets = "com.ishland.c2me.rewrites.chunksystem.common.statuses.ServerBlockTicking", remap = false) ++@Mixin(value = ServerBlockTicking.class, remap = false) + public class MixinServerBlockTicking { + +- @Dynamic + @Inject(method = "sendChunkToPlayer(Lcom/ishland/c2me/rewrites/chunksystem/common/ChunkLoadingContext;)V", at = @At("HEAD"), remap = false, cancellable = true) + private static void preventChunkSending(CallbackInfo ci) { + if (!Config.ensureChunkCorrectness) { +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinThreadedAnvilChunkStorage.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinThreadedAnvilChunkStorage.java +index 32021941..4e3c9e48 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinThreadedAnvilChunkStorage.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinThreadedAnvilChunkStorage.java +@@ -81,18 +81,4 @@ public abstract class MixinThreadedAnvilChunkStorage { + return Config.ensureChunkCorrectness; // TODO config set to false unfixes MC-264947 + } + +- // private static synthetic method_20582(Lnet/minecraft/world/chunk/Chunk;)Z +- @Dynamic +- @Inject(method = "method_20582", at = @At("RETURN"), cancellable = true) // TODO lambda expression of the 1st filter "chunk instanceof ReadOnlyChunk || chunk instanceof WorldChunk" +- private static void onSaveFilter1(Chunk chunk, CallbackInfoReturnable cir) { +- if (true) return; +- if (chunk instanceof WorldChunk worldChunk) { +- final ServerWorld serverWorld = (ServerWorld) worldChunk.getWorld(); +- final IServerChunkManager serverChunkManager = (IServerChunkManager) serverWorld.getChunkManager(); +- final ChunkTicketManagerExtension ticketManager = +- (ChunkTicketManagerExtension) serverChunkManager.getTicketManager(); +- cir.setReturnValue(cir.getReturnValueZ() && !ticketManager.getNoTickOnlyChunks().contains(chunk.getPos().toLong())); +- } +- } +- + } +diff --git a/c2me-notickvd/src/main/resources/c2me-notickvd.mixins.json b/c2me-notickvd/src/main/resources/c2me-notickvd.mixins.json +index da5feb8d..c03ffb8f 100644 +--- a/c2me-notickvd/src/main/resources/c2me-notickvd.mixins.json ++++ b/c2me-notickvd/src/main/resources/c2me-notickvd.mixins.json +@@ -4,12 +4,12 @@ + "package": "com.ishland.c2me.notickvd.mixin", + "plugin": "com.ishland.c2me.base.common.ModuleMixinPlugin", + "mixins": [ +- "MixinChunkGenerationSteps", + "MixinChunkHolder", + "MixinChunkTicketManager", + "MixinChunkTicketManagerNearbyChunkTicketUpdater", ++ "MixinMinecraftServer", + "MixinPlayerManager", +- "MixinServerAccessible", ++ "MixinServerAccessibleChunkSending", + "MixinServerBlockTicking", + "MixinServerChunkManager", + "MixinThreadedAnvilChunkStorage", +diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/NewChunkStatus.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/NewChunkStatus.java +index 052508d6..8dd0a219 100644 +--- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/NewChunkStatus.java ++++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/NewChunkStatus.java +@@ -3,6 +3,7 @@ package com.ishland.c2me.rewrites.chunksystem.common; + import com.ishland.c2me.rewrites.chunksystem.common.statuses.ReadFromDisk; + import com.ishland.c2me.rewrites.chunksystem.common.statuses.ReadFromDiskAsync; + import com.ishland.c2me.rewrites.chunksystem.common.statuses.ServerAccessible; ++import com.ishland.c2me.rewrites.chunksystem.common.statuses.ServerAccessibleChunkSending; + import com.ishland.c2me.rewrites.chunksystem.common.statuses.ServerBlockTicking; + import com.ishland.c2me.rewrites.chunksystem.common.statuses.ServerEntityTicking; + import com.ishland.c2me.rewrites.chunksystem.common.statuses.VanillaWorldGenerationDelegate; +@@ -33,6 +34,7 @@ public abstract class NewChunkStatus implements ItemStatus[] deps; +- +- static { +- final NewChunkStatus depStatus = NewChunkStatus.fromVanillaStatus(ChunkLevels.getStatusForAdditionalLevel(1)); +- deps = new KeyStatusPair[]{ +- new KeyStatusPair<>(new ChunkPos(-1, -1), depStatus), +- new KeyStatusPair<>(new ChunkPos(-1, 0), depStatus), +- new KeyStatusPair<>(new ChunkPos(-1, 1), depStatus), +- new KeyStatusPair<>(new ChunkPos(0, -1), depStatus), +- new KeyStatusPair<>(new ChunkPos(0, 1), depStatus), +- new KeyStatusPair<>(new ChunkPos(1, -1), depStatus), +- new KeyStatusPair<>(new ChunkPos(1, 0), depStatus), +- new KeyStatusPair<>(new ChunkPos(1, 1), depStatus), +- }; +- } +- + public ServerAccessible(int ordinal) { + super(ordinal, ChunkStatus.FULL); + } +@@ -109,30 +88,10 @@ public class ServerAccessible extends NewChunkStatus { + + ((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()); +- } + } + }, ((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor()); + } + +- private static boolean needSendChunks() { +- return false; +- } +- +- private static void sendChunkToPlayer(ServerChunkLoadingManager tacs, ItemHolder holder) { +- final Chunk chunk = holder.getItem().get().chunk(); +- if (chunk instanceof WorldChunk worldChunk) { +- CompletableFuture completableFuturexx = holder.getUserData().get().getPostProcessingFuture(); +- if (completableFuturexx.isDone()) { +- ((IThreadedAnvilChunkStorage) tacs).invokeSendToPlayers(worldChunk); +- } else { +- completableFuturexx.thenAcceptAsync(v -> ((IThreadedAnvilChunkStorage) tacs).invokeSendToPlayers(worldChunk), ((IThreadedAnvilChunkStorage) tacs).getMainThreadExecutor()); +- } +- } +- } +- + private static WorldChunk toFullChunk(ProtoChunk protoChunk, ServerWorld serverWorld) { + WorldChunk worldChunk; + if (protoChunk instanceof WrapperProtoChunk) { +@@ -166,21 +125,6 @@ public class ServerAccessible extends NewChunkStatus { + }, ((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor()); + } + +- @Override +- public KeyStatusPair[] getDependencies(ItemHolder holder) { +- return relativeToAbsoluteDependencies(holder, deps); +- } +- +- @Override +- public KeyStatusPair[] getDependenciesToRemove(ItemHolder holder) { +- return EMPTY_DEPENDENCIES; +- } +- +- @Override +- public KeyStatusPair[] getDependenciesToAdd(ItemHolder holder) { +- return EMPTY_DEPENDENCIES; +- } +- + @Override + public String toString() { + return "minecraft:full, Border"; +diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessibleChunkSending.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessibleChunkSending.java +new file mode 100644 +index 00000000..b82f83b6 +--- /dev/null ++++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessibleChunkSending.java +@@ -0,0 +1,56 @@ ++package com.ishland.c2me.rewrites.chunksystem.common.statuses; ++ ++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.flowsched.scheduler.Cancellable; ++import com.ishland.flowsched.scheduler.ItemHolder; ++import com.ishland.flowsched.scheduler.KeyStatusPair; ++import net.minecraft.util.math.ChunkPos; ++import net.minecraft.world.chunk.ChunkStatus; ++ ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionStage; ++ ++public class ServerAccessibleChunkSending extends NewChunkStatus { ++ ++ private static final KeyStatusPair[] deps; ++ ++ static { ++ deps = EMPTY_DEPENDENCIES; ++ } ++ ++ public ServerAccessibleChunkSending(int ordinal) { ++ super(ordinal, ChunkStatus.FULL); ++ } ++ ++ @Override ++ public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancellable cancellable) { ++ return CompletableFuture.completedFuture(null); ++ } ++ ++ @Override ++ public CompletionStage downgradeFromThis(ChunkLoadingContext context, Cancellable cancellable) { ++ return CompletableFuture.completedFuture(null); ++ } ++ ++ @Override ++ public KeyStatusPair[] getDependencies(ItemHolder holder) { ++ return relativeToAbsoluteDependencies(holder, deps); ++ } ++ ++ @Override ++ public KeyStatusPair[] getDependenciesToRemove(ItemHolder holder) { ++ return EMPTY_DEPENDENCIES; ++ } ++ ++ @Override ++ public KeyStatusPair[] getDependenciesToAdd(ItemHolder holder) { ++ return EMPTY_DEPENDENCIES; ++ } ++ ++ @Override ++ public String toString() { ++ return "minecraft:full, Border, Chunk Sending"; ++ } ++} +diff --git a/c2me-server-utils/src/main/java/com/ishland/c2me/server/utils/common/C2MECommands.java b/c2me-server-utils/src/main/java/com/ishland/c2me/server/utils/common/C2MECommands.java +index 2f9d3cc3..f63a2495 100644 +--- a/c2me-server-utils/src/main/java/com/ishland/c2me/server/utils/common/C2MECommands.java ++++ b/c2me-server-utils/src/main/java/com/ishland/c2me/server/utils/common/C2MECommands.java +@@ -36,7 +36,7 @@ public class C2MECommands { + private static int noTickCommand(CommandContext ctx) { + final ServerChunkManager chunkManager = ctx.getSource().getWorld().toServerWorld().getChunkManager(); + final ChunkTicketManager ticketManager = ((IServerChunkManager) chunkManager).getTicketManager(); +- final long noTickPendingTicketUpdates = ((ChunkTicketManagerExtension) ticketManager).getPendingLoadsCount(); ++ final long noTickPendingTicketUpdates = ((ChunkTicketManagerExtension) ticketManager).c2me$getPendingLoadsCount(); + ctx.getSource().sendFeedback(() -> Text.of(String.format("No-tick chunk pending chunk loads: %d", noTickPendingTicketUpdates)), true); + + return 0; diff --git a/patches/main/0013-fix-mobcaps-counting-in-border-chunks.patch b/patches/main/0013-fix-mobcaps-counting-in-border-chunks.patch new file mode 100644 index 0000000..d508926 --- /dev/null +++ b/patches/main/0013-fix-mobcaps-counting-in-border-chunks.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Wed, 1 Jan 2025 12:14:11 +0800 +Subject: [PATCH] fix: mobcaps counting in border chunks + +Backported from 28a682c159503b5ad1ed8b4e14bda39d7f6319ce + +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinSimulationDistanceLevelPropagator.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinSimulationDistanceLevelPropagator.java +new file mode 100644 +index 00000000..cb3450e0 +--- /dev/null ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinSimulationDistanceLevelPropagator.java +@@ -0,0 +1,26 @@ ++package com.ishland.c2me.notickvd.mixin; ++ ++import net.minecraft.world.SimulationDistanceLevelPropagator; ++import org.spongepowered.asm.mixin.Mixin; ++import org.spongepowered.asm.mixin.injection.Constant; ++import org.spongepowered.asm.mixin.injection.ModifyConstant; ++ ++@Mixin(SimulationDistanceLevelPropagator.class) ++public class MixinSimulationDistanceLevelPropagator { ++ ++ @ModifyConstant(method = "", constant = {@Constant(intValue = 34), @Constant(intValue = 33)}, require = 2) ++ private static int modifyMax(int constant) { ++ return constant + 1; ++ } ++ ++ @ModifyConstant(method = "getLevel(Lnet/minecraft/util/collection/SortedArraySet;)I", constant = @Constant(intValue = 34)) ++ private int modifyMax1(int constant) { ++ return constant + 1; ++ } ++ ++ @ModifyConstant(method = "setLevel", constant = @Constant(intValue = 33)) ++ private int modifyMax2(int constant) { ++ return constant + 1; ++ } ++ ++} +diff --git a/c2me-notickvd/src/main/resources/c2me-notickvd.mixins.json b/c2me-notickvd/src/main/resources/c2me-notickvd.mixins.json +index c03ffb8f..be745776 100644 +--- a/c2me-notickvd/src/main/resources/c2me-notickvd.mixins.json ++++ b/c2me-notickvd/src/main/resources/c2me-notickvd.mixins.json +@@ -12,6 +12,7 @@ + "MixinServerAccessibleChunkSending", + "MixinServerBlockTicking", + "MixinServerChunkManager", ++ "MixinSimulationDistanceLevelPropagator", + "MixinThreadedAnvilChunkStorage", + "MixinWorld", + "MixinWorldChunk", diff --git a/patches/main/0014-fix-add-vanilla-tickets-in-notickvd.patch b/patches/main/0014-fix-add-vanilla-tickets-in-notickvd.patch new file mode 100644 index 0000000..48b6ebb --- /dev/null +++ b/patches/main/0014-fix-add-vanilla-tickets-in-notickvd.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Fri, 6 Dec 2024 23:12:12 +0800 +Subject: [PATCH] fix: add vanilla tickets in notickvd + +Backported from c75bb5d0ad71904d710a4475896f2ebf20e5fc1f + +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +index 319e00c9..aabdfd8e 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +@@ -18,10 +18,12 @@ import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; + import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; + import it.unimi.dsi.fastutil.objects.ReferenceArrayList; ++import net.minecraft.server.world.ChunkTicketType; + import net.minecraft.server.world.ServerChunkLoadingManager; + import net.minecraft.util.math.ChunkPos; + import org.slf4j.Logger; + ++import java.util.Comparator; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.atomic.AtomicBoolean; + import java.util.function.LongFunction; +@@ -31,6 +33,7 @@ public class PlayerNoTickLoader { + private static final Logger LOGGER = LogUtils.getLogger(); + + public static final ItemTicket.TicketType TICKET_TYPE = new ItemTicket.TicketType("c2me:notickvd"); ++ public static final ChunkTicketType VANILLA_TICKET_TYPE = ChunkTicketType.create("c2me_notickvd", Comparator.comparingLong(ChunkPos::toLong)); + + private final ServerChunkLoadingManager tacs; + private final NoTickSystem noTickSystem; +@@ -181,6 +184,7 @@ public class PlayerNoTickLoader { + NewChunkStatus.SERVER_ACCESSIBLE_CHUNK_SENDING, + StatusAdvancingScheduler.NO_OP + ); ++ this.noTickSystem.mainBeforeTicketTicks.add(() -> this.tacs.getTicketManager().addTicketWithLevel(VANILLA_TICKET_TYPE, pos, 33, pos)); + return holder.getFutureForStatus(NewChunkStatus.SERVER_ACCESSIBLE); + } + +@@ -192,6 +196,7 @@ public class PlayerNoTickLoader { + pos, + NewChunkStatus.SERVER_ACCESSIBLE_CHUNK_SENDING + ); ++ this.noTickSystem.mainBeforeTicketTicks.add(() -> this.tacs.getTicketManager().removeTicketWithLevel(VANILLA_TICKET_TYPE, pos, 33, pos)); + } + + public long getPendingLoadsCount() { diff --git a/patches/main/0015-fix-no-tick-pending-chunk-loads-counter.patch b/patches/main/0015-fix-no-tick-pending-chunk-loads-counter.patch new file mode 100644 index 0000000..3c0dd3e --- /dev/null +++ b/patches/main/0015-fix-no-tick-pending-chunk-loads-counter.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Thu, 2 Jan 2025 21:39:27 +0800 +Subject: [PATCH] fix: no-tick pending chunk loads counter + +Backported from 6073966852cea0d5ffcf5048831766753dace91a + +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +index aabdfd8e..b2a9c4ce 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +@@ -114,16 +114,6 @@ public class PlayerNoTickLoader { + } + + this.tickFutures(); +- +- { +- long pendingLoadsCount = 0L; +- ObjectBidirectionalIterator> iterator = this.iterators.long2ReferenceEntrySet().fastIterator(); +- while (iterator.hasNext()) { +- Long2ReferenceMap.Entry entry = iterator.next(); +- pendingLoadsCount += entry.getValue().remaining(); +- } +- this.pendingLoadsCountSnapshot = pendingLoadsCount; +- } + } + + private void clearTickets() { +@@ -142,6 +132,16 @@ public class PlayerNoTickLoader { + + if (this.closing.get()) return; + while (this.chunkLoadFutures.size() < Config.maxConcurrentChunkLoads && this.addOneTicket()); ++ ++ { ++ long pendingLoadsCount = 0L; ++ ObjectBidirectionalIterator> iterator = this.iterators.long2ReferenceEntrySet().fastIterator(); ++ while (iterator.hasNext()) { ++ Long2ReferenceMap.Entry entry = iterator.next(); ++ pendingLoadsCount += entry.getValue().remaining(); ++ } ++ this.pendingLoadsCountSnapshot = pendingLoadsCount; ++ } + } + + private boolean addOneTicket() { diff --git a/patches/main/0016-change-sync-1.21.4-MixinServerAccessibleChunkSending.patch b/patches/main/0016-change-sync-1.21.4-MixinServerAccessibleChunkSending.patch new file mode 100644 index 0000000..a22c4bb --- /dev/null +++ b/patches/main/0016-change-sync-1.21.4-MixinServerAccessibleChunkSending.patch @@ -0,0 +1,174 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Fri, 24 Jan 2025 22:11:49 +0800 +Subject: [PATCH] change: sync 1.21.4 MixinServerAccessibleChunkSending + + +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessibleChunkSending.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessibleChunkSending.java +index 4bb24b66..21ada3b6 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessibleChunkSending.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinServerAccessibleChunkSending.java +@@ -1,18 +1,32 @@ + package com.ishland.c2me.notickvd.mixin; + ++import com.google.common.collect.ImmutableList; ++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.Config; + import com.ishland.c2me.rewrites.chunksystem.common.NewChunkHolderVanillaInterface; + import com.ishland.c2me.rewrites.chunksystem.common.NewChunkStatus; + import com.ishland.c2me.rewrites.chunksystem.common.statuses.ServerAccessibleChunkSending; ++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; ++import it.unimi.dsi.fastutil.shorts.ShortList; ++import it.unimi.dsi.fastutil.shorts.ShortListIterator; ++import net.minecraft.block.Block; ++import net.minecraft.block.BlockState; ++import net.minecraft.block.Blocks; + import net.minecraft.server.world.ServerChunkLoadingManager; ++import net.minecraft.server.world.ServerWorld; ++import net.minecraft.util.math.BlockPos; + import net.minecraft.util.math.ChunkPos; ++import net.minecraft.world.ChunkRegion; + import net.minecraft.world.chunk.Chunk; ++import net.minecraft.world.chunk.ChunkGenerationSteps; + import net.minecraft.world.chunk.ChunkStatus; ++import net.minecraft.world.chunk.ProtoChunk; + import net.minecraft.world.chunk.WorldChunk; + import org.spongepowered.asm.mixin.Final; + import org.spongepowered.asm.mixin.Mixin; +@@ -24,6 +38,7 @@ 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.ArrayList; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.CompletionStage; + +@@ -56,7 +71,48 @@ public class MixinServerAccessibleChunkSending { + */ + @Overwrite(remap = false) + public CompletionStage upgradeToThis(ChunkLoadingContext context, Cancellable cancellable) { +- return CompletableFuture.runAsync(() -> sendChunkToPlayer(context.tacs(), context.holder()), ((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor()); ++ ArrayList blocksToRemove = new ArrayList<>(); ++ if (Config.suppressGhostMushrooms) { ++ ServerWorld serverWorld = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); ++ ChunkState state = context.holder().getItem().get(); ++ ChunkRegion chunkRegion = new ChunkRegion(serverWorld, context.chunks(), ChunkGenerationSteps.GENERATION.get(ChunkStatus.FULL), state.protoChunk()); ++ Chunk chunk = state.chunk(); ++ ++ ChunkPos chunkPos = context.holder().getKey(); ++ ++ ShortList[] postProcessingLists = chunk.getPostProcessingLists(); ++ for (int i = 0; i < postProcessingLists.length; i++) { ++ if (postProcessingLists[i] != null) { ++ for (ShortListIterator iterator = postProcessingLists[i].iterator(); iterator.hasNext(); ) { ++ short short_ = iterator.nextShort(); ++ BlockPos blockPos = ProtoChunk.joinBlockPos(short_, chunk.sectionIndexToCoord(i), chunkPos); ++ BlockState blockState = chunk.getBlockState(blockPos); ++ ++ if (blockState.getBlock() == Blocks.BROWN_MUSHROOM || blockState.getBlock() == Blocks.RED_MUSHROOM) { ++ if (!blockState.canPlaceAt(chunkRegion, blockPos)) { ++ blocksToRemove.add(blockPos); ++ } ++ } ++ } ++ } ++ } ++ } ++ return CompletableFuture.runAsync(() -> { ++ try (var ignored = ThreadInstrumentation.getCurrent().begin(new ChunkTaskWork(context, (ServerAccessibleChunkSending) (Object) this, true))) { ++ if (Config.suppressGhostMushrooms) { ++ ServerWorld serverWorld = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); ++ ChunkState state = context.holder().getItem().get(); ++ Chunk chunk = state.chunk(); ++ for (BlockPos blockPos : blocksToRemove) { ++ serverWorld.setBlockState(blockPos, Blocks.AIR.getDefaultState(), Block.NO_REDRAW | Block.FORCE_STATE); ++ } ++ for (BlockPos blockPos2 : ImmutableList.copyOf(chunk.getBlockEntityPositions())) { ++ chunk.getBlockEntity(blockPos2); ++ } ++ } ++ sendChunkToPlayer(context.tacs(), context.holder()); ++ } ++ }, ((IThreadedAnvilChunkStorage) context.tacs()).getMainThreadExecutor()); + } + + @Unique +diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinWorldChunk.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinWorldChunk.java +deleted file mode 100644 +index 7746bcbc..00000000 +--- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinWorldChunk.java ++++ /dev/null +@@ -1,55 +0,0 @@ +-package com.ishland.c2me.rewrites.chunksystem.mixin; +- +-import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +-import net.minecraft.block.entity.BlockEntity; +-import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; +-import net.minecraft.registry.Registry; +-import net.minecraft.server.network.ServerPlayerEntity; +-import net.minecraft.server.world.ServerWorld; +-import net.minecraft.util.math.BlockPos; +-import net.minecraft.util.math.ChunkPos; +-import net.minecraft.world.HeightLimitView; +-import net.minecraft.world.World; +-import net.minecraft.world.biome.Biome; +-import net.minecraft.world.chunk.Chunk; +-import net.minecraft.world.chunk.ChunkSection; +-import net.minecraft.world.chunk.UpgradeData; +-import net.minecraft.world.chunk.WorldChunk; +-import net.minecraft.world.gen.chunk.BlendingData; +-import org.jetbrains.annotations.Nullable; +-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 java.util.List; +- +-@Mixin(WorldChunk.class) +-public abstract class MixinWorldChunk extends Chunk { +- +- @Shadow @Final private World world; +- +- public MixinWorldChunk(ChunkPos pos, UpgradeData upgradeData, HeightLimitView heightLimitView, Registry biomeRegistry, long inhabitedTime, @Nullable ChunkSection[] sectionArray, @Nullable BlendingData blendingData) { +- super(pos, upgradeData, heightLimitView, biomeRegistry, inhabitedTime, sectionArray, blendingData); +- } +- +- @WrapOperation(method = "runPostProcessing", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/WorldChunk;getBlockEntity(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/entity/BlockEntity;")) +- private BlockEntity syncBlockEntities(WorldChunk instance, BlockPos pos, Operation original) { +- if (this.world instanceof ServerWorld serverWorld) { +- BlockEntity blockEntity = original.call(instance, pos); +- if (blockEntity == null) return null; +- +- List playersWatchingChunk = serverWorld.getChunkManager().chunkLoadingManager.getPlayersWatchingChunk(this.getPos(), false); +- BlockEntityUpdateS2CPacket packet = BlockEntityUpdateS2CPacket.create(blockEntity); +- for (ServerPlayerEntity player : playersWatchingChunk) { +- player.networkHandler.send(packet); +- } +- +- return blockEntity; +- } else { +- return original.call(instance, pos); +- } +- } +- +-} +diff --git a/c2me-rewrites-chunk-system/src/main/resources/c2me-rewrites-chunk-system.mixins.json b/c2me-rewrites-chunk-system/src/main/resources/c2me-rewrites-chunk-system.mixins.json +index d54edac4..47e86bf4 100644 +--- a/c2me-rewrites-chunk-system/src/main/resources/c2me-rewrites-chunk-system.mixins.json ++++ b/c2me-rewrites-chunk-system/src/main/resources/c2me-rewrites-chunk-system.mixins.json +@@ -14,7 +14,6 @@ + "MixinSerializingRegionBasedStorage", + "MixinServerChunkManager", + "MixinThreadedAnvilChunkStorage", +- "MixinWorldChunk", + "async_serialization.MixinBlender", + "async_serialization.MixinChunkRegion", + "async_serialization.MixinChunkSerializer", diff --git a/patches/main/0017-fix-load-1-extra-radius-to-notickvd-to-avoid-issues.patch b/patches/main/0017-fix-load-1-extra-radius-to-notickvd-to-avoid-issues.patch new file mode 100644 index 0000000..e1301c0 --- /dev/null +++ b/patches/main/0017-fix-load-1-extra-radius-to-notickvd-to-avoid-issues.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Tue, 21 Jan 2025 12:13:43 +0800 +Subject: [PATCH] fix: load 1 extra radius to notickvd to avoid issues + +Backported from a8a51fcbc9b02844e0bec7a8386e0f4128b3d8f4 + +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +index b2a9c4ce..30e68469 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/PlayerNoTickLoader.java +@@ -64,7 +64,6 @@ public class PlayerNoTickLoader { + + public void setViewDistance(int viewDistance) { + this.viewDistance = viewDistance; +- this.dirtyManagedChunks = true; + this.recreateIterators = true; + } + +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java +index 1d683e6d..37732ee8 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/mixin/MixinChunkTicketManager.java +@@ -79,7 +79,7 @@ public class MixinChunkTicketManager implements ChunkTicketManagerExtension { + */ + @Overwrite + public void setWatchDistance(int viewDistance) { +- this.noTickSystem.setNoTickViewDistance(viewDistance); ++ this.noTickSystem.setNoTickViewDistance(viewDistance + 1); + } + + @Override diff --git a/patches/main/0018-new-add-internal-item-count-to-debug-hud.patch b/patches/main/0018-new-add-internal-item-count-to-debug-hud.patch new file mode 100644 index 0000000..9264b5e --- /dev/null +++ b/patches/main/0018-new-add-internal-item-count-to-debug-hud.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Tue, 21 Jan 2025 18:53:01 +0800 +Subject: [PATCH] new: add internal item count to debug hud + +Backported from a9e23405314683a2e74540d9ae75f5291b2522da + +diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinServerChunkManager.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinServerChunkManager.java +index 1eee5bb0..ac4386ff 100644 +--- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinServerChunkManager.java ++++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinServerChunkManager.java +@@ -15,6 +15,7 @@ import net.minecraft.world.chunk.WrapperProtoChunk; + import org.jetbrains.annotations.Nullable; + 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 org.spongepowered.asm.mixin.Unique; + import org.spongepowered.asm.mixin.injection.At; +@@ -38,6 +39,8 @@ public abstract class MixinServerChunkManager { + @Shadow + protected abstract @Nullable ChunkHolder getChunkHolder(long pos); + ++ @Shadow public abstract int getLoadedChunkCount(); ++ + @Inject(method = "getChunk(IILnet/minecraft/world/chunk/ChunkStatus;Z)Lnet/minecraft/world/chunk/Chunk;", at = @At("HEAD"), cancellable = true) + private void shortcutGetChunk(int x, int z, ChunkStatus leastStatus, boolean create, CallbackInfoReturnable cir) { + if (Thread.currentThread() != this.serverThread) { +@@ -73,4 +76,13 @@ public abstract class MixinServerChunkManager { + return ((IChunkSystemAccess) this.chunkLoadingManager).c2me$getTheChunkSystem().vanillaIf$getManagedLevel(instance.getPos().toLong()); + } + ++ /** ++ * @author ishland ++ * @reason add debug string ++ */ ++ @Overwrite ++ public String getDebugString() { ++ return Integer.toString(((IChunkSystemAccess) this.chunkLoadingManager).c2me$getTheChunkSystem().itemCount()) + ", " + Integer.toString(this.getLoadedChunkCount()); ++ } ++ + } diff --git a/patches/main/0019-change-lower-maxConcurrentChunkLoads-for-1.21.1.patch b/patches/main/0019-change-lower-maxConcurrentChunkLoads-for-1.21.1.patch new file mode 100644 index 0000000..0d5ac38 --- /dev/null +++ b/patches/main/0019-change-lower-maxConcurrentChunkLoads-for-1.21.1.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Fri, 24 Jan 2025 22:56:37 +0800 +Subject: [PATCH] change: lower maxConcurrentChunkLoads for 1.21.1 + + +diff --git a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/Config.java b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/Config.java +index 64796be8..9f9f2df5 100644 +--- a/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/Config.java ++++ b/c2me-notickvd/src/main/java/com/ishland/c2me/notickvd/common/Config.java +@@ -10,7 +10,7 @@ public class Config { + .key("noTickViewDistance.maxConcurrentChunkLoads") + .comment("No-tick view distance max concurrent chunk loads \n" + + " Lower this for a better latency and higher this for a faster loading") +- .getLong(GlobalExecutors.GLOBAL_EXECUTOR_PARALLELISM * 2L, GlobalExecutors.GLOBAL_EXECUTOR_PARALLELISM * 2L, ConfigSystem.LongChecks.POSITIVE_VALUES_ONLY); ++ .getLong(GlobalExecutors.GLOBAL_EXECUTOR_PARALLELISM + 1, GlobalExecutors.GLOBAL_EXECUTOR_PARALLELISM + 1, ConfigSystem.LongChecks.POSITIVE_VALUES_ONLY); + + public static final boolean compatibilityMode = new ConfigSystem.ConfigAccessor() + .key("noTickViewDistance.compatibilityMode")