mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
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:
parent
9b3cd8f725
commit
191777773c
5 changed files with 79 additions and 52 deletions
|
@ -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.Chunk;
|
||||||
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
import lombok.Getter;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import org.geysermc.connector.bootstrap.GeyserBootstrap;
|
import org.geysermc.connector.bootstrap.GeyserBootstrap;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||||
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
|
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ChunkCache {
|
public class ChunkCache {
|
||||||
|
|
||||||
private final boolean cache;
|
private final boolean cache;
|
||||||
|
|
||||||
@Getter
|
private final Long2ObjectMap<Column> chunks = new Long2ObjectOpenHashMap<>();
|
||||||
private final Map<ChunkPosition, Column> chunks = new HashMap<>();
|
|
||||||
|
|
||||||
public ChunkCache(GeyserSession session) {
|
public ChunkCache(GeyserSession session) {
|
||||||
if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) {
|
if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) {
|
||||||
|
@ -56,57 +52,66 @@ public class ChunkCache {
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ());
|
|
||||||
if (chunk.getBiomeData() == null && chunks.containsKey(position)) {
|
long chunkPosition = ChunkPosition.toLong(chunk.getX(), chunk.getZ());
|
||||||
Column newColumn = chunk;
|
Column existingChunk;
|
||||||
chunk = chunks.get(position);
|
if (chunk.getBiomeData() != null // Only consider merging columns if the new chunk isn't a full chunk
|
||||||
for (int i = 0; i < newColumn.getChunks().length; i++) {
|
&& (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing
|
||||||
if (newColumn.getChunks()[i] != null) {
|
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
|
||||||
chunk.getChunks()[i] = newColumn.getChunks()[i];
|
if (chunk.getChunks()[i] != null) {
|
||||||
|
existingChunk.getChunks()[i] = chunk.getChunks()[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
chunks.put(position, chunk);
|
chunks.put(chunkPosition, 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (!cache) {
|
||||||
return BlockTranslator.AIR;
|
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);
|
Column column = this.getChunk(x >> 4, z >> 4);
|
||||||
Chunk chunk = column.getChunks()[position.getY() >> 4];
|
if (column == null) {
|
||||||
Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ());
|
return BlockTranslator.AIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk chunk = column.getChunks()[y >> 4];
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
return chunk.get(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
return chunk.get(x & 0xF, y & 0xF, z & 0xF);
|
||||||
}
|
}
|
||||||
|
|
||||||
return BlockTranslator.AIR;
|
return BlockTranslator.AIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeChunk(ChunkPosition position) {
|
public void removeChunk(int chunkX, int chunkZ) {
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chunks.remove(position);
|
|
||||||
|
long chunkPosition = ChunkPosition.toLong(chunkX, chunkZ);
|
||||||
|
chunks.remove(chunkPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ package org.geysermc.connector.network.translators.java.world;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
import org.geysermc.connector.network.translators.Translator;
|
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;
|
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket;
|
||||||
|
|
||||||
|
@ -37,6 +36,6 @@ public class JavaUnloadChunkTranslator extends PacketTranslator<ServerUnloadChun
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(ServerUnloadChunkPacket packet, GeyserSession session) {
|
public void translate(ServerUnloadChunkPacket packet, GeyserSession session) {
|
||||||
session.getChunkCache().removeChunk(new ChunkPosition(packet.getX(), packet.getZ()));
|
session.getChunkCache().removeChunk(packet.getX(), packet.getZ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,12 @@
|
||||||
|
|
||||||
package org.geysermc.connector.network.translators.world;
|
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.entity.player.GameMode;
|
||||||
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
|
|
||||||
import org.geysermc.connector.utils.GameRule;
|
import org.geysermc.connector.utils.GameRule;
|
||||||
|
|
||||||
public class GeyserWorldManager extends WorldManager {
|
public class GeyserWorldManager extends WorldManager {
|
||||||
|
@ -41,14 +39,14 @@ public class GeyserWorldManager extends WorldManager {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
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
|
@Override
|
||||||
public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
|
public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
|
||||||
if (!session.getConnector().getConfig().isCacheChunks())
|
if (!session.getConnector().getConfig().isCacheChunks())
|
||||||
return new int[1024];
|
return new int[1024];
|
||||||
return session.getChunkCache().getChunks().get(new ChunkPosition(x, z)).getBiomeData();
|
return session.getChunkCache().getChunk(x, z).getBiomeData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -27,16 +27,25 @@ package org.geysermc.connector.network.translators.world.chunk;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@EqualsAndHashCode
|
|
||||||
public class ChunkPosition {
|
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 x;
|
||||||
private int z;
|
private int z;
|
||||||
|
|
||||||
|
@ -50,4 +59,21 @@ public class ChunkPosition {
|
||||||
int chunkZ = z & 15;
|
int chunkZ = z & 15;
|
||||||
return new Position(chunkX, chunkY, chunkZ);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,8 +112,7 @@ public class ChunkUtils {
|
||||||
int blockState;
|
int blockState;
|
||||||
// If a non-full chunk, then grab the block that should be here to create a 'full' chunk
|
// If a non-full chunk, then grab the block that should be here to create a 'full' chunk
|
||||||
if (chunk == null) {
|
if (chunk == null) {
|
||||||
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
|
blockState = session.getConnector().getWorldManager().getBlockAt(session, (column.getX() << 4) + x, (chunkY << 4) + y, (column.getZ() << 4) + z);
|
||||||
blockState = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ());
|
|
||||||
} else {
|
} else {
|
||||||
blockState = chunk.get(x, y, z);
|
blockState = chunk.get(x, y, z);
|
||||||
}
|
}
|
||||||
|
@ -250,7 +249,7 @@ public class ChunkUtils {
|
||||||
break; //No block will be a part of two classes
|
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) {
|
public static void sendEmptyChunks(GeyserSession session, Vector3i position, int radius, boolean forceUpdate) {
|
||||||
|
|
Loading…
Reference in a new issue