Skip to content

Commit

Permalink
Optimise chunk tick iteration
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Spottedleaf committed Jul 17, 2024
1 parent cec8e6f commit bbf85f3
Show file tree
Hide file tree
Showing 19 changed files with 620 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,30 @@

public final class ReferenceList<E> implements Iterable<E> {

private final Reference2IntOpenHashMap<E> referenceToIndex = new Reference2IntOpenHashMap<>(2, 0.8f);
{
this.referenceToIndex.defaultReturnValue(Integer.MIN_VALUE);
}

private static final Object[] EMPTY_LIST = new Object[0];

private final Reference2IntOpenHashMap<E> 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<E> referenceToIndex) {
this.references = references;
this.count = count;
this.referenceToIndex = referenceToIndex;
}

public ReferenceList<E> copy() {
return new ReferenceList<>(this.references.clone(), this.count, this.referenceToIndex.clone());
}

public int size() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -143,7 +145,7 @@ public void addPlayer(final ServerPlayer player, final NearbyMapType type) {
final ReferenceList<ServerPlayer> 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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<T> {

private final Reference2ReferenceOpenHashMap<T, PositionCounter> counters = new Reference2ReferenceOpenHashMap<>();
private final Long2IntOpenHashMap positions = new Long2IntOpenHashMap();

public ReferenceSet<T> 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<T> {

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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

public abstract class SingleUserAreaMap<T> {

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;
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public ChunkHolderMixin(ChunkPos chunkPos) {
private NewChunkHolder newChunkHolder;

@Unique
private final ReferenceList<ServerPlayer> playersSentChunkTo = new ReferenceList<>(EMPTY_PLAYER_ARRAY, 0);
private final ReferenceList<ServerPlayer> playersSentChunkTo = new ReferenceList<>(EMPTY_PLAYER_ARRAY);

@Unique
private ChunkMap getChunkMap() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -114,6 +115,18 @@ protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Leve
@Unique
private final NearbyPlayers nearbyPlayers = new NearbyPlayers((ServerLevel)(Object)this);

@Unique
private static final ServerChunkCache.ChunkAndHolder[] EMPTY_CHUNK_AND_HOLDERS = new ServerChunkCache.ChunkAndHolder[0];

@Unique
private final ReferenceList<ServerChunkCache.ChunkAndHolder> loadedChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS);

@Unique
private final ReferenceList<ServerChunkCache.ChunkAndHolder> tickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS);

@Unique
private final ReferenceList<ServerChunkCache.ChunkAndHolder> entityTickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS);

/**
* @reason Initialise fields / destroy entity manager state
* @author Spottedleaf
Expand Down Expand Up @@ -312,6 +325,21 @@ private void init(MinecraftServer minecraftServer, Executor executor,
return this.nearbyPlayers;
}

@Override
public final ReferenceList<ServerChunkCache.ChunkAndHolder> moonrise$getLoadedChunks() {
return this.loadedChunks;
}

@Override
public final ReferenceList<ServerChunkCache.ChunkAndHolder> moonrise$getTickingChunks() {
return this.tickingChunks;
}

@Override
public final ReferenceList<ServerChunkCache.ChunkAndHolder> moonrise$getEntityTickingChunks() {
return this.entityTickingChunks;
}

/**
* @reason Declare method in this class so that any invocations are virtual, and not interface.
* @author Spottedleaf
Expand Down
Loading

0 comments on commit bbf85f3

Please sign in to comment.