mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-28 03:57:50 +01:00
Added a whole new caching system for ChunkDataPacket and UpdateLightPacket
This commit is contained in:
parent
e453a0f9b5
commit
075ff7600a
@ -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.");
|
||||
}
|
||||
|
@ -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.
|
||||
* <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() {
|
||||
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;
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<byte[]> skyLight;
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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<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 {
|
||||
this.channel.writeAndFlush(serverPacket);
|
||||
}
|
||||
|
@ -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
|
||||
|
45
src/main/java/net/minestom/server/utils/cache/CacheablePacket.java
vendored
Normal file
45
src/main/java/net/minestom/server/utils/cache/CacheablePacket.java
vendored
Normal 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();
|
||||
|
||||
}
|
75
src/main/java/net/minestom/server/utils/cache/TemporaryCache.java
vendored
Normal file
75
src/main/java/net/minestom/server/utils/cache/TemporaryCache.java
vendored
Normal 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;
|
||||
}
|
||||
}
|
12
src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java
vendored
Normal file
12
src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user