Merge branch 'master' into position-cleanup

This commit is contained in:
TheMode 2021-05-10 00:31:17 +02:00
commit f69b40109c
20 changed files with 180 additions and 258 deletions

View File

@ -5,7 +5,7 @@ plugins {
id 'java' id 'java'
id 'maven-publish' id 'maven-publish'
id 'net.ltgt.apt' version '0.10' id 'net.ltgt.apt' version '0.10'
id 'org.jetbrains.kotlin.jvm' version '1.4.21' id 'org.jetbrains.kotlin.jvm' version '1.5.0'
id 'checkstyle' id 'checkstyle'
} }

View File

@ -78,13 +78,15 @@ public final class MinecraftServer {
public static final String THREAD_NAME_TICK = "Ms-Tick"; public static final String THREAD_NAME_TICK = "Ms-Tick";
public static final String THREAD_NAME_BLOCK_BATCH = "Ms-BlockBatchPool"; public static final String THREAD_NAME_BLOCK_BATCH = "Ms-BlockBatchPool";
public static final int THREAD_COUNT_BLOCK_BATCH = 4; public static final int THREAD_COUNT_BLOCK_BATCH = getThreadCount("minestom.block-thread-count",
Runtime.getRuntime().availableProcessors() / 2);
public static final String THREAD_NAME_SCHEDULER = "Ms-SchedulerPool"; public static final String THREAD_NAME_SCHEDULER = "Ms-SchedulerPool";
public static final int THREAD_COUNT_SCHEDULER = 1; public static final int THREAD_COUNT_SCHEDULER = getThreadCount("minestom.scheduler-thread-count",
Runtime.getRuntime().availableProcessors() / 2);
public static final String THREAD_NAME_PARALLEL_CHUNK_SAVING = "Ms-ParallelChunkSaving"; public static final String THREAD_NAME_PARALLEL_CHUNK_SAVING = "Ms-ParallelChunkSaving";
public static final int THREAD_COUNT_PARALLEL_CHUNK_SAVING = 4; public static final int THREAD_COUNT_PARALLEL_CHUNK_SAVING = getThreadCount("minestom.save-thread-count", 2);
// Config // Config
// Can be modified at performance cost when increased // Can be modified at performance cost when increased
@ -823,4 +825,8 @@ public final class MinecraftServer {
"You cannot access the manager before MinecraftServer#init, " + "You cannot access the manager before MinecraftServer#init, " +
"if you are developing an extension be sure to retrieve them at least after Extension#preInitialize");*/ "if you are developing an extension be sure to retrieve them at least after Extension#preInitialize");*/
} }
private static int getThreadCount(@NotNull String property, int count) {
return Integer.getInteger(property, Math.min(1, count));
}
} }

View File

