From 075ff7600a109c930219d553dc15561739ca268a Mon Sep 17 00:00:00 2001 From: themode Date: Fri, 20 Nov 2020 11:14:15 +0100 Subject: [PATCH] Added a whole new caching system for ChunkDataPacket and UpdateLightPacket --- .../net/minestom/server/MinecraftServer.java | 2 + .../net/minestom/server/instance/Chunk.java | 17 ++++- .../server/instance/DynamicChunk.java | 2 +- .../server/network/netty/NettyServer.java | 4 +- .../packet/server/play/ChunkDataPacket.java | 31 +++++++- .../packet/server/play/UpdateLightPacket.java | 32 +++++++- .../network/player/NettyPlayerConnection.java | 32 +++++++- .../minestom/server/utils/PacketUtils.java | 2 +- .../server/utils/cache/CacheablePacket.java | 45 +++++++++++ .../server/utils/cache/TemporaryCache.java | 75 +++++++++++++++++++ .../utils/cache/TemporaryPacketCache.java | 12 +++ 11 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 src/main/java/net/minestom/server/utils/cache/CacheablePacket.java create mode 100644 src/main/java/net/minestom/server/utils/cache/TemporaryCache.java create mode 100644 src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 91f37f2c0..03f536c03 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -43,6 +43,7 @@ import net.minestom.server.storage.StorageManager; import net.minestom.server.timer.SchedulerManager; import net.minestom.server.utils.MathUtils; 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.validate.Check; import net.minestom.server.world.Difficulty; @@ -654,6 +655,7 @@ public final class MinecraftServer { LOGGER.info("Shutting down all thread pools."); benchmarkManager.disable(); commandManager.stopConsoleThread(); + TemporaryCache.REMOVER_SERVICE.shutdown(); MinestomThread.shutdownAll(); LOGGER.info("Minestom server stopped successfully."); } diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index 92e37c495..e2327e266 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -61,6 +61,8 @@ public abstract class Chunk implements Viewable, DataContainer { public static final int BIOME_COUNT = 1024; // 4x4x4 blocks group + private final UUID identifier; + @NotNull protected final Biome[] biomes; protected final int chunkX, chunkZ; @@ -79,6 +81,7 @@ public abstract class Chunk implements Viewable, DataContainer { protected Data data; public Chunk(@Nullable Biome[] biomes, int chunkX, int chunkZ, boolean shouldGenerate) { + this.identifier = UUID.randomUUID(); this.chunkX = chunkX; this.chunkZ = chunkZ; this.shouldGenerate = shouldGenerate; @@ -270,6 +273,18 @@ public abstract class Chunk implements Viewable, DataContainer { return getCustomBlock(x, y, z); } + /** + * Gets the unique identifier of this chunk. + *

+ * 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() { return biomes; } @@ -459,7 +474,7 @@ public abstract class Chunk implements Viewable, DataContainer { // TODO do not hardcode light { - UpdateLightPacket updateLightPacket = new UpdateLightPacket(); + UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime()); updateLightPacket.chunkX = getChunkX(); updateLightPacket.chunkZ = getChunkZ(); updateLightPacket.skyLightMask = 0x3FFF0; diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index 80929c9e9..524041707 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -383,7 +383,7 @@ public class DynamicChunk extends Chunk { @NotNull @Override protected ChunkDataPacket createFreshPacket() { - ChunkDataPacket fullDataPacket = new ChunkDataPacket(); + ChunkDataPacket fullDataPacket = new ChunkDataPacket(getIdentifier(), getLastChangeTime()); fullDataPacket.biomes = biomes.clone(); fullDataPacket.chunkX = chunkX; fullDataPacket.chunkZ = chunkZ; diff --git a/src/main/java/net/minestom/server/network/netty/NettyServer.java b/src/main/java/net/minestom/server/network/netty/NettyServer.java index 1af09b5ee..c3b94b75f 100644 --- a/src/main/java/net/minestom/server/network/netty/NettyServer.java +++ b/src/main/java/net/minestom/server/network/netty/NettyServer.java @@ -45,7 +45,7 @@ public final class NettyServer { public static final String DECODER_HANDLER_NAME = "decoder"; // Read 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 ServerBootstrap bootstrap; @@ -121,7 +121,7 @@ public final class NettyServer { // Writes packet to bytebuf pipeline.addLast(ENCODER_HANDLER_NAME, new PacketEncoder()); - pipeline.addLast(CLIENT_HANDLER_NAME, new ClientChannel(packetProcessor)); + pipeline.addLast(CLIENT_CHANNEL_NAME, new ClientChannel(packetProcessor)); } }); } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java index 0f68d6560..aaae57545 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java @@ -13,16 +13,21 @@ import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.Utils; 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.world.biomes.Biome; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; 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 TemporaryPacketCache CACHE = new TemporaryPacketCache(10000L); public boolean fullChunk; 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_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 public void write(@NotNull BinaryWriter writer) { writer.writeInt(chunkX); @@ -121,4 +135,19 @@ public class ChunkDataPacket implements ServerPacket { public int getId() { return ServerPacketIdentifier.CHUNK_DATA; } + + @Override + public TemporaryPacketCache getCache() { + return CACHE; + } + + @Override + public UUID getIdentifier() { + return identifier; + } + + @Override + public long getLastUpdateTime() { + return lastUpdate; + } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/network/packet/server/play/UpdateLightPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/UpdateLightPacket.java index 6647d99ae..c2df52b61 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/UpdateLightPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/UpdateLightPacket.java @@ -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.ServerPacketIdentifier; 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.Nullable; 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 chunkZ; @@ -23,6 +29,15 @@ public class UpdateLightPacket implements ServerPacket { public List skyLight; public List blockLight; + // Cacheable data + private UUID identifier; + private long lastUpdate; + + public UpdateLightPacket(@Nullable UUID identifier, long lastUpdate) { + this.identifier = identifier; + this.lastUpdate = lastUpdate; + } + @Override public void write(@NotNull BinaryWriter writer) { writer.writeVarInt(chunkX); @@ -53,4 +68,19 @@ public class UpdateLightPacket implements ServerPacket { public int getId() { return ServerPacketIdentifier.UPDATE_LIGHT; } + + @Override + public TemporaryPacketCache getCache() { + return CACHE; + } + + @Override + public UUID getIdentifier() { + return identifier; + } + + @Override + public long getLastUpdateTime() { + return lastUpdate; + } } diff --git a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java index 7365329a4..d13b10959 100644 --- a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java @@ -1,5 +1,6 @@ package net.minestom.server.network.player; +import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.socket.SocketChannel; 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.netty.NettyServer; 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.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 org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -108,7 +113,32 @@ public class NettyPlayerConnection extends PlayerConnection { public void sendPacket(@NotNull ServerPacket serverPacket) { if (shouldSendPacket(serverPacket)) { 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 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 { this.channel.writeAndFlush(serverPacket); } diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java index 463f52838..d45dbbd8a 100644 --- a/src/main/java/net/minestom/server/utils/PacketUtils.java +++ b/src/main/java/net/minestom/server/utils/PacketUtils.java @@ -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); // TODO use pooled buffers instead of unpooled ones diff --git a/src/main/java/net/minestom/server/utils/cache/CacheablePacket.java b/src/main/java/net/minestom/server/utils/cache/CacheablePacket.java new file mode 100644 index 000000000..9c32f6103 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/cache/CacheablePacket.java @@ -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. + *

+ * {@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. + *

+ * 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. + *

+ * 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(); + +} diff --git a/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java b/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java new file mode 100644 index 000000000..8a8074b3c --- /dev/null +++ b/src/main/java/net/minestom/server/utils/cache/TemporaryCache.java @@ -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 the object type to cache + */ +public class TemporaryCache { + + public static final ScheduledExecutorService REMOVER_SERVICE = Executors.newScheduledThreadPool(1); + + // Identifier = Cached object + protected ConcurrentHashMap cache = new ConcurrentHashMap<>(); + // Identifier = time + protected ConcurrentHashMap 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; + } +} diff --git a/src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java b/src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java new file mode 100644 index 000000000..4e7b975b0 --- /dev/null +++ b/src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java @@ -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 { + public TemporaryPacketCache(long keepTime) { + super(keepTime); + } +}