Don't use wrapper objects for positions in ChunkCache (#1398)

* make ChunkPosition use a hashCode implementation with far better hash distribution

this should improve the performance when used as a hash table key

* ChunkCache no longer uses position wrapper objects

this yields a roughly 15-20% increase in performance when converting chunk data

* fix code style issues
This commit is contained in:
DaPorkchop_ 2020-10-13 16:44:47 +02:00 committed by GitHub
parent 9b3cd8f725
commit 191777773c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 79 additions and 52 deletions

View File

@ -27,22 +27,18 @@ package org.geysermc.connector.network.session.cache;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import lombok.Getter;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
import java.util.HashMap;
import java.util.Map;
public class ChunkCache {
private final boolean cache;
@Getter
private final Map<ChunkPosition, Column> chunks = new HashMap<>();
private final Long2ObjectMap<Column> chunks = new Long2ObjectOpenHashMap<>();
public ChunkCache(GeyserSession session) {
if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) {
@ -56,57 +52,66 @@ public class ChunkCache {
if (!cache) {
return;
}
ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ());
if (chunk.getBiomeData() == null && chunks.containsKey(position)) {
Column newColumn = chunk;
chunk = chunks.get(position);
for (int i = 0; i < newColumn.getChunks().length; i++) {
if (newColumn.getChunks()[i] != null) {
chunk.getChunks()[i] = newColumn.getChunks()[i];
long chunkPosition = ChunkPosition.toLong(chunk.getX(), chunk.getZ());
Column existingChunk;
if (chunk.getBiomeData() != null // Only consider merging columns if the new chunk isn't a full chunk
&& (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing
for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away
if (chunk.getChunks()[i] != null) {
existingChunk.getChunks()[i] = chunk.getChunks()[i];
}
}
}
chunks.put(position, chunk);
}
public void updateBlock(Position position, int block) {
if (!cache) {
return;
}
ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4);
if (!chunks.containsKey(chunkPosition))
return;
Column column = chunks.get(chunkPosition);
Chunk chunk = column.getChunks()[position.getY() >> 4];
Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ());
if (chunk != null) {
chunk.set(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), block);
} else {
chunks.put(chunkPosition, chunk);
}
}
public int getBlockAt(Position position) {
public Column getChunk(int chunkX, int chunkZ) {
long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ);
return chunks.getOrDefault(chunkPosition, null);
}
public void updateBlock(int x, int y, int z, int block) {
if (!cache) {
return;
}
Column column = this.getChunk(x >> 4, z >> 4);
if (column == null) {
return;
}
Chunk chunk = column.getChunks()[y >> 4];
if (chunk != null) {
chunk.set(x & 0xF, y & 0xF, z & 0xF, block);
}
}
public int getBlockAt(int x, int y, int z) {
if (!cache) {
return BlockTranslator.AIR;
}
ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4);
if (!chunks.containsKey(chunkPosition))
return BlockTranslator.AIR;
Column column = chunks.get(chunkPosition);
Chunk chunk = column.getChunks()[position.getY() >> 4];
Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ());
Column column = this.getChunk(x >> 4, z >> 4);
if (column == null) {
return BlockTranslator.AIR;
}
Chunk chunk = column.getChunks()[y >> 4];
if (chunk != null) {
return chunk.get(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
return chunk.get(x & 0xF, y & 0xF, z & 0xF);
}
return BlockTranslator.AIR;
}
public void removeChunk(ChunkPosition position) {
public void removeChunk(int chunkX, int chunkZ) {
if (!cache) {
return;
}
chunks.remove(position);
long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ);
chunks.remove(chunkPosition);
}
}

View File

@ -28,7 +28,6 @@ package org.geysermc.connector.network.translators.java.world;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket;
@ -37,6 +36,6 @@ public class JavaUnloadChunkTranslator extends PacketTranslator<ServerUnloadChun
@Override
public void translate(ServerUnloadChunkPacket packet, GeyserSession session) {
session.getChunkCache().removeChunk(new ChunkPosition(packet.getX(), packet.getZ()));
session.getChunkCache().removeChunk(packet.getX(), packet.getZ());
}
}

View File

@ -25,14 +25,12 @@
package org.geysermc.connector.network.translators.world;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
import org.geysermc.connector.utils.GameRule;
public class GeyserWorldManager extends WorldManager {
@ -41,14 +39,14 @@ public class GeyserWorldManager extends WorldManager {
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
return session.getChunkCache().getBlockAt(new Position(x, y, z));
return session.getChunkCache().getBlockAt(x, y, z);
}
@Override
public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
if (!session.getConnector().getConfig().isCacheChunks())
return new int[1024];
return session.getChunkCache().getChunks().get(new ChunkPosition(x, z)).getBiomeData();
return session.getChunkCache().getChunk(x, z).getBiomeData();
}
@Override

View File

@ -27,16 +27,25 @@ package org.geysermc.connector.network.translators.world.chunk;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@EqualsAndHashCode
public class ChunkPosition {
/**
* Packs a chunk's X and Z coordinates into a single {@code long}.
*
* @param x the X coordinate
* @param z the Z coordinate
* @return the packed coordinates
*/
public static long toLong(int x, int z) {
return ((x & 0xFFFFFFFFL) << 32L) | (z & 0xFFFFFFFFL);
}
private int x;
private int z;
@ -50,4 +59,21 @@ public class ChunkPosition {
int chunkZ = z & 15;
return new Position(chunkX, chunkY, chunkZ);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof ChunkPosition) {
ChunkPosition chunkPosition = (ChunkPosition) obj;
return this.x == chunkPosition.x && this.z == chunkPosition.z;
} else {
return false;
}
}
@Override
public int hashCode() {
return this.x * 2061811133 + this.z * 1424368303;
}
}

View File

@ -112,8 +112,7 @@ public class ChunkUtils {
int blockState;
// If a non-full chunk, then grab the block that should be here to create a 'full' chunk
if (chunk == null) {
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
blockState = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ());
blockState = session.getConnector().getWorldManager().getBlockAt(session, (column.getX() << 4) + x, (chunkY << 4) + y, (column.getZ() << 4) + z);
} else {
blockState = chunk.get(x, y, z);
}
@ -250,7 +249,7 @@ public class ChunkUtils {
break; //No block will be a part of two classes
}
}
session.getChunkCache().updateBlock(new Position(position.getX(), position.getY(), position.getZ()), blockState);
session.getChunkCache().updateBlock(position.getX(), position.getY(), position.getZ(), blockState);
}
public static void sendEmptyChunks(GeyserSession session, Vector3i position, int radius, boolean forceUpdate) {