Skip to content

Commit

Permalink
Copy entity tracker optimisations from Folia
Browse files Browse the repository at this point in the history
  • Loading branch information
Spottedleaf committed Jul 11, 2024
1 parent 6ff9aca commit 6d8e12e
Show file tree
Hide file tree
Showing 15 changed files with 715 additions and 1 deletion.
209 changes: 209 additions & 0 deletions src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package ca.spottedleaf.moonrise.common.misc;

import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystem;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.ChunkPos;

public final class NearbyPlayers {

public static enum NearbyMapType {
GENERAL,
GENERAL_SMALL,
GENERAL_REALLY_SMALL,
TICK_VIEW_DISTANCE,
VIEW_DISTANCE,
SPAWN_RANGE,
}

private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values();
public static final int TOTAL_MAP_TYPES = MAP_TYPES.length;

private static final int GENERAL_AREA_VIEW_DISTANCE = MoonriseConstants.MAX_VIEW_DISTANCE + 1;
private static final int GENERAL_SMALL_VIEW_DISTANCE = 10;
private static final int GENERAL_REALLY_SMALL_VIEW_DISTANCE = 3;

public static final int GENERAL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_AREA_VIEW_DISTANCE << 4);
public static final int GENERAL_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_SMALL_VIEW_DISTANCE << 4);
public static final int GENERAL_REALLY_SMALL_AREA_VIEW_DISTANCE_BLOCKS = (GENERAL_REALLY_SMALL_VIEW_DISTANCE << 4);

private final ServerLevel world;
private final Reference2ReferenceOpenHashMap<ServerPlayer, TrackedPlayer[]> players = new Reference2ReferenceOpenHashMap<>();
private final Long2ReferenceOpenHashMap<TrackedChunk> byChunk = new Long2ReferenceOpenHashMap<>();

public NearbyPlayers(final ServerLevel world) {
this.world = world;
}

public void addPlayer(final ServerPlayer player) {
final TrackedPlayer[] newTrackers = new TrackedPlayer[TOTAL_MAP_TYPES];
if (this.players.putIfAbsent(player, newTrackers) != null) {
throw new IllegalStateException("Already have player " + player);
}

final ChunkPos chunk = player.chunkPosition();

for (int i = 0; i < TOTAL_MAP_TYPES; ++i) {
// use 0 for default, will be updated by tickPlayer
(newTrackers[i] = new TrackedPlayer(player, MAP_TYPES[i])).add(chunk.x, chunk.z, 0);
}

// update view distances
this.tickPlayer(player);
}

public void removePlayer(final ServerPlayer player) {
final TrackedPlayer[] players = this.players.remove(player);
if (players == null) {
return; // May be called during teleportation before the player is actually placed
}

for (final TrackedPlayer tracker : players) {
tracker.remove();
}
}

public void tickPlayer(final ServerPlayer player) {
final TrackedPlayer[] players = this.players.get(player);
if (players == null) {
throw new IllegalStateException("Don't have player " + player);
}

final ChunkPos chunk = player.chunkPosition();

players[NearbyMapType.GENERAL.ordinal()].update(chunk.x, chunk.z, GENERAL_AREA_VIEW_DISTANCE);
players[NearbyMapType.GENERAL_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_SMALL_VIEW_DISTANCE);
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));
}

public TrackedChunk getChunk(final ChunkPos pos) {
return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
}

public TrackedChunk getChunk(final BlockPos pos) {
return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
}

public ReferenceList<ServerPlayer> getPlayers(final BlockPos pos, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));

return chunk == null ? null : chunk.players[type.ordinal()];
}

public ReferenceList<ServerPlayer> getPlayers(final ChunkPos pos, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));

return chunk == null ? null : chunk.players[type.ordinal()];
}

public ReferenceList<ServerPlayer> getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));

return chunk == null ? null : chunk.players[type.ordinal()];
}

public ReferenceList<ServerPlayer> getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));

return chunk == null ? null : chunk.players[type.ordinal()];
}

public static final class TrackedChunk {

private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0];

private final ReferenceList<ServerPlayer>[] players = new ReferenceList[TOTAL_MAP_TYPES];
private int nonEmptyLists;
private long updateCount;

public boolean isEmpty() {
return this.nonEmptyLists == 0;
}

public long getUpdateCount() {
return this.updateCount;
}

public ReferenceList<ServerPlayer> getPlayers(final NearbyMapType type) {
return this.players[type.ordinal()];
}

public void addPlayer(final ServerPlayer player, final NearbyMapType type) {
++this.updateCount;

final int idx = type.ordinal();
final ReferenceList<ServerPlayer> list = this.players[idx];
if (list == null) {
++this.nonEmptyLists;
(this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY, 0)).add(player);
return;
}

if (!list.add(player)) {
throw new IllegalStateException("Already contains player " + player);
}
}

public void removePlayer(final ServerPlayer player, final NearbyMapType type) {
++this.updateCount;

final int idx = type.ordinal();
final ReferenceList<ServerPlayer> list = this.players[idx];
if (list == null) {
throw new IllegalStateException("Does not contain player " + player);
}

if (!list.remove(player)) {
throw new IllegalStateException("Does not contain player " + player);
}

if (list.size() == 0) {
this.players[idx] = null;
--this.nonEmptyLists;
}
}
}

