Merge branch 'Minestom:master' into instance-based-threading

This commit is contained in:
Leon 2025-01-15 18:52:51 +00:00 committed by GitHub
commit 4f00d7a2cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 281 additions and 112 deletions

View File

@ -46,6 +46,7 @@ import net.minestom.server.timer.Schedulable;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.PacketViewableUtils;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.block.BlockIterator;
@ -79,6 +80,10 @@ import java.util.function.UnaryOperator;
*/
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, Taggable,
HoverEventSource<ShowEntity>, Sound.Emitter, Shape, AcquirableSource<Entity> {
// This is somewhat arbitrary, but we don't want to hit the max int ever because it is very easy to
// overflow while working with a position at the max int (for example, looping over a bounding box)
private static final int MAX_COORDINATE = 2_000_000_000;
private static final AtomicInteger LAST_ENTITY_ID = new AtomicInteger();
// Certain entities should only have their position packets sent during synchronization
@ -96,7 +101,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
protected Instance instance;
protected Chunk currentChunk;
protected Pos position;
protected Pos position; // Should be updated by setPositionInternal only.
protected Pos previousPosition;
protected Pos lastSyncedPosition;
protected boolean onGround;
@ -202,6 +207,19 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
this(entityType, UUID.randomUUID());
}
protected void setPositionInternal(@NotNull Pos newPosition) {
if (newPosition.x() >= MAX_COORDINATE || newPosition.x() <= -MAX_COORDINATE ||
newPosition.y() >= MAX_COORDINATE || newPosition.y() <= -MAX_COORDINATE ||
newPosition.z() >= MAX_COORDINATE || newPosition.z() <= -MAX_COORDINATE) {
newPosition = newPosition.withCoord(
MathUtils.clamp(newPosition.x(), -MAX_COORDINATE, MAX_COORDINATE),
MathUtils.clamp(newPosition.y(), -MAX_COORDINATE, MAX_COORDINATE),
MathUtils.clamp(newPosition.z(), -MAX_COORDINATE, MAX_COORDINATE)
);
}
this.position = newPosition;
}
/**
* Schedules a task to be run during the next entity tick.
*
@ -325,7 +343,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final Runnable endCallback = () -> {
this.previousPosition = this.position;
this.position = globalPosition;
setPositionInternal(globalPosition);
this.velocity = globalVelocity;
refreshCoordinate(globalPosition);
if (this instanceof Player player) player.synchronizePositionAfterTeleport(position, velocity, flags, shouldConfirm);
@ -356,7 +374,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
public void setView(float yaw, float pitch) {
final Pos currentPosition = this.position;
if (currentPosition.sameView(yaw, pitch)) return;
this.position = currentPosition.withView(yaw, pitch);
setPositionInternal(currentPosition.withView(yaw, pitch));
synchronizeView();
}
@ -785,7 +803,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
if (previousInstance != null) removeFromInstance(previousInstance);
this.isActive = true;
this.position = spawnPosition;
setPositionInternal(spawnPosition);
this.previousPosition = spawnPosition;
this.lastSyncedPosition = spawnPosition;
this.previousPhysicsResult = null;
@ -1239,7 +1257,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final var previousPosition = this.position;
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
if (position.equals(lastSyncedPosition)) return;
this.position = position;
setPositionInternal(position);
this.previousPosition = previousPosition;
if (!position.samePoint(previousPosition)) refreshCoordinate(position);
if (nextSynchronizationTick <= ticks + 1 || !sendPackets) {
@ -1300,7 +1318,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final Pos newPassengerPos = oldPassengerPos.withCoord(newPosition.x(),
newPosition.y() + EntityUtils.getPassengerHeightOffset(this, passenger),
newPosition.z());
passenger.position = newPassengerPos;
passenger.setPositionInternal(newPassengerPos);
passenger.previousPosition = oldPassengerPos;
passenger.refreshCoordinate(newPassengerPos);
}
@ -1477,7 +1495,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
this.removed = true;
if (!permanent) {
// Reset some state to be ready for re-use
this.position = Pos.ZERO;
setPositionInternal(Pos.ZERO);
this.previousPosition = Pos.ZERO;
this.lastSyncedPosition = Pos.ZERO;
}

View File

@ -115,6 +115,7 @@ public interface Palette {
if (bitsPerEntry == 0) {
// Single valued 0-0
final int value = buffer.read(VAR_INT);
buffer.read(VAR_INT); // Skip size
return new PaletteSingle((byte) dimension, value);
} else if (bitsPerEntry >= minIndirect && bitsPerEntry <= maxIndirect) {
// Indirect palette

View File

@ -45,33 +45,33 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
private void writeInnerComponent(@NotNull NetworkBuffer buffer, @NotNull Component component) {
buffer.write(BYTE, TAG_STRING); // Start first tag (always the type)
writeUtf(buffer, "type");
buffer.write(STRING_IO_UTF8, "type");
switch (component) {
case TextComponent text -> {
writeUtf(buffer, "text");
buffer.write(STRING_IO_UTF8, "text");
buffer.write(BYTE, TAG_STRING); // Start "text" tag
writeUtf(buffer, "text");
writeUtf(buffer, text.content());
buffer.write(STRING_IO_UTF8, "text");
buffer.write(STRING_IO_UTF8, text.content());
}
case TranslatableComponent translatable -> {
writeUtf(buffer, "translatable");
buffer.write(STRING_IO_UTF8, "translatable");
buffer.write(BYTE, TAG_STRING); // Start "translate" tag
writeUtf(buffer, "translate");
writeUtf(buffer, translatable.key());
buffer.write(STRING_IO_UTF8, "translate");
buffer.write(STRING_IO_UTF8, translatable.key());
final String fallback = translatable.fallback();
if (fallback != null) {
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "fallback");
writeUtf(buffer, fallback);
buffer.write(STRING_IO_UTF8, "fallback");
buffer.write(STRING_IO_UTF8, fallback);
}
final List<TranslationArgument> args = translatable.arguments();
if (!args.isEmpty()) {
buffer.write(BYTE, TAG_LIST);
writeUtf(buffer, "with");
buffer.write(STRING_IO_UTF8, "with");
buffer.write(BYTE, TAG_COMPOUND); // List type
buffer.write(INT, args.size());
for (final TranslationArgument arg : args)
@ -79,42 +79,42 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
}
}
case ScoreComponent score -> {
writeUtf(buffer, "score");
buffer.write(STRING_IO_UTF8, "score");
buffer.write(BYTE, TAG_COMPOUND); // Start "score" tag
writeUtf(buffer, "score");
buffer.write(STRING_IO_UTF8, "score");
{
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "name");
writeUtf(buffer, score.name());
buffer.write(STRING_IO_UTF8, "name");
buffer.write(STRING_IO_UTF8, score.name());
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "objective");
writeUtf(buffer, score.objective());
buffer.write(STRING_IO_UTF8, "objective");
buffer.write(STRING_IO_UTF8, score.objective());
}
buffer.write(BYTE, TAG_END); // End "score" tag
}
case SelectorComponent selector -> {
writeUtf(buffer, "selector");
buffer.write(STRING_IO_UTF8, "selector");
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "selector");
writeUtf(buffer, selector.pattern());
buffer.write(STRING_IO_UTF8, "selector");
buffer.write(STRING_IO_UTF8, selector.pattern());
final Component separator = selector.separator();
if (separator != null) {
buffer.write(BYTE, TAG_COMPOUND);
writeUtf(buffer, "separator");
buffer.write(STRING_IO_UTF8, "separator");
writeInnerComponent(buffer, separator);
}
}
case KeybindComponent keybind -> {
writeUtf(buffer, "keybind");
buffer.write(STRING_IO_UTF8, "keybind");
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "keybind");
writeUtf(buffer, keybind.keybind());
buffer.write(STRING_IO_UTF8, "keybind");
buffer.write(STRING_IO_UTF8, keybind.keybind());
}
case NBTComponent<?, ?> nbt -> {
//todo
@ -126,7 +126,7 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
// Children
if (!component.children().isEmpty()) {
buffer.write(BYTE, TAG_LIST);
writeUtf(buffer, "extra");
buffer.write(STRING_IO_UTF8, "extra");
buffer.write(BYTE, TAG_COMPOUND); // List type
buffer.write(INT, component.children().size());
@ -144,59 +144,59 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
final TextColor color = style.color();
if (color != null) {
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "color");
buffer.write(STRING_IO_UTF8, "color");
if (color instanceof NamedTextColor namedColor)
writeUtf(buffer, namedColor.toString());
else writeUtf(buffer, color.asHexString());
buffer.write(STRING_IO_UTF8, namedColor.toString());
else buffer.write(STRING_IO_UTF8, color.asHexString());
}
final Key font = style.font();
if (font != null) {
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "font");
writeUtf(buffer, font.asString());
buffer.write(STRING_IO_UTF8, "font");
buffer.write(STRING_IO_UTF8, font.asString());
}
final TextDecoration.State bold = style.decoration(TextDecoration.BOLD);
if (bold != TextDecoration.State.NOT_SET) {
buffer.write(BYTE, TAG_BYTE);
writeUtf(buffer, "bold");
buffer.write(STRING_IO_UTF8, "bold");
buffer.write(BYTE, bold == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
}
final TextDecoration.State italic = style.decoration(TextDecoration.ITALIC);
if (italic != TextDecoration.State.NOT_SET) {
buffer.write(BYTE, TAG_BYTE);
writeUtf(buffer, "italic");
buffer.write(STRING_IO_UTF8, "italic");
buffer.write(BYTE, italic == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
}
final TextDecoration.State underlined = style.decoration(TextDecoration.UNDERLINED);
if (underlined != TextDecoration.State.NOT_SET) {
buffer.write(BYTE, TAG_BYTE);
writeUtf(buffer, "underlined");
buffer.write(STRING_IO_UTF8, "underlined");
buffer.write(BYTE, underlined == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
}
final TextDecoration.State strikethrough = style.decoration(TextDecoration.STRIKETHROUGH);
if (strikethrough != TextDecoration.State.NOT_SET) {
buffer.write(BYTE, TAG_BYTE);
writeUtf(buffer, "strikethrough");
buffer.write(STRING_IO_UTF8, "strikethrough");
buffer.write(BYTE, strikethrough == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
}
final TextDecoration.State obfuscated = style.decoration(TextDecoration.OBFUSCATED);
if (obfuscated != TextDecoration.State.NOT_SET) {
buffer.write(BYTE, TAG_BYTE);
writeUtf(buffer, "obfuscated");
buffer.write(STRING_IO_UTF8, "obfuscated");
buffer.write(BYTE, obfuscated == TextDecoration.State.TRUE ? (byte) 1 : (byte) 0);
}
final String insertion = style.insertion();
if (insertion != null) {
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "insertion");
writeUtf(buffer, insertion);
buffer.write(STRING_IO_UTF8, "insertion");
buffer.write(STRING_IO_UTF8, insertion);
}
final ClickEvent clickEvent = style.clickEvent();
@ -208,15 +208,15 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
private void writeClickEvent(@NotNull NetworkBuffer buffer, @NotNull ClickEvent clickEvent) {
buffer.write(BYTE, TAG_COMPOUND);
writeUtf(buffer, "clickEvent");
buffer.write(STRING_IO_UTF8, "clickEvent");
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "action");
writeUtf(buffer, clickEvent.action().name().toLowerCase(Locale.ROOT));
buffer.write(STRING_IO_UTF8, "action");
buffer.write(STRING_IO_UTF8, clickEvent.action().name().toLowerCase(Locale.ROOT));
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "value");
writeUtf(buffer, clickEvent.value());
buffer.write(STRING_IO_UTF8, "value");
buffer.write(STRING_IO_UTF8, clickEvent.value());
buffer.write(BYTE, TAG_END);
}
@ -224,29 +224,29 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
@SuppressWarnings("unchecked")
private void writeHoverEvent(@NotNull NetworkBuffer buffer, @NotNull HoverEvent<?> hoverEvent) {
buffer.write(BYTE, TAG_COMPOUND);
writeUtf(buffer, "hoverEvent");
buffer.write(STRING_IO_UTF8, "hoverEvent");
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "action");
writeUtf(buffer, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
buffer.write(STRING_IO_UTF8, "action");
buffer.write(STRING_IO_UTF8, hoverEvent.action().toString().toLowerCase(Locale.ROOT));
buffer.write(BYTE, TAG_COMPOUND); // Start contents tag
writeUtf(buffer, "contents");
buffer.write(STRING_IO_UTF8, "contents");
if (hoverEvent.action() == HoverEvent.Action.SHOW_TEXT) {
writeInnerComponent(buffer, (Component) hoverEvent.value());
} else if (hoverEvent.action() == HoverEvent.Action.SHOW_ITEM) {
var value = ((HoverEvent<HoverEvent.ShowItem>) hoverEvent).value();
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "id");
writeUtf(buffer, value.item().asString());
buffer.write(STRING_IO_UTF8, "id");
buffer.write(STRING_IO_UTF8, value.item().asString());
buffer.write(BYTE, TAG_INT);
writeUtf(buffer, "count");
buffer.write(STRING_IO_UTF8, "count");
buffer.write(INT, value.count());
buffer.write(BYTE, TAG_COMPOUND);
writeUtf(buffer, "components");
buffer.write(STRING_IO_UTF8, "components");
//todo item components
buffer.write(BYTE, TAG_END);
@ -257,17 +257,17 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
final Component name = value.name();
if (name != null) {
buffer.write(BYTE, TAG_COMPOUND);
writeUtf(buffer, "name");
buffer.write(STRING_IO_UTF8, "name");
writeInnerComponent(buffer, name);
}
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "type");
writeUtf(buffer, value.type().asString());
buffer.write(STRING_IO_UTF8, "type");
buffer.write(STRING_IO_UTF8, value.type().asString());
buffer.write(BYTE, TAG_STRING);
writeUtf(buffer, "id");
writeUtf(buffer, value.id().toString());
buffer.write(STRING_IO_UTF8, "id");
buffer.write(STRING_IO_UTF8, value.id().toString());
buffer.write(BYTE, TAG_END); // End contents tag
} else {
@ -276,53 +276,4 @@ record ComponentNetworkBufferTypeImpl() implements NetworkBufferTypeImpl<Compone
buffer.write(BYTE, TAG_END);
}
/**
* This is a very gross version of {@link java.io.DataOutputStream#writeUTF(String)}. We need the data in the java
* modified utf-8 format, and I couldnt find a method without creating a new buffer for it.
*
* @param buffer the buffer to write to
* @param str the string to write
*/
private static void writeUtf(@NotNull NetworkBuffer buffer, @NotNull String str) {
final int strlen = str.length();
int utflen = strlen; // optimized for ASCII
for (int i = 0; i < strlen; i++) {
int c = str.charAt(i);
if (c >= 0x80 || c == 0)
utflen += (c >= 0x800) ? 2 : 1;
}
if (utflen > 65535 || /* overflow */ utflen < strlen)
throw new RuntimeException("UTF-8 string too long");
buffer.write(SHORT, (short) utflen);
buffer.ensureWritable(utflen);
var impl = (NetworkBufferImpl) buffer;
int i;
for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII
int c = str.charAt(i);
if (c >= 0x80 || c == 0) break;
impl._putByte(buffer.writeIndex(), (byte) c);
impl.advanceWrite(1);
}
for (; i < strlen; i++) {
int c = str.charAt(i);
if (c < 0x80 && c != 0) {
impl._putByte(buffer.writeIndex(), (byte) c);
impl.advanceWrite(1);
} else if (c >= 0x800) {
impl._putByte(buffer.writeIndex(), (byte) (0xE0 | ((c >> 12) & 0x0F)));
impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 6) & 0x3F)));
impl._putByte(buffer.writeIndex() + 2, (byte) (0x80 | ((c >> 0) & 0x3F)));
impl.advanceWrite(3);
} else {
impl._putByte(buffer.writeIndex(), (byte) (0xC0 | ((c >> 6) & 0x1F)));
impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 0) & 0x3F)));
impl.advanceWrite(2);
}
}
}
}