@ -17,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
@ -26,45 +27,44 @@ public class ArgumentParser {
private static final Map<String, Function<String, Argument<?>>> ARGUMENT_FUNCTION_MAP = new ConcurrentHashMap<>(); private static final Map<String, Function<String, Argument<?>>> ARGUMENT_FUNCTION_MAP = new ConcurrentHashMap<>();
static { static {
ARGUMENT_FUNCTION_MAP.put("Literal", ArgumentLiteral::new); ARGUMENT_FUNCTION_MAP.put("literal", ArgumentLiteral::new);
ARGUMENT_FUNCTION_MAP.put("Boolean", ArgumentBoolean::new); ARGUMENT_FUNCTION_MAP.put("boolean", ArgumentBoolean::new);
ARGUMENT_FUNCTION_MAP.put("Integer", ArgumentInteger::new); ARGUMENT_FUNCTION_MAP.put("integer", ArgumentInteger::new);
ARGUMENT_FUNCTION_MAP.put("Double", ArgumentDouble::new); ARGUMENT_FUNCTION_MAP.put("double", ArgumentDouble::new);
ARGUMENT_FUNCTION_MAP.put("Float", ArgumentFloat::new); ARGUMENT_FUNCTION_MAP.put("float", ArgumentFloat::new);
ARGUMENT_FUNCTION_MAP.put("String", ArgumentString::new); ARGUMENT_FUNCTION_MAP.put("string", ArgumentString::new);
ARGUMENT_FUNCTION_MAP.put("Word", ArgumentWord::new); ARGUMENT_FUNCTION_MAP.put("word", ArgumentWord::new);
ARGUMENT_FUNCTION_MAP.put("StringArray", ArgumentStringArray::new); ARGUMENT_FUNCTION_MAP.put("stringarray", ArgumentStringArray::new);
ARGUMENT_FUNCTION_MAP.put("Command", ArgumentCommand::new); ARGUMENT_FUNCTION_MAP.put("command", ArgumentCommand::new);
// TODO enum // TODO enum
ARGUMENT_FUNCTION_MAP.put("Color", ArgumentColor::new); ARGUMENT_FUNCTION_MAP.put("color", ArgumentColor::new);
ARGUMENT_FUNCTION_MAP.put("Time", ArgumentTime::new); ARGUMENT_FUNCTION_MAP.put("time", ArgumentTime::new);
ARGUMENT_FUNCTION_MAP.put("Enchantment", ArgumentEnchantment::new); ARGUMENT_FUNCTION_MAP.put("enchantment", ArgumentEnchantment::new);
ARGUMENT_FUNCTION_MAP.put("Particle", ArgumentParticle::new); ARGUMENT_FUNCTION_MAP.put("particle", ArgumentParticle::new);
ARGUMENT_FUNCTION_MAP.put("ResourceLocation", ArgumentResourceLocation::new); ARGUMENT_FUNCTION_MAP.put("resourceLocation", ArgumentResourceLocation::new);
ARGUMENT_FUNCTION_MAP.put("Potion", ArgumentPotionEffect::new); ARGUMENT_FUNCTION_MAP.put("potion", ArgumentPotionEffect::new);
ARGUMENT_FUNCTION_MAP.put("EntityType", ArgumentEntityType::new); ARGUMENT_FUNCTION_MAP.put("entityType", ArgumentEntityType::new);
ARGUMENT_FUNCTION_MAP.put("BlockState", ArgumentBlockState::new); ARGUMENT_FUNCTION_MAP.put("blockState", ArgumentBlockState::new);
ARGUMENT_FUNCTION_MAP.put("IntRange", ArgumentIntRange::new); ARGUMENT_FUNCTION_MAP.put("intrange", ArgumentIntRange::new);
ARGUMENT_FUNCTION_MAP.put("FloatRange", ArgumentFloatRange::new); ARGUMENT_FUNCTION_MAP.put("floatrange", ArgumentFloatRange::new);
ARGUMENT_FUNCTION_MAP.put("Entity", s -> new ArgumentEntity(s).singleEntity(true)); ARGUMENT_FUNCTION_MAP.put("entity", s -> new ArgumentEntity(s).singleEntity(true));
ARGUMENT_FUNCTION_MAP.put("Entities", ArgumentEntity::new); ARGUMENT_FUNCTION_MAP.put("entities", ArgumentEntity::new);
ARGUMENT_FUNCTION_MAP.put("Player", s -> new ArgumentEntity(s).singleEntity(true).onlyPlayers(true)); ARGUMENT_FUNCTION_MAP.put("player", s -> new ArgumentEntity(s).singleEntity(true).onlyPlayers(true));
ARGUMENT_FUNCTION_MAP.put("Players", s -> new ArgumentEntity(s).onlyPlayers(true)); ARGUMENT_FUNCTION_MAP.put("players", s -> new ArgumentEntity(s).onlyPlayers(true));
ARGUMENT_FUNCTION_MAP.put("ItemStack", ArgumentItemStack::new); ARGUMENT_FUNCTION_MAP.put("itemstack", ArgumentItemStack::new);
ARGUMENT_FUNCTION_MAP.put("Component", ArgumentComponent::new); ARGUMENT_FUNCTION_MAP.put("component", ArgumentComponent::new);
ARGUMENT_FUNCTION_MAP.put("UUID", ArgumentUUID::new); ARGUMENT_FUNCTION_MAP.put("uuid", ArgumentUUID::new);
ARGUMENT_FUNCTION_MAP.put("NBT", ArgumentNbtTag::new); ARGUMENT_FUNCTION_MAP.put("nbt", ArgumentNbtTag::new);
ARGUMENT_FUNCTION_MAP.put("NbtCompound", ArgumentNbtCompoundTag::new); ARGUMENT_FUNCTION_MAP.put("nbtcompound", ArgumentNbtCompoundTag::new);
ARGUMENT_FUNCTION_MAP.put("RelativeBlockPosition", ArgumentRelativeBlockPosition::new); ARGUMENT_FUNCTION_MAP.put("relativeblockposition", ArgumentRelativeBlockPosition::new);
ARGUMENT_FUNCTION_MAP.put("RelativeVec3", ArgumentRelativeVec3::new); ARGUMENT_FUNCTION_MAP.put("relativevec3", ArgumentRelativeVec3::new);
ARGUMENT_FUNCTION_MAP.put("RelativeVec2", ArgumentRelativeVec2::new); ARGUMENT_FUNCTION_MAP.put("relativevec2", ArgumentRelativeVec2::new);
} }
@Beta @Beta
@NotNull public static @NotNull Argument<?>[] generate(@NotNull String format) {
public static Argument<?>[] generate(@NotNull String format) {
List<Argument<?>> result = new ArrayList<>(); List<Argument<?>> result = new ArrayList<>();
// 0 = no state // 0 = no state
@ -92,7 +92,7 @@ public class ArgumentParser {
} else if (c == '<') { } else if (c == '<') {
// Retrieve argument type // Retrieve argument type
final String argument = builder.toString(); final String argument = builder.toString();
argumentFunction = ARGUMENT_FUNCTION_MAP.get(argument); argumentFunction = ARGUMENT_FUNCTION_MAP.get(argument.toLowerCase(Locale.ROOT));
if (argumentFunction == null) { if (argumentFunction == null) {
throw new IllegalArgumentException("error invalid argument name: " + argument); throw new IllegalArgumentException("error invalid argument name: " + argument);
} }

View File

@ -417,22 +417,18 @@ public abstract class Chunk implements Viewable, Tickable, DataContainer {
UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime()); UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime());
updateLightPacket.chunkX = getChunkX(); updateLightPacket.chunkX = getChunkX();
updateLightPacket.chunkZ = getChunkZ(); updateLightPacket.chunkZ = getChunkZ();
updateLightPacket.skyLightMask = 0x3FFF0; updateLightPacket.skyLightMask = 0b111111111111111111;
updateLightPacket.blockLightMask = 0x3F; updateLightPacket.emptySkyLightMask = 0b000000000000000000;
updateLightPacket.emptySkyLightMask = 0x0F; updateLightPacket.blockLightMask = 0b000000000000000000;
updateLightPacket.emptyBlockLightMask = 0x3FFC0; updateLightPacket.emptyBlockLightMask = 0b111111111111111111;
byte[] bytes = new byte[2048]; byte[] bytes = new byte[2048];
Arrays.fill(bytes, (byte) 0xFF); Arrays.fill(bytes, (byte) 0xFF);
List<byte[]> temp = new ArrayList<>(14); final List<byte[]> temp = new ArrayList<>(18);
List<byte[]> temp2 = new ArrayList<>(6); for (int i = 0; i < 18; ++i) {
for (int i = 0; i < 14; ++i) {
temp.add(bytes); temp.add(bytes);
} }
for (int i = 0; i < 6; ++i) {
temp2.add(bytes);
}
updateLightPacket.skyLight = temp; updateLightPacket.skyLight = temp;
updateLightPacket.blockLight = temp2; updateLightPacket.blockLight = new ArrayList<>(0);
return updateLightPacket; return updateLightPacket;
} }

View File

@ -26,6 +26,7 @@ import net.minestom.server.world.biomes.Biome;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.lang.ref.SoftReference;
import java.util.Set; import java.util.Set;
/** /**
@ -59,7 +60,7 @@ public class DynamicChunk extends Chunk {
private long lastChangeTime; private long lastChangeTime;
private ChunkDataPacket cachedPacket; private SoftReference<ChunkDataPacket> cachedPacket = new SoftReference<>(null);
private long cachedPacketTime; private long cachedPacketTime;
public DynamicChunk(@NotNull Instance instance, @Nullable Biome[] biomes, int chunkX, int chunkZ, public DynamicChunk(@NotNull Instance instance, @Nullable Biome[] biomes, int chunkX, int chunkZ,
@ -386,22 +387,22 @@ public class DynamicChunk extends Chunk {
@NotNull @NotNull
@Override @Override
protected ChunkDataPacket createFreshPacket() { protected ChunkDataPacket createFreshPacket() {
if (cachedPacket != null && cachedPacketTime == getLastChangeTime()) { ChunkDataPacket packet = cachedPacket.get();
return cachedPacket; if (packet != null && cachedPacketTime == getLastChangeTime()) {
return packet;
} }
ChunkDataPacket fullDataPacket = new ChunkDataPacket(getIdentifier(), getLastChangeTime()); packet = new ChunkDataPacket(getIdentifier(), getLastChangeTime());
fullDataPacket.biomes = biomes; packet.biomes = biomes;
fullDataPacket.chunkX = chunkX; packet.chunkX = chunkX;
fullDataPacket.chunkZ = chunkZ; packet.chunkZ = chunkZ;
fullDataPacket.paletteStorage = blockPalette.clone(); packet.paletteStorage = blockPalette.clone();
fullDataPacket.customBlockPaletteStorage = customBlockPalette.clone(); packet.customBlockPaletteStorage = customBlockPalette.clone();
fullDataPacket.blockEntities = blockEntities.clone(); packet.blockEntities = blockEntities.clone();
fullDataPacket.blocksData = blocksData.clone(); packet.blocksData = blocksData.clone();
this.cachedPacketTime = getLastChangeTime(); this.cachedPacketTime = getLastChangeTime();
this.cachedPacket = fullDataPacket; this.cachedPacket = new SoftReference<>(packet);
return packet;
return fullDataPacket;
} }
@NotNull @NotNull

View File

@ -793,6 +793,10 @@ public class InstanceContainer extends Instance {
* Unsafe because it has to be done on the same thread as the instance/chunks tick update. * Unsafe because it has to be done on the same thread as the instance/chunks tick update.
*/ */
protected void UNSAFE_unloadChunks() { protected void UNSAFE_unloadChunks() {
if (scheduledChunksToRemove.isEmpty()) {
// Fast exit
return;
}
synchronized (scheduledChunksToRemove) { synchronized (scheduledChunksToRemove) {
for (Chunk chunk : scheduledChunksToRemove) { for (Chunk chunk : scheduledChunksToRemove) {
final int chunkX = chunk.getChunkX(); final int chunkX = chunk.getChunkX();

View File

@ -272,38 +272,40 @@ public class PlayerInventory extends AbstractInventory implements EquipmentHandl
@Override @Override
public boolean leftClick(@NotNull Player player, int slot) { public boolean leftClick(@NotNull Player player, int slot) {
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
final ItemStack cursor = getCursorItem(); final ItemStack cursor = getCursorItem();
final ItemStack clicked = getItemStack(convertPlayerInventorySlot(slot, OFFSET)); final ItemStack clicked = getItemStack(convertedSlot);
final InventoryClickResult clickResult = clickProcessor.leftClick(null, player, slot, clicked, cursor); final InventoryClickResult clickResult = clickProcessor.leftClick(null, player, convertedSlot, clicked, cursor);
if (clickResult.doRefresh()) if (clickResult.doRefresh())
sendSlotRefresh((short) slot, clicked); sendSlotRefresh((short) slot, clicked);
setItemStack(slot, OFFSET, clickResult.getClicked()); setItemStack(convertedSlot, clickResult.getClicked());
setCursorItem(clickResult.getCursor()); setCursorItem(clickResult.getCursor());
if (!clickResult.isCancel()) if (!clickResult.isCancel())
callClickEvent(player, null, slot, ClickType.LEFT_CLICK, clicked, cursor); callClickEvent(player, null, convertedSlot, ClickType.LEFT_CLICK, clicked, cursor);
return !clickResult.isCancel(); return !clickResult.isCancel();
} }
@Override @Override
public boolean rightClick(@NotNull Player player, int slot) { public boolean rightClick(@NotNull Player player, int slot) {
final int convertedSlot = convertPlayerInventorySlot(slot, OFFSET);
final ItemStack cursor = getCursorItem(); final ItemStack cursor = getCursorItem();
final ItemStack clicked = getItemStack(slot, OFFSET); final ItemStack clicked = getItemStack(convertedSlot);
final InventoryClickResult clickResult = clickProcessor.rightClick(null, player, slot, clicked, cursor); final InventoryClickResult clickResult = clickProcessor.rightClick(null, player, convertedSlot, clicked, cursor);
if (clickResult.doRefresh()) if (clickResult.doRefresh())
sendSlotRefresh((short) slot, clicked); sendSlotRefresh((short) slot, clicked);
setItemStack(slot, OFFSET, clickResult.getClicked()); setItemStack(convertedSlot, clickResult.getClicked());
setCursorItem(clickResult.getCursor()); setCursorItem(clickResult.getCursor());
if (!clickResult.isCancel()) if (!clickResult.isCancel())
callClickEvent(player, null, slot, ClickType.RIGHT_CLICK, clicked, cursor); callClickEvent(player, null, convertedSlot, ClickType.RIGHT_CLICK, clicked, cursor);
return !clickResult.isCancel(); return !clickResult.isCancel();
} }

View File

@ -99,13 +99,15 @@ public class BlockPlacementListener {
blockPosition.add(offsetX, offsetY, offsetZ); blockPosition.add(offsetX, offsetY, offsetZ);
if (!canPlaceBlock) { if (!canPlaceBlock) {
//Send a block change with AIR as block to keep the client in sync, if (useMaterial.isBlock()) {
//using refreshChunk results in the client not being in sync //Send a block change with AIR as block to keep the client in sync,
//after rapid invalid block placements //using refreshChunk results in the client not being in sync
BlockChangePacket blockChangePacket = new BlockChangePacket(); //after rapid invalid block placements
blockChangePacket.blockPosition = blockPosition; BlockChangePacket blockChangePacket = new BlockChangePacket();
blockChangePacket.blockStateId = Block.AIR.getBlockId(); blockChangePacket.blockPosition = blockPosition;
player.getPlayerConnection().sendPacket(blockChangePacket); blockChangePacket.blockStateId = Block.AIR.getBlockId();
player.getPlayerConnection().sendPacket(blockChangePacket);
}
return; return;
} }

View File

@ -76,8 +76,8 @@ public final class BenchmarkManager {
stop = false; stop = false;
}, MinecraftServer.THREAD_NAME_BENCHMARK, 0L); }, MinecraftServer.THREAD_NAME_BENCHMARK);
thread.setDaemon(true);
thread.start(); thread.start();
this.enabled = true; this.enabled = true;

View File

@ -13,8 +13,6 @@ import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.traffic.GlobalChannelTrafficShapingHandler;
import io.netty.handler.traffic.TrafficCounter;
import io.netty.incubator.channel.uring.IOUring; import io.netty.incubator.channel.uring.IOUring;
import io.netty.incubator.channel.uring.IOUringEventLoopGroup; import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
import io.netty.incubator.channel.uring.IOUringServerSocketChannel; import io.netty.incubator.channel.uring.IOUringServerSocketChannel;
@ -22,7 +20,6 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.network.PacketProcessor; import net.minestom.server.network.PacketProcessor;
import net.minestom.server.network.netty.channel.ClientChannel; import net.minestom.server.network.netty.channel.ClientChannel;
import net.minestom.server.network.netty.codec.*; import net.minestom.server.network.netty.codec.*;
import net.minestom.server.ping.ResponseDataConsumer;
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;
@ -30,22 +27,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public final class NettyServer { public final class NettyServer {
public static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class); public static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class);
public static final int BUFFER_SIZE = Integer.getInteger("minestom.channel-buffer-size", 65535);
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20, private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20,
1 << 21); 1 << 21);
private static final long DEFAULT_COMPRESSED_CHANNEL_WRITE_LIMIT = 600_000L;
private static final long DEFAULT_COMPRESSED_CHANNEL_READ_LIMIT = 100_000L;
private static final long DEFAULT_UNCOMPRESSED_CHANNEL_WRITE_LIMIT = 15_000_000L;
private static final long DEFAULT_UNCOMPRESSED_CHANNEL_READ_LIMIT = 1_000_000L;
public static final String TRAFFIC_LIMITER_HANDLER_NAME = "traffic-limiter"; // Read/write
public static final String LEGACY_PING_HANDLER_NAME = "legacy-ping"; // Read public static final String LEGACY_PING_HANDLER_NAME = "legacy-ping"; // Read
public static final String ENCRYPT_HANDLER_NAME = "encrypt"; // Write public static final String ENCRYPT_HANDLER_NAME = "encrypt"; // Write
@ -63,7 +53,6 @@ public final class NettyServer {
private boolean initialized = false; private boolean initialized = false;
private final PacketProcessor packetProcessor; private final PacketProcessor packetProcessor;
private final GlobalChannelTrafficShapingHandler globalTrafficHandler;
private EventLoopGroup boss, worker; private EventLoopGroup boss, worker;
private ServerBootstrap bootstrap; private ServerBootstrap bootstrap;
@ -73,27 +62,14 @@ public final class NettyServer {
private String address; private String address;
private int port; private int port;
/**
* Scheduler used by {@code globalTrafficHandler}.
*/
private final ScheduledExecutorService trafficScheduler = Executors.newScheduledThreadPool(1);
public NettyServer(@NotNull PacketProcessor packetProcessor) { public NettyServer(@NotNull PacketProcessor packetProcessor) {
this.packetProcessor = packetProcessor; this.packetProcessor = packetProcessor;
this.globalTrafficHandler = new GlobalChannelTrafficShapingHandler(trafficScheduler, 1000) {
@Override
protected void doAccounting(TrafficCounter counter) {
// TODO proper monitoring API
//System.out.println("data " + counter.getRealWriteThroughput() / 1e6);
}
};
} }
/** /**
* Inits the server by choosing which transport layer to use, number of threads, pipeline order, etc... * Inits the server by choosing which transport layer to use, number of threads, pipeline order, etc...
* <p> * <p>
* Called just before {@link #start(String, int)} in {@link MinecraftServer#start(String, int, ResponseDataConsumer)}. * Called just before {@link #start(String, int)}.
*/ */
public void init() { public void init() {
Check.stateCondition(initialized, "Netty server has already been initialized!"); Check.stateCondition(initialized, "Netty server has already been initialized!");
@ -146,14 +122,11 @@ public final class NettyServer {
ChannelConfig config = ch.config(); ChannelConfig config = ch.config();
config.setOption(ChannelOption.TCP_NODELAY, true); config.setOption(ChannelOption.TCP_NODELAY, true);
config.setOption(ChannelOption.SO_KEEPALIVE, true); config.setOption(ChannelOption.SO_KEEPALIVE, true);
config.setOption(ChannelOption.SO_SNDBUF, 262_144); config.setOption(ChannelOption.SO_SNDBUF, BUFFER_SIZE);
config.setAllocator(ByteBufAllocator.DEFAULT); config.setAllocator(ByteBufAllocator.DEFAULT);
ChannelPipeline pipeline = ch.pipeline(); ChannelPipeline pipeline = ch.pipeline();
// TODO enable when properly implemented (dynamic limit based on the number of clients)
//pipeline.addLast(TRAFFIC_LIMITER_HANDLER_NAME, globalTrafficHandler);
// First check should verify if the packet is a legacy ping (from 1.6 version and earlier) // First check should verify if the packet is a legacy ping (from 1.6 version and earlier)
// Removed from the pipeline later in LegacyPingHandler if unnecessary (>1.6) // Removed from the pipeline later in LegacyPingHandler if unnecessary (>1.6)
pipeline.addLast(LEGACY_PING_HANDLER_NAME, new LegacyPingHandler()); pipeline.addLast(LEGACY_PING_HANDLER_NAME, new LegacyPingHandler());
@ -185,18 +158,6 @@ public final class NettyServer {
this.address = address; this.address = address;
this.port = port; this.port = port;
// Setup traffic limiter
{
final boolean compression = MinecraftServer.getCompressionThreshold() != 0;
if (compression) {
this.globalTrafficHandler.setWriteChannelLimit(DEFAULT_COMPRESSED_CHANNEL_WRITE_LIMIT);
this.globalTrafficHandler.setReadChannelLimit(DEFAULT_COMPRESSED_CHANNEL_READ_LIMIT);
} else {
this.globalTrafficHandler.setWriteChannelLimit(DEFAULT_UNCOMPRESSED_CHANNEL_WRITE_LIMIT);
this.globalTrafficHandler.setReadChannelLimit(DEFAULT_UNCOMPRESSED_CHANNEL_READ_LIMIT);
}
}
// Bind address // Bind address
try { try {
ChannelFuture cf = bootstrap.bind(new InetSocketAddress(address, port)).sync(); ChannelFuture cf = bootstrap.bind(new InetSocketAddress(address, port)).sync();
@ -231,25 +192,15 @@ public final class NettyServer {
} }
/** /**
* Gets the traffic handler, used to control channel and global bandwidth. * Stops the server.
* <p>
* The object can be modified as specified by Netty documentation.
*
* @return the global traffic handler
*/
@NotNull
public GlobalChannelTrafficShapingHandler getGlobalTrafficHandler() {
return globalTrafficHandler;
}
/**
* Stops the server and the various services.
*/ */
public void stop() { public void stop() {
this.worker.shutdownGracefully(); try {
this.boss.shutdownGracefully(); this.boss.shutdownGracefully().sync();
this.worker.shutdownGracefully().sync();
this.trafficScheduler.shutdown(); this.serverChannel.closeFuture().sync();
this.globalTrafficHandler.release(); } catch (InterruptedException e) {
e.printStackTrace();
}
} }
} }

View File

@ -1,6 +1,7 @@
package net.minestom.server.network.packet.server.play; package net.minestom.server.network.packet.server.play;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
@ -13,7 +14,6 @@ import net.minestom.server.instance.palette.Section;
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.BlockPosition; import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.BufUtils;
import net.minestom.server.utils.Utils; import net.minestom.server.utils.Utils;
import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryReader;
import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.binary.BinaryWriter;
@ -82,7 +82,7 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
writer.writeBoolean(fullChunk); writer.writeBoolean(fullChunk);
int mask = 0; int mask = 0;
ByteBuf blocks = BufUtils.getBuffer(MAX_BUFFER_SIZE); ByteBuf blocks = Unpooled.buffer(MAX_BUFFER_SIZE);
for (byte i = 0; i < CHUNK_SECTION_COUNT; i++) { for (byte i = 0; i < CHUNK_SECTION_COUNT; i++) {
if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) { if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) {
final Section section = paletteStorage.getSections()[i]; final Section section = paletteStorage.getSections()[i];

View File

@ -62,7 +62,7 @@ public class NettyPlayerConnection extends PlayerConnection {
private PlayerSkin bungeeSkin; private PlayerSkin bungeeSkin;
private final Object tickBufferLock = new Object(); private final Object tickBufferLock = new Object();
private volatile ByteBuf tickBuffer = BufUtils.getBuffer(true); private volatile ByteBuf tickBuffer = BufUtils.direct();
public NettyPlayerConnection(@NotNull SocketChannel channel) { public NettyPlayerConnection(@NotNull SocketChannel channel) {
super(); super();

View File

@ -22,26 +22,31 @@ public class MinestomTerminal {
@ApiStatus.Internal @ApiStatus.Internal
public static void start() { public static void start() {
try { final Thread thread = new Thread(null, () -> {
terminal = TerminalBuilder.terminal();
} catch (IOException e) {
e.printStackTrace();
}
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.build();
running = true;
while (running) {
String command;
try { try {
command = reader.readLine(PROMPT); terminal = TerminalBuilder.terminal();
COMMAND_MANAGER.execute(COMMAND_MANAGER.getConsoleSender(), command); } catch (IOException e) {
} catch (UserInterruptException e) { e.printStackTrace();
// Ignore
} catch (EndOfFileException e) {
return;
} }
} LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.build();
running = true;
while (running) {
String command;
try {
command = reader.readLine(PROMPT);
COMMAND_MANAGER.execute(COMMAND_MANAGER.getConsoleSender(), command);
} catch (UserInterruptException e) {
// Ignore
} catch (EndOfFileException e) {
return;
}
}
}, "Jline");
thread.setDaemon(true);
thread.start();
} }
@ApiStatus.Internal @ApiStatus.Internal

View File

@ -4,8 +4,6 @@ import net.minestom.server.MinecraftServer;
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 java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
@ -50,10 +48,7 @@ public class TickThread extends Thread {
private volatile boolean stop; private volatile boolean stop;
private TickThread tickThread; private TickThread tickThread;
private volatile boolean inTick; private final AtomicReference<TickContext> tickContext = new AtomicReference<>();
private final AtomicReference<CountDownLatch> countDownLatch = new AtomicReference<>();
private final Queue<Runnable> queue = new ConcurrentLinkedQueue<>();
@Override @Override
public void run() { public void run() {
@ -62,42 +57,38 @@ public class TickThread extends Thread {
LockSupport.park(tickThread); LockSupport.park(tickThread);
if (stop) if (stop)
break; break;
CountDownLatch localCountDownLatch = this.countDownLatch.get(); TickContext localContext = this.tickContext.get();
// The context is necessary to control the tick rates
// The latch is necessary to control the tick rates if (localContext == null) {
if (localCountDownLatch == null) {
continue; continue;
} }
this.inTick = true; // Execute tick
localContext.runnable.run();
// Execute all pending runnable localContext.countDownLatch.countDown();
Runnable runnable; this.tickContext.compareAndSet(localContext, null);
while ((runnable = queue.poll()) != null) {
runnable.run();
}
localCountDownLatch.countDown();
this.countDownLatch.compareAndSet(localCountDownLatch, null);
// Wait for the next notify (game tick)
this.inTick = false;
} }
} }
public void startTick(@NotNull CountDownLatch countDownLatch, @NotNull Runnable runnable) { protected void startTick(@NotNull CountDownLatch countDownLatch, @NotNull Runnable runnable) {
this.countDownLatch.set(countDownLatch); this.tickContext.set(new TickContext(countDownLatch, runnable));
this.queue.add(runnable);
LockSupport.unpark(tickThread); LockSupport.unpark(tickThread);
} }
public boolean isInTick() {
return inTick;
}
private void setLinkedThread(TickThread tickThread) { private void setLinkedThread(TickThread tickThread) {
this.tickThread = tickThread; this.tickThread = tickThread;
} }
} }
private static class TickContext {
private final CountDownLatch countDownLatch;
private final Runnable runnable;
private TickContext(@NotNull CountDownLatch countDownLatch, @NotNull Runnable runnable) {
this.countDownLatch = countDownLatch;
this.runnable = runnable;
}
}
} }

View File

@ -7,28 +7,7 @@ public class BufUtils {
private static final PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT; private static final PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
public static ByteBuf getBuffer() { public static ByteBuf direct() {
return alloc.heapBuffer(); return alloc.ioBuffer();
} }
public static ByteBuf getBuffer(boolean io) {
return io ? alloc.ioBuffer() : alloc.heapBuffer();
}
public static ByteBuf getBuffer(int initialCapacity) {
return alloc.heapBuffer(initialCapacity);
}
public static ByteBuf getBuffer(boolean io, int initialCapacity) {
return io ? alloc.ioBuffer(initialCapacity) : alloc.heapBuffer(initialCapacity);
}
public static ByteBuf getBuffer(int initialCapacity, int maxCapacity) {
return alloc.heapBuffer(initialCapacity, maxCapacity);
}
public static ByteBuf getBuffer(boolean io, int initialCapacity, int maxCapacity) {
return io ? alloc.ioBuffer(initialCapacity, maxCapacity) : alloc.heapBuffer(initialCapacity, maxCapacity);
}
} }

View File

@ -3,7 +3,6 @@ package net.minestom.server.utils;
import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.audience.ForwardingAudience;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
@ -21,7 +20,6 @@ import net.minestom.server.utils.callback.validator.PlayerValidator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.util.Collection; import java.util.Collection;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
@ -35,7 +33,6 @@ public final class PacketUtils {
private static final ThreadLocal<VelocityCompressor> COMPRESSOR = ThreadLocal.withInitial(() -> Natives.compress.get().create(4)); private static final ThreadLocal<VelocityCompressor> COMPRESSOR = ThreadLocal.withInitial(() -> Natives.compress.get().create(4));
private PacketUtils() { private PacketUtils() {
} }
/** /**
@ -54,7 +51,7 @@ public final class PacketUtils {
* </ol> * </ol>
* *
* @param audience the audience * @param audience the audience
* @param packet the packet * @param packet the packet
*/ */
@SuppressWarnings("OverrideOnly") // we need to access the audiences inside ForwardingAudience @SuppressWarnings("OverrideOnly") // we need to access the audiences inside ForwardingAudience
public static void sendPacket(@NotNull Audience audience, @NotNull ServerPacket packet) { public static void sendPacket(@NotNull Audience audience, @NotNull ServerPacket packet) {
@ -96,7 +93,7 @@ public final class PacketUtils {
// Send grouped packet... // Send grouped packet...
final boolean success = PACKET_LISTENER_MANAGER.processServerPacket(packet, players); final boolean success = PACKET_LISTENER_MANAGER.processServerPacket(packet, players);
if (success) { if (success) {
final ByteBuf finalBuffer = createFramedPacket(packet, true); final ByteBuf finalBuffer = createFramedPacket(packet);
final FramedPacket framedPacket = new FramedPacket(finalBuffer); final FramedPacket framedPacket = new FramedPacket(finalBuffer);
// Send packet to all players // Send packet to all players
@ -282,14 +279,10 @@ public final class PacketUtils {
* <p> * <p>
* Can be used if you want to store a raw buffer and send it later without the additional writing cost. * Can be used if you want to store a raw buffer and send it later without the additional writing cost.
* Compression is applied if {@link MinecraftServer#getCompressionThreshold()} is greater than 0. * Compression is applied if {@link MinecraftServer#getCompressionThreshold()} is greater than 0.
*
* @param serverPacket the server packet to write
*/ */
@NotNull public static @NotNull ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket) {
public static ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket, boolean directBuffer) { ByteBuf packetBuf = BufUtils.direct();
ByteBuf packetBuf = directBuffer ? BufUtils.getBuffer(true) : Unpooled.buffer();
writeFramedPacket(packetBuf, serverPacket); writeFramedPacket(packetBuf, serverPacket);
return packetBuf; return packetBuf;
} }
} }

View File

@ -25,42 +25,34 @@ public final class Utils {
? 4 : 5; ? 4 : 5;
} }
public static void writeVarIntBuf(ByteBuf buffer, int value) { public static void writeVarIntBuf(ByteBuf buf, int value) {
do { // Took from velocity
byte temp = (byte) (value & 0b01111111); if ((value & (0xFFFFFFFF << 7)) == 0) {
value >>>= 7; buf.writeByte(value);
if (value != 0) { } else if ((value & (0xFFFFFFFF << 14)) == 0) {
temp |= 0b10000000; int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
buf.writeShort(w);
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
buf.writeMedium(w);
} else {
// Naive loop
while (true) {
if ((value & 0xFFFFFF80) == 0) {
buf.writeByte(value);
return;
}
buf.writeByte(value & 0x7F | 0x80);
value >>>= 7;
} }
buffer.writeByte(temp); }
} while (value != 0);
}
public static void writeVarInt(BinaryWriter writer, int value) {
do {
byte temp = (byte) (value & 0b01111111);
value >>>= 7;
if (value != 0) {
temp |= 0b10000000;
}
writer.writeByte(temp);
} while (value != 0);
} }
public static void overrideVarIntHeader(@NotNull ByteBuf buffer, int startIndex, int value) { public static void overrideVarIntHeader(@NotNull ByteBuf buffer, int startIndex, int value) {
final int indexCache = buffer.writerIndex(); final int indexCache = buffer.writerIndex();
buffer.writerIndex(startIndex); buffer.writerIndex(startIndex);
final int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
for (int i = 0; i < VARINT_HEADER_SIZE; i++) { buffer.writeMedium(w);
byte temp = (byte) (value & 0b01111111);
value >>>= 7;
if (value != 0 || i != VARINT_HEADER_SIZE - 1) {
temp |= 0b10000000;
}
buffer.writeByte(temp);
}
buffer.writerIndex(indexCache); buffer.writerIndex(indexCache);
} }

View File

@ -152,7 +152,7 @@ public class BinaryWriter extends OutputStream {
* @param i the int to write * @param i the int to write
*/ */
public void writeVarInt(int i) { public void writeVarInt(int i) {
Utils.writeVarInt(this, i); Utils.writeVarIntBuf(buffer, i);
} }
/** /**

View File

@ -63,7 +63,7 @@ public interface CacheablePacket {
if (shouldUpdate) { if (shouldUpdate) {
// Buffer freed by guava cache #removalListener // Buffer freed by guava cache #removalListener
final ByteBuf buffer = PacketUtils.createFramedPacket(serverPacket, true); final ByteBuf buffer = PacketUtils.createFramedPacket(serverPacket);
timedBuffer = new TimedBuffer(buffer, timestamp); timedBuffer = new TimedBuffer(buffer, timestamp);
temporaryCache.cache(identifier, timedBuffer); temporaryCache.cache(identifier, timedBuffer);
} }

View File

@ -11,7 +11,7 @@ public class TestCommand extends Command {
super("testcmd"); super("testcmd");
setDefaultExecutor(this::usage); setDefaultExecutor(this::usage);
addSyntax((sender, context) -> System.out.println("executed"), "test get"); addSyntax((sender, context) -> System.out.println("executed"), "test Get integer<number>");
} }
private void usage(CommandSender sender, CommandContext context) { private void usage(CommandSender sender, CommandContext context) {