Added a whole new caching system for ChunkDataPacket and UpdateLightPacket

This commit is contained in:
themode 2020-11-20 11:14:15 +01:00
parent e453a0f9b5
commit 075ff7600a
11 changed files with 246 additions and 8 deletions

View File

@ -43,6 +43,7 @@ import net.minestom.server.storage.StorageManager;
import net.minestom.server.timer.SchedulerManager; import net.minestom.server.timer.SchedulerManager;
import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.PacketUtils; import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.cache.TemporaryCache;
import net.minestom.server.utils.thread.MinestomThread; import net.minestom.server.utils.thread.MinestomThread;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.Difficulty; import net.minestom.server.world.Difficulty;
@ -654,6 +655,7 @@ public final class MinecraftServer {
LOGGER.info("Shutting down all thread pools."); LOGGER.info("Shutting down all thread pools.");
benchmarkManager.disable(); benchmarkManager.disable();
commandManager.stopConsoleThread(); commandManager.stopConsoleThread();
TemporaryCache.REMOVER_SERVICE.shutdown();
MinestomThread.shutdownAll(); MinestomThread.shutdownAll();
LOGGER.info("Minestom server stopped successfully."); LOGGER.info("Minestom server stopped successfully.");
} }

View File

@ -61,6 +61,8 @@ public abstract class Chunk implements Viewable, DataContainer {
public static final int BIOME_COUNT = 1024; // 4x4x4 blocks group public static final int BIOME_COUNT = 1024; // 4x4x4 blocks group
private final UUID identifier;
@NotNull @NotNull
protected final Biome[] biomes; protected final Biome[] biomes;
protected final int chunkX, chunkZ; protected final int chunkX, chunkZ;
@ -79,6 +81,7 @@ public abstract class Chunk implements Viewable, DataContainer {
protected Data data; protected Data data;
public Chunk(@Nullable Biome[] biomes, int chunkX, int chunkZ, boolean shouldGenerate) { public Chunk(@Nullable Biome[] biomes, int chunkX, int chunkZ, boolean shouldGenerate) {
this.identifier = UUID.randomUUID();
this.chunkX = chunkX; this.chunkX = chunkX;
this.chunkZ = chunkZ; this.chunkZ = chunkZ;
this.shouldGenerate = shouldGenerate; this.shouldGenerate = shouldGenerate;
@ -270,6 +273,18 @@ public abstract class Chunk implements Viewable, DataContainer {
return getCustomBlock(x, y, z); return getCustomBlock(x, y, z);
} }
/**
* Gets the unique identifier of this chunk.
* <p>
* WARNING: this UUID is not persistent but randomized once the object is instantiate.
*
* @return the chunk identifier
*/
@NotNull
public UUID getIdentifier() {
return identifier;
}
public Biome[] getBiomes() { public Biome[] getBiomes() {
return biomes; return biomes;
} }
@ -459,7 +474,7 @@ public abstract class Chunk implements Viewable, DataContainer {
// TODO do not hardcode light // TODO do not hardcode light
{ {
UpdateLightPacket updateLightPacket = new UpdateLightPacket(); UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime());
updateLightPacket.chunkX = getChunkX(); updateLightPacket.chunkX = getChunkX();
updateLightPacket.chunkZ = getChunkZ(); updateLightPacket.chunkZ = getChunkZ();
updateLightPacket.skyLightMask = 0x3FFF0; updateLightPacket.skyLightMask = 0x3FFF0;

View File

@ -383,7 +383,7 @@ public class DynamicChunk extends Chunk {
@NotNull @NotNull
@Override @Override
protected ChunkDataPacket createFreshPacket() { protected ChunkDataPacket createFreshPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket(); ChunkDataPacket fullDataPacket = new ChunkDataPacket(getIdentifier(), getLastChangeTime());
fullDataPacket.biomes = biomes.clone(); fullDataPacket.biomes = biomes.clone();
fullDataPacket.chunkX = chunkX; fullDataPacket.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ; fullDataPacket.chunkZ = chunkZ;

View File

@ -45,7 +45,7 @@ public final class NettyServer {
public static final String DECODER_HANDLER_NAME = "decoder"; // Read public static final String DECODER_HANDLER_NAME = "decoder"; // Read
public static final String ENCODER_HANDLER_NAME = "encoder"; // Write public static final String ENCODER_HANDLER_NAME = "encoder"; // Write
public static final String CLIENT_HANDLER_NAME = "handler"; // Read public static final String CLIENT_CHANNEL_NAME = "handler"; // Read
private final EventLoopGroup boss, worker; private final EventLoopGroup boss, worker;
private final ServerBootstrap bootstrap; private final ServerBootstrap bootstrap;
@ -121,7 +121,7 @@ public final class NettyServer {
// Writes packet to bytebuf // Writes packet to bytebuf
pipeline.addLast(ENCODER_HANDLER_NAME, new PacketEncoder()); pipeline.addLast(ENCODER_HANDLER_NAME, new PacketEncoder());
pipeline.addLast(CLIENT_HANDLER_NAME, new ClientChannel(packetProcessor)); pipeline.addLast(CLIENT_CHANNEL_NAME, new ClientChannel(packetProcessor));
} }
}); });
} }

View File

@ -13,16 +13,21 @@ import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Utils; import net.minestom.server.utils.Utils;
import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.cache.CacheablePacket;
import net.minestom.server.utils.cache.TemporaryPacketCache;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.world.biomes.Biome; import net.minestom.server.world.biomes.Biome;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.Set; import java.util.Set;
import java.util.UUID;
public class ChunkDataPacket implements ServerPacket { public class ChunkDataPacket implements ServerPacket, CacheablePacket {
private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager();
private static final TemporaryPacketCache CACHE = new TemporaryPacketCache(10000L);
public boolean fullChunk; public boolean fullChunk;
public Biome[] biomes; public Biome[] biomes;
@ -40,6 +45,15 @@ public class ChunkDataPacket implements ServerPacket {
private static final int MAX_BITS_PER_ENTRY = 16; private static final int MAX_BITS_PER_ENTRY = 16;
private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * MAX_BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES; private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * MAX_BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES;
// Cacheable data
private UUID identifier;
private long lastUpdate;
public ChunkDataPacket(@Nullable UUID identifier, long lastUpdate) {
this.identifier = identifier;
this.lastUpdate = lastUpdate;
}
@Override @Override
public void write(@NotNull BinaryWriter writer) { public void write(@NotNull BinaryWriter writer) {
writer.writeInt(chunkX); writer.writeInt(chunkX);
@ -121,4 +135,19 @@ public class ChunkDataPacket implements ServerPacket {
public int getId() { public int getId() {
return ServerPacketIdentifier.CHUNK_DATA; return ServerPacketIdentifier.CHUNK_DATA;
} }
@Override
public TemporaryPacketCache getCache() {
return CACHE;
}
@Override
public UUID getIdentifier() {
return identifier;
}
@Override
public long getLastUpdateTime() {
return lastUpdate;
}
} }

View File

@ -3,11 +3,17 @@ package net.minestom.server.network.packet.server.play;
import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.cache.CacheablePacket;
import net.minestom.server.utils.cache.TemporaryPacketCache;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.UUID;
public class UpdateLightPacket implements ServerPacket { public class UpdateLightPacket implements ServerPacket, CacheablePacket {
private static final TemporaryPacketCache CACHE = new TemporaryPacketCache(10000L);
public int chunkX; public int chunkX;
public int chunkZ; public int chunkZ;
@ -23,6 +29,15 @@ public class UpdateLightPacket implements ServerPacket {
public List<byte[]> skyLight; public List<byte[]> skyLight;
public List<byte[]> blockLight; public List<byte[]> blockLight;
// Cacheable data
private UUID identifier;
private long lastUpdate;
public UpdateLightPacket(@Nullable UUID identifier, long lastUpdate) {
this.identifier = identifier;
this.lastUpdate = lastUpdate;
}
@Override @Override
public void write(@NotNull BinaryWriter writer) { public void write(@NotNull BinaryWriter writer) {
writer.writeVarInt(chunkX); writer.writeVarInt(chunkX);
@ -53,4 +68,19 @@ public class UpdateLightPacket implements ServerPacket {
public int getId() { public int getId() {
return ServerPacketIdentifier.UPDATE_LIGHT; return ServerPacketIdentifier.UPDATE_LIGHT;
} }
@Override
public TemporaryPacketCache getCache() {
return CACHE;
}
@Override
public UUID getIdentifier() {
return identifier;
}
@Override
public long getLastUpdateTime() {
return lastUpdate;
}
} }

View File

@ -1,5 +1,6 @@
package net.minestom.server.network.player; package net.minestom.server.network.player;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
@ -10,8 +11,12 @@ import net.minestom.server.extras.mojangAuth.MojangCrypt;
import net.minestom.server.network.ConnectionState; import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.netty.NettyServer; import net.minestom.server.network.netty.NettyServer;
import net.minestom.server.network.netty.codec.PacketCompressor; import net.minestom.server.network.netty.codec.PacketCompressor;
import net.minestom.server.network.netty.packet.FramedPacket;
import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.login.SetCompressionPacket; import net.minestom.server.network.packet.server.login.SetCompressionPacket;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.cache.CacheablePacket;
import net.minestom.server.utils.cache.TemporaryCache;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -108,7 +113,32 @@ public class NettyPlayerConnection extends PlayerConnection {
public void sendPacket(@NotNull ServerPacket serverPacket) { public void sendPacket(@NotNull ServerPacket serverPacket) {
if (shouldSendPacket(serverPacket)) { if (shouldSendPacket(serverPacket)) {
if (getPlayer() != null) { if (getPlayer() != null) {
this.channel.write(serverPacket); // Flush on player update // Flush happen during #update()
if (serverPacket instanceof CacheablePacket) {
CacheablePacket cacheablePacket = (CacheablePacket) serverPacket;
final UUID identifier = cacheablePacket.getIdentifier();
if (identifier == null) {
// This packet explicitly said to do not retrieve the cache
this.channel.write(serverPacket);
} else {
// Try to retrieve the cached buffer
TemporaryCache<ByteBuf> temporaryCache = cacheablePacket.getCache();
ByteBuf buffer = temporaryCache.retrieve(identifier);
if (buffer == null) {
// Buffer not found, create and cache it
final long time = System.currentTimeMillis();
buffer = PacketUtils.createFramedPacket(serverPacket);
temporaryCache.cacheObject(identifier, buffer, time);
}
FramedPacket framedPacket = new FramedPacket(buffer);
this.channel.write(framedPacket);
}
} else {
this.channel.write(serverPacket);
}
} else { } else {
this.channel.writeAndFlush(serverPacket); this.channel.writeAndFlush(serverPacket);
} }

View File

@ -149,7 +149,7 @@ public final class PacketUtils {
} }
} }
private static ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket) { public static ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket) {
ByteBuf packetBuf = writePacket(serverPacket); ByteBuf packetBuf = writePacket(serverPacket);
// TODO use pooled buffers instead of unpooled ones // TODO use pooled buffers instead of unpooled ones

View File

@ -0,0 +1,45 @@
package net.minestom.server.utils.cache;
import net.minestom.server.network.packet.server.ServerPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* Implemented by {@link ServerPacket server packets} which can be temporary cached in memory to be re-sent later
* without having to go through all the writing and compression.
* <p>
* {@link #getIdentifier()} is to differenciate this packet from the others of the same type,
* and {@link #getLastUpdateTime()} to know if one packet is newer than the previous one.
*/
public interface CacheablePacket {
/**
* Gets the cache linked to this packet.
* <p>
* WARNING: the cache needs to be shared between all the object instances, tips is to make it static.
*
* @return the temporary packet cache
*/
@NotNull
TemporaryPacketCache getCache();
/**
* Gets the identifier of this packet.
* <p>
* Used to verify if this packet is already cached or not.
*
* @return this packet identifier, null to do not retrieve the cache
*/
@Nullable
UUID getIdentifier();
/**
* Gets the last time this packet changed.
*
* @return the last packet update time in milliseconds
*/
long getLastUpdateTime();
}

View File

@ -0,0 +1,75 @@
package net.minestom.server.utils.cache;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Cache objects with a timeout.
*
* @param <T> the object type to cache
*/
public class TemporaryCache<T> {
public static final ScheduledExecutorService REMOVER_SERVICE = Executors.newScheduledThreadPool(1);
// Identifier = Cached object
protected ConcurrentHashMap<UUID, T> cache = new ConcurrentHashMap<>();
// Identifier = time
protected ConcurrentHashMap<UUID, Long> cacheTime = new ConcurrentHashMap<>();
private long keepTime;
/**
* Creates a new temporary cache.
*
* @param keepTime the time before considering an object unused in milliseconds
* @see #getKeepTime()
*/
public TemporaryCache(long keepTime) {
this.keepTime = keepTime;
REMOVER_SERVICE.scheduleAtFixedRate(() -> {
final boolean removed = cacheTime.values().removeIf(time -> time + keepTime > System.currentTimeMillis());
if (removed) {
this.cache.entrySet().removeIf(entry -> !cacheTime.containsKey(entry.getKey()));
}
}, keepTime, keepTime, TimeUnit.MILLISECONDS);
}
/**
* Caches an object
*
* @param identifier the object identifier
* @param value the object to cache
* @param time the current time in milliseconds
*/
public synchronized void cacheObject(@NotNull UUID identifier, T value, long time) {
this.cache.put(identifier, value);
this.cacheTime.put(identifier, time);
}
/**
* Retrieves an object from cache.
*
* @param identifier the object identifier
* @return the retrieved object or null if not found
*/
@Nullable
public T retrieve(@NotNull UUID identifier) {
return cache.get(identifier);
}
/**
* Gets the time an object will be kept without being retrieved
*
* @return the keep time in milliseconds
*/
public long getKeepTime() {
return keepTime;
}
}

View File

@ -0,0 +1,12 @@
package net.minestom.server.utils.cache;
import io.netty.buffer.ByteBuf;
/**
* Convenient superclass of {@link TemporaryCache} explicitly for packet to store a {@link ByteBuf}.
*/
public class TemporaryPacketCache extends TemporaryCache<ByteBuf> {
public TemporaryPacketCache(long keepTime) {
super(keepTime);
}
}