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 'maven-publish'
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'
}

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_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 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 int THREAD_COUNT_PARALLEL_CHUNK_SAVING = 4;
public static final int THREAD_COUNT_PARALLEL_CHUNK_SAVING = getThreadCount("minestom.save-thread-count", 2);
// Config
// Can be modified at performance cost when increased
@ -823,4 +825,8 @@ public final class MinecraftServer {
"You cannot access the manager before MinecraftServer#init, " +
"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.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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<>();
static {
ARGUMENT_FUNCTION_MAP.put("Literal", ArgumentLiteral::new);
ARGUMENT_FUNCTION_MAP.put("Boolean", ArgumentBoolean::new);
ARGUMENT_FUNCTION_MAP.put("Integer", ArgumentInteger::new);
ARGUMENT_FUNCTION_MAP.put("Double", ArgumentDouble::new);
ARGUMENT_FUNCTION_MAP.put("Float", ArgumentFloat::new);
ARGUMENT_FUNCTION_MAP.put("String", ArgumentString::new);
ARGUMENT_FUNCTION_MAP.put("Word", ArgumentWord::new);
ARGUMENT_FUNCTION_MAP.put("StringArray", ArgumentStringArray::new);
ARGUMENT_FUNCTION_MAP.put("Command", ArgumentCommand::new);
ARGUMENT_FUNCTION_MAP.put("literal", ArgumentLiteral::new);
ARGUMENT_FUNCTION_MAP.put("boolean", ArgumentBoolean::new);
ARGUMENT_FUNCTION_MAP.put("integer", ArgumentInteger::new);
ARGUMENT_FUNCTION_MAP.put("double", ArgumentDouble::new);
ARGUMENT_FUNCTION_MAP.put("float", ArgumentFloat::new);
ARGUMENT_FUNCTION_MAP.put("string", ArgumentString::new);
ARGUMENT_FUNCTION_MAP.put("word", ArgumentWord::new);
ARGUMENT_FUNCTION_MAP.put("stringarray", ArgumentStringArray::new);
ARGUMENT_FUNCTION_MAP.put("command", ArgumentCommand::new);
// TODO enum
ARGUMENT_FUNCTION_MAP.put("Color", ArgumentColor::new);
ARGUMENT_FUNCTION_MAP.put("Time", ArgumentTime::new);
ARGUMENT_FUNCTION_MAP.put("Enchantment", ArgumentEnchantment::new);
ARGUMENT_FUNCTION_MAP.put("Particle", ArgumentParticle::new);
ARGUMENT_FUNCTION_MAP.put("ResourceLocation", ArgumentResourceLocation::new);
ARGUMENT_FUNCTION_MAP.put("Potion", ArgumentPotionEffect::new);
ARGUMENT_FUNCTION_MAP.put("EntityType", ArgumentEntityType::new);
ARGUMENT_FUNCTION_MAP.put("BlockState", ArgumentBlockState::new);
ARGUMENT_FUNCTION_MAP.put("IntRange", ArgumentIntRange::new);
ARGUMENT_FUNCTION_MAP.put("FloatRange", ArgumentFloatRange::new);
ARGUMENT_FUNCTION_MAP.put("color", ArgumentColor::new);
ARGUMENT_FUNCTION_MAP.put("time", ArgumentTime::new);
ARGUMENT_FUNCTION_MAP.put("enchantment", ArgumentEnchantment::new);
ARGUMENT_FUNCTION_MAP.put("particle", ArgumentParticle::new);
ARGUMENT_FUNCTION_MAP.put("resourceLocation", ArgumentResourceLocation::new);
ARGUMENT_FUNCTION_MAP.put("potion", ArgumentPotionEffect::new);
ARGUMENT_FUNCTION_MAP.put("entityType", ArgumentEntityType::new);
ARGUMENT_FUNCTION_MAP.put("blockState", ArgumentBlockState::new);
ARGUMENT_FUNCTION_MAP.put("intrange", ArgumentIntRange::new);
ARGUMENT_FUNCTION_MAP.put("floatrange", ArgumentFloatRange::new);
ARGUMENT_FUNCTION_MAP.put("Entity", s -> new ArgumentEntity(s).singleEntity(true));
ARGUMENT_FUNCTION_MAP.put("Entities", ArgumentEntity::new);
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("entity", s -> new ArgumentEntity(s).singleEntity(true));
ARGUMENT_FUNCTION_MAP.put("entities", ArgumentEntity::new);
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("ItemStack", ArgumentItemStack::new);
ARGUMENT_FUNCTION_MAP.put("Component", ArgumentComponent::new);
ARGUMENT_FUNCTION_MAP.put("UUID", ArgumentUUID::new);
ARGUMENT_FUNCTION_MAP.put("NBT", ArgumentNbtTag::new);
ARGUMENT_FUNCTION_MAP.put("NbtCompound", ArgumentNbtCompoundTag::new);
ARGUMENT_FUNCTION_MAP.put("RelativeBlockPosition", ArgumentRelativeBlockPosition::new);
ARGUMENT_FUNCTION_MAP.put("RelativeVec3", ArgumentRelativeVec3::new);
ARGUMENT_FUNCTION_MAP.put("RelativeVec2", ArgumentRelativeVec2::new);
ARGUMENT_FUNCTION_MAP.put("itemstack", ArgumentItemStack::new);
ARGUMENT_FUNCTION_MAP.put("component", ArgumentComponent::new);
ARGUMENT_FUNCTION_MAP.put("uuid", ArgumentUUID::new);
ARGUMENT_FUNCTION_MAP.put("nbt", ArgumentNbtTag::new);
ARGUMENT_FUNCTION_MAP.put("nbtcompound", ArgumentNbtCompoundTag::new);
ARGUMENT_FUNCTION_MAP.put("relativeblockposition", ArgumentRelativeBlockPosition::new);
ARGUMENT_FUNCTION_MAP.put("relativevec3", ArgumentRelativeVec3::new);
ARGUMENT_FUNCTION_MAP.put("relativevec2", ArgumentRelativeVec2::new);
}
@Beta
@NotNull
public static Argument<?>[] generate(@NotNull String format) {
public static @NotNull Argument<?>[] generate(@NotNull String format) {
List<Argument<?>> result = new ArrayList<>();
// 0 = no state
@ -92,7 +92,7 @@ public class ArgumentParser {
} else if (c == '<') {
// Retrieve argument type
final String argument = builder.toString();
argumentFunction = ARGUMENT_FUNCTION_MAP.get(argument);
argumentFunction = ARGUMENT_FUNCTION_MAP.get(argument.toLowerCase(Locale.ROOT));
if (argumentFunction == null) {
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.chunkX = getChunkX();
updateLightPacket.chunkZ = getChunkZ();
updateLightPacket.skyLightMask = 0x3FFF0;
updateLightPacket.blockLightMask = 0x3F;
updateLightPacket.emptySkyLightMask = 0x0F;
updateLightPacket.emptyBlockLightMask = 0x3FFC0;
updateLightPacket.skyLightMask = 0b111111111111111111;
updateLightPacket.emptySkyLightMask = 0b000000000000000000;
updateLightPacket.blockLightMask = 0b000000000000000000;
updateLightPacket.emptyBlockLightMask = 0b111111111111111111;
byte[] bytes = new byte[2048];
Arrays.fill(bytes, (byte) 0xFF);
List<byte[]> temp = new ArrayList<>(14);
List<byte[]> temp2 = new ArrayList<>(6);
for (int i = 0; i < 14; ++i) {
final List<byte[]> temp = new ArrayList<>(18);
for (int i = 0; i < 18; ++i) {
temp.add(bytes);
}
for (int i = 0; i < 6; ++i) {
temp2.add(bytes);
}
updateLightPacket.skyLight = temp;
updateLightPacket.blockLight = temp2;
updateLightPacket.blockLight = new ArrayList<>(0);
return updateLightPacket;
}

View File

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

View File

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

View File

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

View File

@ -76,8 +76,8 @@ public final class BenchmarkManager {
stop = false;
}, MinecraftServer.THREAD_NAME_BENCHMARK, 0L);
}, MinecraftServer.THREAD_NAME_BENCHMARK);
thread.setDaemon(true);
thread.start();
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.SocketChannel;
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.IOUringEventLoopGroup;
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.netty.channel.ClientChannel;
import net.minestom.server.network.netty.codec.*;
import net.minestom.server.ping.ResponseDataConsumer;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -30,22 +27,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public final class NettyServer {
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,
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 ENCRYPT_HANDLER_NAME = "encrypt"; // Write
@ -63,7 +53,6 @@ public final class NettyServer {
private boolean initialized = false;
private final PacketProcessor packetProcessor;
private final GlobalChannelTrafficShapingHandler globalTrafficHandler;
private EventLoopGroup boss, worker;
private ServerBootstrap bootstrap;
@ -73,27 +62,14 @@ public final class NettyServer {
private String address;
private int port;
/**
* Scheduler used by {@code globalTrafficHandler}.
*/
private final ScheduledExecutorService trafficScheduler = Executors.newScheduledThreadPool(1);
public NettyServer(@NotNull 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...
* <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() {
Check.stateCondition(initialized, "Netty server has already been initialized!");
@ -146,14 +122,11 @@ public final class NettyServer {
ChannelConfig config = ch.config();
config.setOption(ChannelOption.TCP_NODELAY, 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);
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)
// Removed from the pipeline later in LegacyPingHandler if unnecessary (>1.6)
pipeline.addLast(LEGACY_PING_HANDLER_NAME, new LegacyPingHandler());
@ -185,18 +158,6 @@ public final class NettyServer {
this.address = address;
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
try {
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.
* <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.
* Stops the server.
*/
public void stop() {
this.worker.shutdownGracefully();
this.boss.shutdownGracefully();
this.trafficScheduler.shutdown();
this.globalTrafficHandler.release();
try {
this.boss.shutdownGracefully().sync();
this.worker.shutdownGracefully().sync();
this.serverChannel.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

View File

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

View File

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

View File

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

View File

@ -4,8 +4,6 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
@ -50,10 +48,7 @@ public class TickThread extends Thread {
private volatile boolean stop;
private TickThread tickThread;
private volatile boolean inTick;
private final AtomicReference<CountDownLatch> countDownLatch = new AtomicReference<>();
private final Queue<Runnable> queue = new ConcurrentLinkedQueue<>();
private final AtomicReference<TickContext> tickContext = new AtomicReference<>();
@Override
public void run() {
@ -62,42 +57,38 @@ public class TickThread extends Thread {
LockSupport.park(tickThread);
if (stop)
break;
CountDownLatch localCountDownLatch = this.countDownLatch.get();
// The latch is necessary to control the tick rates
if (localCountDownLatch == null) {
TickContext localContext = this.tickContext.get();
// The context is necessary to control the tick rates
if (localContext == null) {
continue;
}
this.inTick = true;
// Execute tick
localContext.runnable.run();
// Execute all pending runnable
Runnable runnable;
while ((runnable = queue.poll()) != null) {
runnable.run();
}
localCountDownLatch.countDown();
this.countDownLatch.compareAndSet(localCountDownLatch, null);
// Wait for the next notify (game tick)
this.inTick = false;
localContext.countDownLatch.countDown();
this.tickContext.compareAndSet(localContext, null);
}
}
public void startTick(@NotNull CountDownLatch countDownLatch, @NotNull Runnable runnable) {
this.countDownLatch.set(countDownLatch);
this.queue.add(runnable);
protected void startTick(@NotNull CountDownLatch countDownLatch, @NotNull Runnable runnable) {
this.tickContext.set(new TickContext(countDownLatch, runnable));
LockSupport.unpark(tickThread);
}
public boolean isInTick() {
return inTick;
}
private void setLinkedThread(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;
public static ByteBuf getBuffer() {
return alloc.heapBuffer();
public static ByteBuf direct() {
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.util.Natives;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience;
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.Nullable;
import java.time.Duration;
import java.util.Collection;
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 PacketUtils() {
}
/**
@ -54,7 +51,7 @@ public final class PacketUtils {
* </ol>
*
* @param audience the audience
* @param packet the packet
* @param packet the packet
*/
@SuppressWarnings("OverrideOnly") // we need to access the audiences inside ForwardingAudience
public static void sendPacket(@NotNull Audience audience, @NotNull ServerPacket packet) {
@ -96,7 +93,7 @@ public final class PacketUtils {
// Send grouped packet...
final boolean success = PACKET_LISTENER_MANAGER.processServerPacket(packet, players);
if (success) {
final ByteBuf finalBuffer = createFramedPacket(packet, true);
final ByteBuf finalBuffer = createFramedPacket(packet);
final FramedPacket framedPacket = new FramedPacket(finalBuffer);
// Send packet to all players
@ -282,14 +279,10 @@ public final class PacketUtils {
* <p>
* 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.
*
* @param serverPacket the server packet to write
*/
@NotNull
public static ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket, boolean directBuffer) {
ByteBuf packetBuf = directBuffer ? BufUtils.getBuffer(true) : Unpooled.buffer();
public static @NotNull ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket) {
ByteBuf packetBuf = BufUtils.direct();
writeFramedPacket(packetBuf, serverPacket);
return packetBuf;
}
}

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ public class TestCommand extends Command {
super("testcmd");
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) {