-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Copy entity tracker optimisations from Folia
- Loading branch information
1 parent
6ff9aca
commit 6d8e12e
Showing
15 changed files
with
715 additions
and
1 deletion.
There are no files selected for viewing
209 changes: 209 additions & 0 deletions
209
src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
src/main/java/ca/spottedleaf/moonrise/mixin/entity_tracker/ChunkMapMixin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.