View File

@ -45,6 +45,7 @@ public sealed interface NetworkBuffer permits NetworkBufferImpl {
Type<byte[]> RAW_BYTES = new NetworkBufferTypeImpl.RawBytesType(-1);
Type<String> STRING = new NetworkBufferTypeImpl.StringType();
Type<String> STRING_TERMINATED = new NetworkBufferTypeImpl.StringTerminatedType();
Type<String> STRING_IO_UTF8 = new NetworkBufferTypeImpl.IOUTF8StringType();
Type<BinaryTag> NBT = new NetworkBufferTypeImpl.NbtType();
@SuppressWarnings({"unchecked", "rawtypes"})
Type<CompoundBinaryTag> NBT_COMPOUND = (Type) new NetworkBufferTypeImpl.NbtType();

View File

@ -819,6 +819,123 @@ interface NetworkBufferTypeImpl<T> extends NetworkBuffer.Type<T> {
}
}
/**
* This is a very gross version of {@link java.io.DataOutputStream#writeUTF(String)} & ${@link DataInputStream#readUTF()}. We need the data in the java
* modified utf-8 format for Component, and I couldnt find a method without creating a new buffer for it.
*/
record IOUTF8StringType() implements NetworkBufferTypeImpl<String> {
@Override
public void write(@NotNull NetworkBuffer buffer, String value) {
final int strlen = value.length();
int utflen = strlen; // optimized for ASCII
for (int i = 0; i < strlen; i++) {
int c = value.charAt(i);
if (c >= 0x80 || c == 0)
utflen += (c >= 0x800) ? 2 : 1;
}
if (utflen > 65535 || /* overflow */ utflen < strlen)
throw new RuntimeException("UTF-8 string too long");
buffer.write(SHORT, (short) utflen);
buffer.ensureWritable(utflen);
var impl = (NetworkBufferImpl) buffer;
int i;
for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII
int c = value.charAt(i);
if (c >= 0x80 || c == 0) break;
impl._putByte(buffer.writeIndex(), (byte) c);
impl.advanceWrite(1);
}
for (; i < strlen; i++) {
int c = value.charAt(i);
if (c < 0x80 && c != 0) {
impl._putByte(buffer.writeIndex(), (byte) c);
impl.advanceWrite(1);
} else if (c >= 0x800) {
impl._putByte(buffer.writeIndex(), (byte) (0xE0 | ((c >> 12) & 0x0F)));
impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 6) & 0x3F)));
impl._putByte(buffer.writeIndex() + 2, (byte) (0x80 | ((c >> 0) & 0x3F)));
impl.advanceWrite(3);
} else {
impl._putByte(buffer.writeIndex(), (byte) (0xC0 | ((c >> 6) & 0x1F)));
impl._putByte(buffer.writeIndex() + 1, (byte) (0x80 | ((c >> 0) & 0x3F)));
impl.advanceWrite(2);
}
}
}
@Override
public String read(@NotNull NetworkBuffer buffer) {
int utflen = buffer.read(UNSIGNED_SHORT);
if (buffer.readableBytes() < utflen) throw new IllegalArgumentException("Invalid String size.");
byte[] bytearr = buffer.read(RAW_BYTES);
final char[] chararr = new char[utflen];
int c, char2, char3;
int count = 0;
int chararr_count = 0;
while (count < utflen) {
c = (int) bytearr[count] & 0xff;
if (c > 127) break;
count++;
chararr[chararr_count++] = (char) c;
}
while (count < utflen) {
c = (int) bytearr[count] & 0xff;
try { // Surround in try catch to throw a runtime exception instead of a checked one
switch (c >> 4) {
case 0, 1, 2, 3, 4, 5, 6, 7 -> {
/* 0xxxxxxx*/
count++;
chararr[chararr_count++] = (char) c;
}
case 12, 13 -> {
/* 110x xxxx 10xx xxxx*/
count += 2;
if (count > utflen)
throw new UTFDataFormatException(
"malformed input: partial character at end");
char2 = bytearr[count - 1];
if ((char2 & 0xC0) != 0x80)
throw new UTFDataFormatException(
"malformed input around byte " + count);
chararr[chararr_count++] = (char) (((c & 0x1F) << 6) |
(char2 & 0x3F));
}
case 14 -> {
/* 1110 xxxx 10xx xxxx 10xx xxxx */
count += 3;
if (count > utflen)
throw new UTFDataFormatException(
"malformed input: partial character at end");
char2 = bytearr[count - 2];
char3 = bytearr[count - 1];
if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
throw new UTFDataFormatException(
"malformed input around byte " + (count - 1));
chararr[chararr_count++] = (char) (((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
}
default ->
/* 10xx xxxx, 1111 xxxx */
throw new UTFDataFormatException(
"malformed input around byte " + count);
}
} catch (UTFDataFormatException e) {
throw new IllegalArgumentException(e);
}
}
// The number of chars produced may be less than utflen
return new String(chararr, 0, chararr_count);
}
}
static <T> long sizeOf(Type<T> type, T value, Registries registries) {
NetworkBuffer buffer = NetworkBufferImpl.dummy(registries);
type.write(buffer, value);

View File

@ -202,6 +202,7 @@ public final class PacketWriting {
if (written < minWrite) {
// Try again with a bigger buffer
final long newSize = Math.min(buffer.capacity() * 2, ServerFlag.MAX_PACKET_SIZE);
if (newSize == buffer.capacity()) break; // We reached the maximum size
buffer.resize(newSize);
} else {
// At least one packet has been written

View File

@ -141,9 +141,9 @@ public abstract class PlayerConnection {
*/
public void disconnect() {
this.online = false;
MinecraftServer.getConnectionManager().removePlayer(this);
final Player player = getPlayer();
final Player player = MinecraftServer.getConnectionManager().getPlayer(this);
if (player != null) {
MinecraftServer.getConnectionManager().removePlayer(this);
if (connectionState == ConnectionState.PLAY && !player.isRemoved())
player.scheduleNextTick(Entity::remove);
else {

View File

@ -8,6 +8,11 @@ import net.minestom.testing.Env;
import net.minestom.testing.EnvTest;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnvTest
@ -99,4 +104,21 @@ public class EntityTeleportIntegrationTest {
player.teleport(new Pos(5, 10, 2, 5, 5), null, RelativeFlags.VIEW).join();
assertEquals(player.getPosition(), new Pos(5, 10, 2, 95, 5));
}
@Test
public void entityTeleportToInfinity(Env env) throws ExecutionException, InterruptedException, TimeoutException {
var instance = env.createFlatInstance();
var entity = new Entity(EntityTypes.ZOMBIE);
entity.setInstance(instance, new Pos(0, 42, 0)).join();
assertEquals(instance, entity.getInstance());
assertEquals(new Pos(0, 42, 0), entity.getPosition());
entity.teleport(new Pos(Double.POSITIVE_INFINITY, 42, 52)).join();
CompletableFuture.runAsync(() -> entity.tick(System.currentTimeMillis()))
.get(10, TimeUnit.SECONDS);
// This should not hang forever
// The position should have been capped at 2 billion.
assertEquals(new Pos(2_000_000_000, 42, 52), entity.getPosition());
}
}

View File

@ -11,6 +11,8 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;
@ -474,6 +476,62 @@ public class NetworkBufferTest {
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING)); // oom
}
@Test
public void oomStringUtf8Regression() {
var buffer = NetworkBuffer.resizableBuffer(100);
buffer.write(UNSIGNED_SHORT, 65535); // String length
buffer.write(RAW_BYTES, "Hello".getBytes(StandardCharsets.UTF_8)); // String data
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING)); // oom
}
@Test
public void testStringUtf8ModifiedWrite() throws IOException {
var stream = new java.io.ByteArrayOutputStream();
java.io.DataOutputStream out = new java.io.DataOutputStream(stream);
out.writeUTF("Hello");
assertBufferType(STRING_IO_UTF8, "Hello", stream.toByteArray());
}
@Test
public void testStringUtf8ModifiedRead() throws IOException {
var stream = new java.io.ByteArrayOutputStream();
java.io.DataOutputStream out = new java.io.DataOutputStream(stream);
out.writeUTF("Hello");
var buffer = NetworkBuffer.wrap(stream.toByteArray(), 0, stream.size());
assertEquals("Hello", buffer.read(STRING_IO_UTF8));
}
@Test
public void oomStringUtf8ModfiedRegression() throws IOException {
var buffer = NetworkBuffer.resizableBuffer(100);
buffer.write(UNSIGNED_SHORT, 65535); // String length
// Write the raw bytes that are invalid
buffer.write(RAW_BYTES, new byte[]{(byte) 0xC0, (byte) 0x80}); // Invalid UTF-8
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING_IO_UTF8)); // oom
buffer.clear();
var stream = new java.io.ByteArrayOutputStream();
java.io.DataOutputStream out = new java.io.DataOutputStream(stream);
out.writeUTF("Hello");
var byteArray = stream.toByteArray();
// Mess with the length to 0
byteArray[0] = (byte) 0x00;
byteArray[1] = (byte) 0x00;
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING_IO_UTF8)); // oom
buffer.clear();
buffer.write(UNSIGNED_SHORT, 5);
buffer.write(RAW_BYTES, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); // Invalid utf8
assertThrows(IllegalArgumentException.class, () -> buffer.read(STRING_IO_UTF8)); // oom
}
static <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @UnknownNullability T value, byte[] expected, @NotNull Action<T> action) {
var buffer = NetworkBuffer.resizableBuffer(MinecraftServer.process());
action.write(buffer, type, value);