private final class TrackedPlayer extends SingleUserAreaMap<ServerPlayer> {

private final NearbyMapType type;

public TrackedPlayer(final ServerPlayer player, final NearbyMapType type) {
super(player);
this.type = type;
}

@Override
protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);

NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> {
return new TrackedChunk();
}).addPlayer(parameter, this.type);
}

@Override
protected void removeCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);

final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey);
if (chunk == null) {
throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey));
}

chunk.removePlayer(parameter, this.type);

if (chunk.isEmpty()) {
NearbyPlayers.this.byChunk.remove(chunkKey);
}
}
}
}
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.misc.NearbyPlayers;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.MoonriseCommon;
import ca.spottedleaf.moonrise.patches.chunk_system.io.RegionFileIOThread;
Expand Down Expand Up @@ -104,6 +105,9 @@ protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Leve
@Unique
private long tickedBlocksOrFluids;

@Unique
private final NearbyPlayers nearbyPlayers = new NearbyPlayers((ServerLevel)(Object)this);

/**
* @reason Initialise fields / destroy entity manager state
* @author Spottedleaf
Expand Down Expand Up @@ -306,6 +310,11 @@ private void init(MinecraftServer minecraftServer, Executor executor,
this.lastMidTickFailure = time;
}

@Override
public final NearbyPlayers moonrise$getNearbyPlayers() {
return this.nearbyPlayers;
}

/**
* @reason Entities are guaranteed to be ticking in the new chunk system
* @author Spottedleaf
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package ca.spottedleaf.moonrise.mixin.entity_tracker;

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_system.level.entity.server.ServerEntityLookup;
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity;
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.datafixers.DataFixer;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.GeneratingChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.chunk.storage.ChunkStorage;
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Iterator;

@Mixin(ChunkMap.class)
public abstract class ChunkMapMixin extends ChunkStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap {
@Shadow
@Final
public ServerLevel level;

public ChunkMapMixin(RegionStorageInfo regionStorageInfo, Path path, DataFixer dataFixer, boolean bl) {
super(regionStorageInfo, path, dataFixer, bl);
}

/**
* @reason The new tracker tick method will perform the necessary tracker updates.
* @author Spottedleaf
*/
@Redirect(
method = "move",
at = @At(
value = "INVOKE",
target = "Ljava/util/Iterator;hasNext()Z",
ordinal = 0
)
)
private boolean skipMoveTrackerUpdate(final Iterator<?> iterator) {
return false;
}

/**
* @reason New entity tracker tick method which scales better.
* @author Spottedleaf
*/
@Redirect(
method = "tick()V",
at = @At(
value = "INVOKE",
target = "Ljava/util/Iterator;hasNext()Z",
ordinal = 1
)
)
private boolean newTrackerTick(final Iterator<?> iterator) {
final NearbyPlayers nearbyPlayers = ((ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers();
final ServerEntityLookup entityLookup = (ServerEntityLookup)((ChunkSystemServerLevel)this.level).moonrise$getEntityLookup();;

final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
for (int i = 0, len = trackerEntities.size(); i < len; ++i) {
final Entity entity = trackerEntitiesRaw[i];
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity)entity).moonrise$getTrackedEntity();
if (tracker == null) {
continue;
}
((EntityTrackerTrackedEntity)tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
tracker.serverEntity.sendChanges();
}

// process unloads
final ReferenceList<Entity> unloadedEntities = entityLookup.trackerUnloadedEntities;
final Entity[] unloadedEntitiesRaw = Arrays.copyOf(unloadedEntities.getRawDataUnchecked(), unloadedEntities.size());
unloadedEntities.clear();

for (final Entity entity : unloadedEntitiesRaw) {
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity)entity).moonrise$getTrackedEntity();
if (tracker == null) {
continue;
}
((EntityTrackerTrackedEntity)tracker).moonrise$clearPlayers();
}

return false;
}

/**
* @reason Update tracker field
* @author Spottedleaf
*/
@Inject(
method = "addEntity",
at = @At(
value = "INVOKE",
target = "Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;put(ILjava/lang/Object;)Ljava/lang/Object;",
shift = At.Shift.AFTER
)
)
private void addEntityTrackerField(final Entity entity, final CallbackInfo ci,
@Local(ordinal = 0) final ChunkMap.TrackedEntity trackedEntity) {
if (((EntityTrackerEntity)entity).moonrise$getTrackedEntity() != null) {
throw new IllegalStateException("Entity is already tracked");
}
((EntityTrackerEntity)entity).moonrise$setTrackedEntity(trackedEntity);
}

/**
* @reason Update tracker field
* @author Spottedleaf
*/
@Inject(
method = "removeEntity",
at = @At(
value = "RETURN"
)
)
private void removeEntityTrackerField(final Entity entity, final CallbackInfo ci) {
((EntityTrackerEntity)entity).moonrise$setTrackedEntity(null);
}
}
Loading

0 comments on commit 6d8e12e

Please sign in to comment.