From bbf85f323fff7516064c06f565c6348cf284dba0 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Wed, 17 Jul 2024 07:25:48 -0700 Subject: [PATCH] Optimise chunk tick iteration The basic problem with the chunk tick iteration is that Vanilla will iterate over all chunk holders to find ticking chunks. However, there are usually many more chunk holders than ticking chunks. We can eliminate the cost of finding the ticking chunks by maintaining our own list of ticking chunks. --- .../moonrise/common/list/ReferenceList.java | 23 ++- .../moonrise/common/misc/NearbyPlayers.java | 6 +- .../common/misc/PositionCountingAreaMap.java | 94 ++++++++++++ .../common/misc/SingleUserAreaMap.java | 18 ++- .../mixin/chunk_system/ChunkHolderMixin.java | 2 +- .../mixin/chunk_system/LevelChunkMixin.java | 14 ++ .../mixin/chunk_system/ServerLevelMixin.java | 28 ++++ .../chunk_tick_iteration/ChunkMapMixin.java | 141 ++++++++++++++++++ .../DistanceManagerMixin.java | 113 ++++++++++++++ .../ServerChunkCacheMixin.java | 122 +++++++++++++++ .../patches/chunk_system/ChunkSystem.java | 23 ++- .../level/ChunkSystemServerLevel.java | 8 + .../level/chunk/ChunkSystemLevelChunk.java | 6 + .../entity/server/ServerEntityLookup.java | 4 +- .../scheduling/task/ChunkFullTask.java | 5 +- .../ChunkTickConstants.java | 7 + .../ChunkTickDistanceManager.java | 16 ++ src/main/resources/moonrise.accesswidener | 8 +- src/main/resources/moonrise.mixins.json | 3 + 19 files changed, 620 insertions(+), 21 deletions(-) create mode 100644 src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java create mode 100644 src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/ChunkMapMixin.java create mode 100644 src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/DistanceManagerMixin.java create mode 100644 src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/ServerChunkCacheMixin.java create mode 100644 src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java create mode 100644 src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java index 93e8c813..2e876b91 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java @@ -7,23 +7,30 @@ public final class ReferenceList implements Iterable { - private final Reference2IntOpenHashMap referenceToIndex = new Reference2IntOpenHashMap<>(2, 0.8f); - { - this.referenceToIndex.defaultReturnValue(Integer.MIN_VALUE); - } - private static final Object[] EMPTY_LIST = new Object[0]; + private final Reference2IntOpenHashMap referenceToIndex; private E[] references; private int count; public ReferenceList() { - this((E[])EMPTY_LIST, 0); + this((E[])EMPTY_LIST); + } + + public ReferenceList(final E[] referenceArray) { + this.references = referenceArray; + this.referenceToIndex = new Reference2IntOpenHashMap<>(2, 0.8f); + this.referenceToIndex.defaultReturnValue(Integer.MIN_VALUE); } - public ReferenceList(final E[] array, final int count) { - this.references = array; + private ReferenceList(final E[] references, final int count, final Reference2IntOpenHashMap referenceToIndex) { + this.references = references; this.count = count; + this.referenceToIndex = referenceToIndex; + } + + public ReferenceList copy() { + return new ReferenceList<>(this.references.clone(), this.count, this.referenceToIndex.clone()); } public int size() { diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java index fdde1f5f..3d5be35b 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java @@ -4,6 +4,7 @@ import ca.spottedleaf.moonrise.common.util.CoordinateUtils; import ca.spottedleaf.moonrise.common.util.MoonriseConstants; import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystem; +import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants; import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; import net.minecraft.core.BlockPos; @@ -19,7 +20,7 @@ public static enum NearbyMapType { GENERAL_REALLY_SMALL, TICK_VIEW_DISTANCE, VIEW_DISTANCE, - SPAWN_RANGE, + SPAWN_RANGE, // Moonrise - chunk tick iteration } private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values(); @@ -82,6 +83,7 @@ public void tickPlayer(final ServerPlayer player) { players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE); players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player)); players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getLoadViewDistance(player)); + players[NearbyMapType.SPAWN_RANGE.ordinal()].update(chunk.x, chunk.z, ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE); // Moonrise - chunk tick iteration } public TrackedChunk getChunk(final ChunkPos pos) { @@ -143,7 +145,7 @@ public void addPlayer(final ServerPlayer player, final NearbyMapType type) { final ReferenceList list = this.players[idx]; if (list == null) { ++this.nonEmptyLists; - (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY, 0)).add(player); + (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player); return; } diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java new file mode 100644 index 00000000..efefd94b --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java @@ -0,0 +1,94 @@ +package ca.spottedleaf.moonrise.common.misc; + +import ca.spottedleaf.concurrentutil.util.IntPairUtil; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceSet; + +public final class PositionCountingAreaMap { + + private final Reference2ReferenceOpenHashMap counters = new Reference2ReferenceOpenHashMap<>(); + private final Long2IntOpenHashMap positions = new Long2IntOpenHashMap(); + + public ReferenceSet getObjects() { + return this.counters.keySet(); + } + + public int getTotalPositions() { + return this.positions.size(); + } + + public boolean hasObjectsNear(final int toX, final int toZ) { + return this.positions.containsKey(IntPairUtil.key(toX, toZ)); + } + + public int getObjectsNear(final int toX, final int toZ) { + return this.positions.get(IntPairUtil.key(toX, toZ)); + } + + public boolean add(final T parameter, final int toX, final int toZ, final int distance) { + final PositionCounter existing = this.counters.get(parameter); + if (existing != null) { + return false; + } + + final PositionCounter counter = new PositionCounter(parameter); + + this.counters.put(parameter, counter); + + return counter.add(toX, toZ, distance); + } + + public boolean addOrUpdate(final T parameter, final int toX, final int toZ, final int distance) { + final PositionCounter existing = this.counters.get(parameter); + if (existing != null) { + return existing.update(toX, toZ, distance); + } + + final PositionCounter counter = new PositionCounter(parameter); + + this.counters.put(parameter, counter); + + return counter.add(toX, toZ, distance); + } + + public boolean remove(final T parameter) { + final PositionCounter counter = this.counters.remove(parameter); + if (counter == null) { + return false; + } + + counter.remove(); + + return true; + } + + public boolean update(final T parameter, final int toX, final int toZ, final int distance) { + final PositionCounter counter = this.counters.get(parameter); + if (counter == null) { + return false; + } + + return counter.update(toX, toZ, distance); + } + + private final class PositionCounter extends SingleUserAreaMap { + + public PositionCounter(final T parameter) { + super(parameter); + } + + @Override + protected void addCallback(final T parameter, final int toX, final int toZ) { + PositionCountingAreaMap.this.positions.addTo(IntPairUtil.key(toX, toZ), 1); + } + + @Override + protected void removeCallback(final T parameter, final int toX, final int toZ) { + final long key = IntPairUtil.key(toX, toZ); + if (PositionCountingAreaMap.this.positions.addTo(key, -1) == 1) { + PositionCountingAreaMap.this.positions.remove(key); + } + } + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java index 61f70247..94689e03 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java @@ -4,7 +4,7 @@ public abstract class SingleUserAreaMap { - private static final int NOT_SET = Integer.MIN_VALUE; + public static final int NOT_SET = Integer.MIN_VALUE; private final T parameter; private int lastChunkX = NOT_SET; @@ -15,6 +15,22 @@ public SingleUserAreaMap(final T parameter) { this.parameter = parameter; } + public final T getParameter() { + return this.parameter; + } + + public final int getLastChunkX() { + return this.lastChunkX; + } + + public final int getLastChunkZ() { + return this.lastChunkZ; + } + + public final int getLastDistance() { + return this.distance; + } + /* math sign function except 0 returns 1 */ protected static int sign(int val) { return 1 | (val >> (Integer.SIZE - 1)); diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ChunkHolderMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ChunkHolderMixin.java index 36455dd4..0c65c8cd 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ChunkHolderMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ChunkHolderMixin.java @@ -64,7 +64,7 @@ public ChunkHolderMixin(ChunkPos chunkPos) { private NewChunkHolder newChunkHolder; @Unique - private final ReferenceList playersSentChunkTo = new ReferenceList<>(EMPTY_PLAYER_ARRAY, 0); + private final ReferenceList playersSentChunkTo = new ReferenceList<>(EMPTY_PLAYER_ARRAY); @Unique private ChunkMap getChunkMap() { diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelChunkMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelChunkMixin.java index 136103d3..eb0f560f 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelChunkMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelChunkMixin.java @@ -3,6 +3,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk; import ca.spottedleaf.moonrise.patches.chunk_system.ticks.ChunkSystemLevelChunkTicks; import net.minecraft.core.Registry; +import net.minecraft.server.level.ServerChunkCache; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelHeightAccessor; @@ -46,11 +47,24 @@ public LevelChunkMixin(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAc @Unique private boolean postProcessingDone; + @Unique + private ServerChunkCache.ChunkAndHolder chunkAndHolder; + @Override public final boolean moonrise$isPostProcessingDone() { return this.postProcessingDone; } + @Override + public final ServerChunkCache.ChunkAndHolder moonrise$getChunkAndHolder() { + return this.chunkAndHolder; + } + + @Override + public final void moonrise$setChunkAndHolder(final ServerChunkCache.ChunkAndHolder holder) { + this.chunkAndHolder = holder; + } + /** * @reason Hook to set {@link #postProcessingDone} to {@code true} when post-processing completes to avoid invoking * this function many times by the player chunk loader. diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java index 53024318..4de7bfad 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java @@ -1,6 +1,7 @@ package ca.spottedleaf.moonrise.mixin.chunk_system; import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; +import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; import ca.spottedleaf.moonrise.common.util.CoordinateUtils; import ca.spottedleaf.moonrise.common.util.MoonriseCommon; @@ -114,6 +115,18 @@ protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey loadedChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS); + + @Unique + private final ReferenceList tickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS); + + @Unique + private final ReferenceList entityTickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS); + /** * @reason Initialise fields / destroy entity manager state * @author Spottedleaf @@ -312,6 +325,21 @@ private void init(MinecraftServer minecraftServer, Executor executor, return this.nearbyPlayers; } + @Override + public final ReferenceList moonrise$getLoadedChunks() { + return this.loadedChunks; + } + + @Override + public final ReferenceList moonrise$getTickingChunks() { + return this.tickingChunks; + } + + @Override + public final ReferenceList moonrise$getEntityTickingChunks() { + return this.entityTickingChunks; + } + /** * @reason Declare method in this class so that any invocations are virtual, and not interface. * @author Spottedleaf diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/ChunkMapMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/ChunkMapMixin.java new file mode 100644 index 00000000..e5967206 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/ChunkMapMixin.java @@ -0,0 +1,141 @@ +package ca.spottedleaf.moonrise.mixin.chunk_tick_iteration; + +import ca.spottedleaf.moonrise.common.list.ReferenceList; +import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +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.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Mixin(ChunkMap.class) +public abstract class ChunkMapMixin { + + @Shadow + @Final + private ChunkMap.DistanceManager distanceManager; + + @Shadow + @Final + public ServerLevel level; + + @Shadow + protected abstract boolean playerIsCloseEnoughForSpawning(ServerPlayer serverPlayer, ChunkPos chunkPos); + + /** + * @reason Hook for updating the spawn tracker in distance manager. We add our own hook instead of using the + * addPlayer/removePlayer calls as it is more efficient to update the spawn tracker than to add and remove, + * as the update method will only update chunks that are different. + * @author Spottedleaf + */ + @Inject( + method = "move", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/ChunkMap;updatePlayerPos(Lnet/minecraft/server/level/ServerPlayer;)V" + ) + ) + private void updateSpawnTracker(final ServerPlayer player, final CallbackInfo ci, + @Local(ordinal = 0) final SectionPos oldPos, @Local(ordinal = 1) final SectionPos newPos, + @Local(ordinal = 0) final boolean oldIgnore, @Local(ordinal = 1) final boolean newIgnore) { + ((ChunkTickDistanceManager)this.distanceManager).moonrise$updatePlayer(player, oldPos, newPos, oldIgnore, newIgnore); + } + + /** + * @reason Add hook for spawn tracker + * @author Spottedleaf + */ + @Inject( + method = "updatePlayerStatus", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/ChunkMap$DistanceManager;addPlayer(Lnet/minecraft/core/SectionPos;Lnet/minecraft/server/level/ServerPlayer;)V" + ) + ) + private void addPlayerToSpawnTracker(final ServerPlayer player, final boolean add, final CallbackInfo ci) { + ((ChunkTickDistanceManager)this.distanceManager).moonrise$addPlayer(player, SectionPos.of(player)); + } + + /** + * @reason Remove hook for spawn tracker + * @author Spottedleaf + */ + @Inject( + method = "updatePlayerStatus", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/ChunkMap$DistanceManager;removePlayer(Lnet/minecraft/core/SectionPos;Lnet/minecraft/server/level/ServerPlayer;)V" + ) + ) + private void removePlayerFromSpawnTracker(final ServerPlayer player, final boolean add, final CallbackInfo ci) { + ((ChunkTickDistanceManager)this.distanceManager).moonrise$removePlayer(player, SectionPos.of(player)); + } + + /** + * @reason Use nearby players to avoid iterating over all online players + * @author Spottedleaf + */ + @Overwrite + public boolean anyPlayerCloseEnoughForSpawning(final ChunkPos pos) { + final ReferenceList players = ((ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers( + pos, NearbyPlayers.NearbyMapType.SPAWN_RANGE + ); + if (players == null) { + return false; + } + + final ServerPlayer[] raw = players.getRawDataUnchecked(); + final int len = players.size(); + + Objects.checkFromIndexSize(0, len, raw.length); + for (int i = 0; i < len; ++i) { + if (this.playerIsCloseEnoughForSpawning(raw[i], pos)) { + return true; + } + } + + return false; + } + + /** + * @reason Use nearby players to avoid iterating over all online players + * @author Spottedleaf + */ + @Overwrite + public List getPlayersCloseForSpawning(final ChunkPos pos) { + final List ret = new ArrayList<>(); + + final ReferenceList players = ((ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers( + pos, NearbyPlayers.NearbyMapType.SPAWN_RANGE + ); + if (players == null) { + return ret; + } + + final ServerPlayer[] raw = players.getRawDataUnchecked(); + final int len = players.size(); + + Objects.checkFromIndexSize(0, len, raw.length); + for (int i = 0; i < len; ++i) { + final ServerPlayer player = raw[i]; + if (this.playerIsCloseEnoughForSpawning(player, pos)) { + ret.add(player); + } + } + + return ret; + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/DistanceManagerMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/DistanceManagerMixin.java new file mode 100644 index 00000000..26a96fd7 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/DistanceManagerMixin.java @@ -0,0 +1,113 @@ +package ca.spottedleaf.moonrise.mixin.chunk_tick_iteration; + +import ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; +import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants; +import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.DistanceManager; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.TicketType; +import net.minecraft.server.level.TickingTracker; +import net.minecraft.world.level.ChunkPos; +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; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(DistanceManager.class) +public abstract class DistanceManagerMixin implements ChunkTickDistanceManager { + + @Shadow + private DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter; + + + @Unique + private final PositionCountingAreaMap spawnChunkTracker = new PositionCountingAreaMap<>(); + + @Override + public final void moonrise$addPlayer(final ServerPlayer player, final SectionPos pos) { + this.spawnChunkTracker.add(player, pos.x(), pos.z(), ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE); + } + + @Override + public final void moonrise$removePlayer(final ServerPlayer player, final SectionPos pos) { + this.spawnChunkTracker.remove(player); + } + + @Override + public final void moonrise$updatePlayer(final ServerPlayer player, + final SectionPos oldPos, final SectionPos newPos, + final boolean oldIgnore, final boolean newIgnore) { + if (newIgnore) { + this.spawnChunkTracker.remove(player); + } else { + this.spawnChunkTracker.addOrUpdate(player, newPos.x(), newPos.z(), ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE); + } + } + + /** + * @reason Destroy natural spawning tracker field to prevent it from being used + * @author Spottedleaf + */ + @Inject( + method = "", + at = @At( + value = "RETURN" + ) + ) + private void destroyFields(final CallbackInfo ci) { + this.naturalSpawnChunkCounter = null; + } + + /** + * @reason Destroy hook to old spawn tracker + * @author Spottedleaf + */ + @Redirect( + method = "addPlayer", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/DistanceManager$FixedPlayerDistanceChunkTracker;update(JIZ)V" + ) + ) + private void skipSpawnTrackerAdd(final DistanceManager.FixedPlayerDistanceChunkTracker instance, + final long pos, final int i0, final boolean b0) {} + + /** + * @reason Destroy hook to old spawn tracker + * @author Spottedleaf + */ + @Redirect( + method = "removePlayer", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/level/DistanceManager$FixedPlayerDistanceChunkTracker;update(JIZ)V" + ) + ) + private void skipSpawnTrackerRemove(final DistanceManager.FixedPlayerDistanceChunkTracker instance, + final long pos, final int i0, final boolean b0) {} + + /** + * @reason Use spawnChunkTracker instead + * @author Spottedleaf + */ + @Overwrite + public int getNaturalSpawnChunkCount() { + return this.spawnChunkTracker.getTotalPositions(); + } + + /** + * @reason Use spawnChunkTracker instead + * @author Spottedleaf + */ + @Overwrite + public boolean hasPlayersNearby(final long pos) { + return this.spawnChunkTracker.hasObjectsNear(CoordinateUtils.getChunkX(pos), CoordinateUtils.getChunkZ(pos)); + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/ServerChunkCacheMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/ServerChunkCacheMixin.java new file mode 100644 index 00000000..2123493f --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_tick_iteration/ServerChunkCacheMixin.java @@ -0,0 +1,122 @@ +package ca.spottedleaf.moonrise.mixin.chunk_tick_iteration; + +import ca.spottedleaf.moonrise.common.list.ReferenceList; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkSource; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +@Mixin(ServerChunkCache.class) +public abstract class ServerChunkCacheMixin extends ChunkSource { + + @Shadow + @Final + public ServerLevel level; + + @Unique + private ServerChunkCache.ChunkAndHolder[] iterationCopy; + + /** + * @reason Avoid creating the list, which is sized at the chunkholder count. The actual number of ticking + * chunks is always lower. The mixin below will initialise the list to non-null. + * @author Spottedleaf + */ + @Redirect( + method = "tickChunks", + at = @At( + value = "INVOKE", + target = "Lcom/google/common/collect/Lists;newArrayListWithCapacity(I)Ljava/util/ArrayList;" + ) + ) + private ArrayList avoidListCreation(final int initialArraySize) { + return null; + } + + /** + * @reason Initialise the list to contain only the ticking chunks. + * @author Spottedleaf + */ + @ModifyVariable( + method = "tickChunks", + at = @At( + value = "STORE", + opcode = Opcodes.ASTORE, + ordinal = 0 + ) + ) + private List initTickChunks(final List shouldBeNull) { + final ReferenceList tickingChunks = + ((ChunkSystemServerLevel)this.level).moonrise$getTickingChunks(); + + final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked(); + final int size = tickingChunks.size(); + + if (this.iterationCopy == null || this.iterationCopy.length < size) { + this.iterationCopy = new ServerChunkCache.ChunkAndHolder[raw.length]; + } + System.arraycopy(raw, 0, this.iterationCopy, 0, size); + + return ObjectArrayList.wrap( + this.iterationCopy, size + ); + } + + /** + * @reason Do not initialise ticking chunk list, as we did that above. + * @author Spottedleaf + */ + @Redirect( + method = "tickChunks", + at = @At( + value = "INVOKE", + target = "Ljava/util/Iterator;hasNext()Z", + ordinal = 0 + ) + ) + private boolean skipTickAdd(final Iterator instance) { + return false; + } + + /** + * @reason Clear the iteration array, and at the same time broadcast chunk changes. + * @author Spottedleaf + */ + @Redirect( + method = "tickChunks", + at = @At( + value = "INVOKE", + target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V", + ordinal = 0 + ) + ) + private void broadcastChanges(final List instance, + final Consumer consumer) { + final ObjectArrayList chunks = (ObjectArrayList)instance; + final ServerChunkCache.ChunkAndHolder[] raw = chunks.elements(); + final int size = chunks.size(); + + Objects.checkFromToIndex(0, size, raw.length); + for (int i = 0; i < size; ++i) { + final ServerChunkCache.ChunkAndHolder holder = raw[i]; + raw[i] = null; + + holder.holder().broadcastChanges(holder.chunk()); + } + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java index 7d158f62..1ef7e0ce 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java @@ -86,11 +86,15 @@ public static void onChunkPreBorder(final LevelChunk chunk, final ChunkHolder ho } public static void onChunkBorder(final LevelChunk chunk, final ChunkHolder holder) { - + ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().add( + ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() + ); } public static void onChunkNotBorder(final LevelChunk chunk, final ChunkHolder holder) { - + ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getLoadedChunks().remove( + ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() + ); } public static void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolder holder) { @@ -99,6 +103,9 @@ public static void onChunkPostNotBorder(final LevelChunk chunk, final ChunkHolde } public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder holder) { + ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().add( + ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() + ); if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) { chunk.postProcessGeneration(); } @@ -107,15 +114,21 @@ public static void onChunkTicking(final LevelChunk chunk, final ChunkHolder hold } public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) { - + ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove( + ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() + ); } public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { - + ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().add( + ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() + ); } public static void onChunkNotEntityTicking(final LevelChunk chunk, final ChunkHolder holder) { - + ((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getEntityTickingChunks().remove( + ((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder() + ); } public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) { diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java index 93bd370d..93583ea9 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java @@ -1,11 +1,13 @@ package ca.spottedleaf.moonrise.patches.chunk_system.level; import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; +import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread; import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerChunkCache; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.status.ChunkStatus; import java.util.List; @@ -52,4 +54,10 @@ public interface ChunkSystemServerLevel extends ChunkSystemLevel { public void moonrise$setLastMidTickFailure(final long time); public NearbyPlayers moonrise$getNearbyPlayers(); + + public ReferenceList moonrise$getLoadedChunks(); + + public ReferenceList moonrise$getTickingChunks(); + + public ReferenceList moonrise$getEntityTickingChunks(); } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java index 755b08dd..5b092bca 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/chunk/ChunkSystemLevelChunk.java @@ -1,7 +1,13 @@ package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk; +import net.minecraft.server.level.ServerChunkCache; + public interface ChunkSystemLevelChunk { public boolean moonrise$isPostProcessingDone(); + public ServerChunkCache.ChunkAndHolder moonrise$getChunkAndHolder(); + + public void moonrise$setChunkAndHolder(final ServerChunkCache.ChunkAndHolder holder); + } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java index 524d93bc..0053ecb8 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java @@ -15,8 +15,8 @@ public final class ServerEntityLookup extends EntityLookup { private static final Entity[] EMPTY_ENTITY_ARRAY = new Entity[0]; private final ServerLevel serverWorld; - public final ReferenceList trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY, 0); // Moonrise - entity tracker - public final ReferenceList trackerUnloadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY, 0); // Moonrise - entity tracker + public final ReferenceList trackerEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker + public final ReferenceList trackerUnloadedEntities = new ReferenceList<>(EMPTY_ENTITY_ARRAY); // Moonrise - entity tracker public ServerEntityLookup(final ServerLevel world, final LevelCallback worldCallback) { super(world, worldCallback); diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java index 789537a1..00729ce5 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/task/ChunkFullTask.java @@ -3,11 +3,12 @@ import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk; import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiManager; import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; -import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ImposterProtoChunk; @@ -65,6 +66,8 @@ public void run() { this.chunkHolder.replaceProtoChunk(new ImposterProtoChunk(chunk, false)); } + ((ChunkSystemLevelChunk)chunk).moonrise$setChunkAndHolder(new ServerChunkCache.ChunkAndHolder(chunk, this.chunkHolder.vanillaChunkHolder)); + final NewChunkHolder chunkHolder = this.chunkHolder; chunk.setFullStatus(chunkHolder::getChunkStatus); diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java new file mode 100644 index 00000000..e97e7d27 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickConstants.java @@ -0,0 +1,7 @@ +package ca.spottedleaf.moonrise.patches.chunk_tick_iteration; + +public final class ChunkTickConstants { + + public static final int PLAYER_SPAWN_TRACK_RANGE = 8; + +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java new file mode 100644 index 00000000..f28fd0e0 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_tick_iteration/ChunkTickDistanceManager.java @@ -0,0 +1,16 @@ +package ca.spottedleaf.moonrise.patches.chunk_tick_iteration; + +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ServerPlayer; + +public interface ChunkTickDistanceManager { + + public void moonrise$addPlayer(final ServerPlayer player, final SectionPos pos); + + public void moonrise$removePlayer(final ServerPlayer player, final SectionPos pos); + + public void moonrise$updatePlayer(final ServerPlayer player, + final SectionPos oldPos, final SectionPos newPos, + final boolean oldIgnore, final boolean newIgnore); + +} diff --git a/src/main/resources/moonrise.accesswidener b/src/main/resources/moonrise.accesswidener index 3fe371ac..1e40fc95 100644 --- a/src/main/resources/moonrise.accesswidener +++ b/src/main/resources/moonrise.accesswidener @@ -190,6 +190,7 @@ mutable field net/minecraft/server/level/DistanceManager ticketThrottlerInput Ln mutable field net/minecraft/server/level/DistanceManager ticketThrottlerReleaser Lnet/minecraft/util/thread/ProcessorHandle; mutable field net/minecraft/server/level/DistanceManager ticketsToRelease Lit/unimi/dsi/fastutil/longs/LongSet; mutable field net/minecraft/server/level/DistanceManager mainThreadExecutor Ljava/util/concurrent/Executor; +mutable field net/minecraft/server/level/DistanceManager naturalSpawnChunkCounter Lnet/minecraft/server/level/DistanceManager$FixedPlayerDistanceChunkTracker; # DistanceManager$ChunkTicketTracker @@ -268,4 +269,9 @@ mutable field net/minecraft/server/level/GenerationChunkHolder generationRefCoun # ChunkMap.TrackedEntity accessible class net/minecraft/server/level/ChunkMap$TrackedEntity -accessible field net/minecraft/server/level/ChunkMap$TrackedEntity serverEntity Lnet/minecraft/server/level/ServerEntity; \ No newline at end of file +accessible field net/minecraft/server/level/ChunkMap$TrackedEntity serverEntity Lnet/minecraft/server/level/ServerEntity; + + +# ServerChunkCache$ChunkAndHolder +accessible class net/minecraft/server/level/ServerChunkCache$ChunkAndHolder +accessible method net/minecraft/server/level/ServerChunkCache$ChunkAndHolder (Lnet/minecraft/world/level/chunk/LevelChunk;Lnet/minecraft/server/level/ChunkHolder;)V \ No newline at end of file diff --git a/src/main/resources/moonrise.mixins.json b/src/main/resources/moonrise.mixins.json index a72a2621..c8b73d33 100644 --- a/src/main/resources/moonrise.mixins.json +++ b/src/main/resources/moonrise.mixins.json @@ -52,6 +52,9 @@ "chunk_system.StructureCheckMixin", "chunk_system.StructureTemplate$PaletteMixin", "chunk_system.TicketMixin", + "chunk_tick_iteration.ChunkMapMixin", + "chunk_tick_iteration.DistanceManagerMixin", + "chunk_tick_iteration.ServerChunkCacheMixin", "collisions.ArmorStandMixin", "collisions.ArrayVoxelShapeMixin", "collisions.BitSetDiscreteVoxelShapeMixin",