From 18608deefa9e3af7fe8474f925a3dbc51d11219b Mon Sep 17 00:00:00 2001 From: Kieran Wallbanks Date: Tue, 30 Mar 2021 15:54:56 +0100 Subject: [PATCH 01/14] Add util method to send a packet to an audience --- .../minestom/server/utils/PacketUtils.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/main/java/net/minestom/server/utils/PacketUtils.java b/src/main/java/net/minestom/server/utils/PacketUtils.java index c008696c6..d7baf02a6 100644 --- a/src/main/java/net/minestom/server/utils/PacketUtils.java +++ b/src/main/java/net/minestom/server/utils/PacketUtils.java @@ -4,8 +4,11 @@ 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; import net.minestom.server.adventure.AdventureSerializer; +import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.entity.Player; import net.minestom.server.listener.manager.PacketListenerManager; import net.minestom.server.network.netty.packet.FramedPacket; @@ -18,6 +21,7 @@ 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; @@ -34,6 +38,39 @@ public final class PacketUtils { } + /** + * Sends a packet to an audience. This method performs the following steps in the + * following order: + *
    + *
  1. If {@code audience} is a {@link Player}, send the packet to them.
  2. + *
  3. Otherwise, if {@code audience} is a {@link PacketGroupingAudience}, call + * {@link #sendGroupedPacket(Collection, ServerPacket)} on the players that the + * grouping audience contains.
  4. + *
  5. Otherwise, if {@code audience} is a {@link ForwardingAudience.Single}, + * call this method on the single audience inside the forwarding audience.
  6. + *
  7. Otherwise, if {@code audience} is a {@link ForwardingAudience}, call this + * method for each audience member of the forwarding audience.
  8. + *
  9. Otherwise, do nothing.
  10. + *
+ * + * @param audience the audience + * @param packet the packet + */ + @SuppressWarnings("OverrideOnly") // we need to access the audiences inside ForwardingAudience + public static void sendPacket(@NotNull Audience audience, @NotNull ServerPacket packet) { + if (audience instanceof Player) { + ((Player) audience).getPlayerConnection().sendPacket(packet); + } else if (audience instanceof PacketGroupingAudience) { + PacketUtils.sendGroupedPacket(((PacketGroupingAudience) audience).getPlayers(), packet); + } else if (audience instanceof ForwardingAudience.Single) { + PacketUtils.sendPacket(((ForwardingAudience.Single) audience).audience(), packet); + } else if (audience instanceof ForwardingAudience) { + for (Audience member : ((ForwardingAudience) audience).audiences()) { + PacketUtils.sendPacket(member, packet); + } + } + } + /** * Sends a {@link ServerPacket} to multiple players. *

From aaab62083963d8c2f8065ceeba18732dd0def525 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 10 Apr 2021 21:21:37 +0200 Subject: [PATCH 02/14] Reduce entity movement overhead (chunk lookup) --- .../java/net/minestom/server/entity/Entity.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 13ec16f05..776769a18 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -1309,13 +1309,20 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission final Instance instance = getInstance(); if (instance != null) { - final Chunk lastChunk = instance.getChunkAt(lastX, lastZ); - final Chunk newChunk = instance.getChunkAt(x, z); + final int lastChunkX = ChunkUtils.getChunkCoordinate(lastX); + final int lastChunkZ = ChunkUtils.getChunkCoordinate(lastZ); - Check.notNull(lastChunk, "The entity {0} was in an unloaded chunk at {1};{2}", getEntityId(), lastX, lastZ); - Check.notNull(newChunk, "The entity {0} tried to move in an unloaded chunk at {1};{2}", getEntityId(), x, z); + final int newChunkX = ChunkUtils.getChunkCoordinate(x); + final int newChunkZ = ChunkUtils.getChunkCoordinate(z); + + if (lastChunkX != newChunkX || lastChunkZ != newChunkZ) { + // Entity moved in a new chunk + final Chunk lastChunk = instance.getChunk(lastChunkX, lastChunkZ); + final Chunk newChunk = instance.getChunk(newChunkX, newChunkZ); + + Check.notNull(lastChunk, "The entity {0} was in an unloaded chunk at {1};{2}", getEntityId(), lastX, lastZ); + Check.notNull(newChunk, "The entity {0} tried to move in an unloaded chunk at {1};{2}", getEntityId(), x, z); - if (lastChunk != newChunk) { instance.UNSAFE_switchEntityChunk(this, lastChunk, newChunk); if (this instanceof Player) { // Refresh player view From c74946cc3caf99f1cc71d323ab3e980af95c235a Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 10 Apr 2021 21:42:45 +0200 Subject: [PATCH 03/14] Cache entity current chunk --- .../net/minestom/server/entity/Entity.java | 29 ++++++++++++------- .../server/utils/chunk/ChunkUtils.java | 8 +++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 776769a18..f5529b164 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -64,6 +64,7 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission private static final AtomicInteger LAST_ENTITY_ID = new AtomicInteger(); protected Instance instance; + protected Chunk currentChunk; protected final Position position; protected double lastX, lastY, lastZ; protected double cacheX, cacheY, cacheZ; // Used to synchronize with #getPosition @@ -566,11 +567,16 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission // World border collision final Position finalVelocityPosition = CollisionUtils.applyWorldBorder(instance, position, newPosition); - final Chunk finalChunk = instance.getChunkAt(finalVelocityPosition); + final Chunk finalChunk; + if (!ChunkUtils.same(position, finalVelocityPosition)) { + finalChunk = instance.getChunkAt(finalVelocityPosition); - // Entity shouldn't be updated when moving in an unloaded chunk - if (!ChunkUtils.isLoaded(finalChunk)) { - return; + // Entity shouldn't be updated when moving in an unloaded chunk + if (!ChunkUtils.isLoaded(finalChunk)) { + return; + } + } else { + finalChunk = getChunk(); } // Apply the position if changed @@ -816,9 +822,8 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission * * @return the entity chunk, can be null even if unlikely */ - @Nullable - public Chunk getChunk() { - return instance.getChunkAt(position.getX(), position.getZ()); + public @Nullable Chunk getChunk() { + return currentChunk; } /** @@ -826,8 +831,7 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission * * @return the entity instance, can be null if the entity doesn't have an instance yet */ - @Nullable - public Instance getInstance() { + public @Nullable Instance getInstance() { return instance; } @@ -855,6 +859,7 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission this.isActive = true; this.instance = instance; + this.currentChunk = instance.getChunkAt(position.getX(), position.getZ()); instance.UNSAFE_addEntity(this); spawn(); EntitySpawnEvent entitySpawnEvent = new EntitySpawnEvent(this, instance); @@ -1309,8 +1314,8 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission final Instance instance = getInstance(); if (instance != null) { - final int lastChunkX = ChunkUtils.getChunkCoordinate(lastX); - final int lastChunkZ = ChunkUtils.getChunkCoordinate(lastZ); + final int lastChunkX = currentChunk.getChunkX(); + final int lastChunkZ = currentChunk.getChunkZ(); final int newChunkX = ChunkUtils.getChunkCoordinate(x); final int newChunkZ = ChunkUtils.getChunkCoordinate(z); @@ -1330,6 +1335,8 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission player.refreshVisibleChunks(newChunk); player.refreshVisibleEntities(newChunk); } + + this.currentChunk = newChunk; } } diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java index ba1692a2f..418fe0da1 100644 --- a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java +++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java @@ -84,6 +84,14 @@ public final class ChunkUtils { return isLoaded(chunk); } + public static boolean same(@NotNull Position pos1, @NotNull Position pos2) { + final int x1 = ChunkUtils.getChunkCoordinate(pos1.getX()); + final int z1 = ChunkUtils.getChunkCoordinate(pos1.getZ()); + final int x2 = ChunkUtils.getChunkCoordinate(pos2.getX()); + final int z2 = ChunkUtils.getChunkCoordinate(pos2.getZ()); + return x1 != x2 || z1 != z2; + } + /** * @param xz the instance coordinate to convert * @return the chunk X or Z based on the argument From f5a21948c662c666298fa73722e0f95dd913f04e Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 10 Apr 2021 21:57:33 +0200 Subject: [PATCH 04/14] Reduce chunk lookup when switching --- src/main/java/net/minestom/server/entity/Entity.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index f5529b164..b59ea8317 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -1322,13 +1322,10 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission if (lastChunkX != newChunkX || lastChunkZ != newChunkZ) { // Entity moved in a new chunk - final Chunk lastChunk = instance.getChunk(lastChunkX, lastChunkZ); final Chunk newChunk = instance.getChunk(newChunkX, newChunkZ); - - Check.notNull(lastChunk, "The entity {0} was in an unloaded chunk at {1};{2}", getEntityId(), lastX, lastZ); Check.notNull(newChunk, "The entity {0} tried to move in an unloaded chunk at {1};{2}", getEntityId(), x, z); - instance.UNSAFE_switchEntityChunk(this, lastChunk, newChunk); + instance.UNSAFE_switchEntityChunk(this, currentChunk, newChunk); if (this instanceof Player) { // Refresh player view final Player player = (Player) this; From 032343011a3bdd6cff56aa45474b36bcc0bfd424 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 10 Apr 2021 22:03:03 +0200 Subject: [PATCH 05/14] Reduce map lookup when looping through surrounding custom blocks --- .../java/net/minestom/server/entity/Entity.java | 13 +++++++------ .../net/minestom/server/utils/chunk/ChunkUtils.java | 6 ++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index b59ea8317..0dd426fac 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -567,7 +567,7 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission // World border collision final Position finalVelocityPosition = CollisionUtils.applyWorldBorder(instance, position, newPosition); - final Chunk finalChunk; + Chunk finalChunk = currentChunk; if (!ChunkUtils.same(position, finalVelocityPosition)) { finalChunk = instance.getChunkAt(finalVelocityPosition); @@ -575,8 +575,6 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission if (!ChunkUtils.isLoaded(finalChunk)) { return; } - } else { - finalChunk = getChunk(); } // Apply the position if changed @@ -643,9 +641,12 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission for (int y = minY; y <= maxY; y++) { for (int x = minX; x <= maxX; x++) { for (int z = minZ; z <= maxZ; z++) { - final Chunk chunk = instance.getChunkAt(x, z); - if (!ChunkUtils.isLoaded(chunk)) - continue; + Chunk chunk = currentChunk; + if (!ChunkUtils.same(currentChunk, x, z)) { + chunk = instance.getChunkAt(x, z); + if (!ChunkUtils.isLoaded(chunk)) + continue; + } final CustomBlock customBlock = chunk.getCustomBlock(x, y, z); if (customBlock != null) { diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java index 418fe0da1..f3388a0eb 100644 --- a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java +++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java @@ -84,6 +84,12 @@ public final class ChunkUtils { return isLoaded(chunk); } + public static boolean same(@NotNull Chunk chunk, double x, double z) { + final int chunkX = ChunkUtils.getChunkCoordinate(x); + final int chunkZ = ChunkUtils.getChunkCoordinate(z); + return chunk.getChunkX() != chunkX || chunk.getChunkZ() != chunkZ; + } + public static boolean same(@NotNull Position pos1, @NotNull Position pos2) { final int x1 = ChunkUtils.getChunkCoordinate(pos1.getX()); final int z1 = ChunkUtils.getChunkCoordinate(pos1.getZ()); From 2da42cb8cbcd39e566a968b98a78124f0cb35db3 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 10 Apr 2021 22:33:38 +0200 Subject: [PATCH 06/14] Remove unnecessary chunk callback --- .../java/net/minestom/server/entity/Player.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index e5c1b9795..cdfd50c35 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -671,17 +671,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable, // Load all the required chunks final long[] visibleChunks = ChunkUtils.getChunksInRange(spawnPosition, getChunkRange()); - final ChunkCallback eachCallback = chunk -> { - if (chunk != null) { - final int chunkX = ChunkUtils.getChunkCoordinate(spawnPosition.getX()); - final int chunkZ = ChunkUtils.getChunkCoordinate(spawnPosition.getZ()); - if (chunk.getChunkX() == chunkX && - chunk.getChunkZ() == chunkZ) { - updateViewPosition(chunkX, chunkZ); - } - } - }; - final ChunkCallback endCallback = chunk -> { // This is the last chunk to be loaded , spawn player spawnPlayer(instance, spawnPosition, firstSpawn, true, dimensionChange); @@ -690,7 +679,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, // Chunk 0;0 always needs to be loaded instance.loadChunk(0, 0, chunk -> // Load all the required chunks - ChunkUtils.optionalLoadAll(instance, visibleChunks, eachCallback, endCallback)); + ChunkUtils.optionalLoadAll(instance, visibleChunks, null, endCallback)); } else { // The player already has the good version of all the chunks. From d219570fe6d4364c13505c1ebbcffa182e22f895 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 10 Apr 2021 23:17:20 +0200 Subject: [PATCH 07/14] Make it clear that PlayerAbilitiesPacket uses a bitmask --- .../network/packet/server/play/PlayerAbilitiesPacket.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerAbilitiesPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerAbilitiesPacket.java index 20b9cf044..61649f64d 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerAbilitiesPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerAbilitiesPacket.java @@ -22,13 +22,13 @@ public class PlayerAbilitiesPacket implements ServerPacket { public void write(@NotNull BinaryWriter writer) { byte flags = 0; if (invulnerable) - flags += 1; + flags |= 0x01; if (flying) - flags += 2; + flags |= 0x02; if (allowFlying) - flags += 4; + flags |= 0x04; if (instantBreak) - flags += 8; + flags |= 0x08; writer.writeByte(flags); writer.writeFloat(flyingSpeed); From c347f55c1f96d28130d1966c8cbc128b37668a9a Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 10 Apr 2021 23:18:07 +0200 Subject: [PATCH 08/14] Make it clear that ArgumentEntity uses a bitmask --- .../command/builder/arguments/minecraft/ArgumentEntity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentEntity.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentEntity.java index 5414fff20..3e4b47946 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentEntity.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/ArgumentEntity.java @@ -79,10 +79,10 @@ public class ArgumentEntity extends Argument { argumentNode.properties = BinaryWriter.makeArray(packetWriter -> { byte mask = 0; if (this.isOnlySingleEntity()) { - mask += 1; + mask |= 0x01; } if (this.isOnlyPlayers()) { - mask += 2; + mask |= 0x02; } packetWriter.writeByte(mask); }); From 5adbc287b390e0314fcce03355e7b1f853d8341a Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 11 Apr 2021 00:09:01 +0200 Subject: [PATCH 09/14] Added TickMonitor --- .../net/minestom/server/MinecraftServer.java | 2 +- .../net/minestom/server/UpdateManager.java | 25 +++++++++++++++---- .../BenchmarkManager.java | 2 +- .../ThreadResult.java | 2 +- .../server/monitoring/TickMonitor.java | 14 +++++++++++ src/test/java/demo/Main.java | 2 -- src/test/java/demo/PlayerInit.java | 13 ++++++++-- 7 files changed, 48 insertions(+), 12 deletions(-) rename src/main/java/net/minestom/server/{benchmark => monitoring}/BenchmarkManager.java (99%) rename src/main/java/net/minestom/server/{benchmark => monitoring}/ThreadResult.java (95%) create mode 100644 src/main/java/net/minestom/server/monitoring/TickMonitor.java diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 8c3ce2903..639ca5e36 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -2,7 +2,7 @@ package net.minestom.server; import net.minestom.server.advancements.AdvancementManager; import net.minestom.server.adventure.bossbar.BossBarManager; -import net.minestom.server.benchmark.BenchmarkManager; +import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.command.CommandManager; import net.minestom.server.data.DataManager; import net.minestom.server.data.DataType; diff --git a/src/main/java/net/minestom/server/UpdateManager.java b/src/main/java/net/minestom/server/UpdateManager.java index 1222f0bc4..23b0020b0 100644 --- a/src/main/java/net/minestom/server/UpdateManager.java +++ b/src/main/java/net/minestom/server/UpdateManager.java @@ -4,6 +4,7 @@ import com.google.common.collect.Queues; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceManager; +import net.minestom.server.monitoring.TickMonitor; import net.minestom.server.network.ConnectionManager; import net.minestom.server.network.player.NettyPlayerConnection; import net.minestom.server.thread.PerInstanceThreadProvider; @@ -13,10 +14,8 @@ import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Queue; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; +import java.util.function.Consumer; import java.util.function.LongConsumer; /** @@ -36,6 +35,7 @@ public final class UpdateManager { private final Queue tickStartCallbacks = Queues.newConcurrentLinkedQueue(); private final Queue tickEndCallbacks = Queues.newConcurrentLinkedQueue(); + private final List> tickMonitors = new CopyOnWriteArrayList<>(); { // DEFAULT THREAD PROVIDER @@ -81,7 +81,14 @@ public final class UpdateManager { final long tickTime = System.nanoTime() - currentTime; // Tick end callbacks - doTickCallback(tickEndCallbacks, tickTime / 1000000L); + doTickCallback(tickEndCallbacks, tickTime); + + // Monitoring + if (!tickMonitors.isEmpty()) { + final double tickTimeMs = tickTime / 1e6D; + final TickMonitor tickMonitor = new TickMonitor(tickTimeMs); + this.tickMonitors.forEach(consumer -> consumer.accept(tickMonitor)); + } // Flush all waiting packets AsyncUtils.runAsync(() -> connectionManager.getOnlinePlayers().stream() @@ -246,6 +253,14 @@ public final class UpdateManager { this.tickEndCallbacks.remove(callback); } + public void addTickMonitor(@NotNull Consumer consumer) { + this.tickMonitors.add(consumer); + } + + public void removeTickMonitor(@NotNull Consumer consumer) { + this.tickMonitors.remove(consumer); + } + /** * Stops the server loop. */ diff --git a/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java b/src/main/java/net/minestom/server/monitoring/BenchmarkManager.java similarity index 99% rename from src/main/java/net/minestom/server/benchmark/BenchmarkManager.java rename to src/main/java/net/minestom/server/monitoring/BenchmarkManager.java index e342218a1..f29bf4605 100644 --- a/src/main/java/net/minestom/server/benchmark/BenchmarkManager.java +++ b/src/main/java/net/minestom/server/monitoring/BenchmarkManager.java @@ -1,4 +1,4 @@ -package net.minestom.server.benchmark; +package net.minestom.server.monitoring; import it.unimi.dsi.fastutil.longs.Long2LongMap; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; diff --git a/src/main/java/net/minestom/server/benchmark/ThreadResult.java b/src/main/java/net/minestom/server/monitoring/ThreadResult.java similarity index 95% rename from src/main/java/net/minestom/server/benchmark/ThreadResult.java rename to src/main/java/net/minestom/server/monitoring/ThreadResult.java index be0a81a7a..8c50c49a7 100644 --- a/src/main/java/net/minestom/server/benchmark/ThreadResult.java +++ b/src/main/java/net/minestom/server/monitoring/ThreadResult.java @@ -1,4 +1,4 @@ -package net.minestom.server.benchmark; +package net.minestom.server.monitoring; public class ThreadResult { diff --git a/src/main/java/net/minestom/server/monitoring/TickMonitor.java b/src/main/java/net/minestom/server/monitoring/TickMonitor.java new file mode 100644 index 000000000..a92e5d40d --- /dev/null +++ b/src/main/java/net/minestom/server/monitoring/TickMonitor.java @@ -0,0 +1,14 @@ +package net.minestom.server.monitoring; + +public class TickMonitor { + + private final double tickTime; + + public TickMonitor(double tickTime) { + this.tickTime = tickTime; + } + + public double getTickTime() { + return tickTime; + } +} \ No newline at end of file diff --git a/src/test/java/demo/Main.java b/src/test/java/demo/Main.java index c357c124d..7b2adda4a 100644 --- a/src/test/java/demo/Main.java +++ b/src/test/java/demo/Main.java @@ -22,8 +22,6 @@ public class Main { public static void main(String[] args) { MinecraftServer minecraftServer = MinecraftServer.init(); - // MinecraftServer.setShouldProcessNettyErrors(true); - BlockManager blockManager = MinecraftServer.getBlockManager(); blockManager.registerCustomBlock(new CustomBlockSample()); blockManager.registerCustomBlock(new UpdatableBlockDemo()); diff --git a/src/test/java/demo/PlayerInit.java b/src/test/java/demo/PlayerInit.java index 64d50e014..6c34c2fc1 100644 --- a/src/test/java/demo/PlayerInit.java +++ b/src/test/java/demo/PlayerInit.java @@ -1,11 +1,11 @@ package demo; +import com.google.common.util.concurrent.AtomicDouble; import demo.generator.ChunkGeneratorDemo; import demo.generator.NoiseTestGenerator; import net.kyori.adventure.text.Component; import net.minestom.server.MinecraftServer; import net.minestom.server.adventure.audience.Audiences; -import net.minestom.server.benchmark.BenchmarkManager; import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Entity; import net.minestom.server.entity.GameMode; @@ -28,8 +28,10 @@ import net.minestom.server.inventory.InventoryType; import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; +import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.network.ConnectionManager; import net.minestom.server.ping.ResponseDataConsumer; +import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.Position; import net.minestom.server.utils.Vector; import net.minestom.server.utils.inventory.PlayerInventoryUtils; @@ -64,10 +66,15 @@ public class PlayerInit { //inventory.setItemStack(3, new ItemStack(Material.DIAMOND, (byte) 34)); } + private static final AtomicDouble LAST_TICK_TIME = new AtomicDouble(); + public static void init() { ConnectionManager connectionManager = MinecraftServer.getConnectionManager(); BenchmarkManager benchmarkManager = MinecraftServer.getBenchmarkManager(); + MinecraftServer.getUpdateManager().addTickMonitor(tickMonitor -> + LAST_TICK_TIME.set(tickMonitor.getTickTime())); + MinecraftServer.getSchedulerManager().buildTask(() -> { Collection players = connectionManager.getOnlinePlayers(); @@ -78,7 +85,9 @@ public class PlayerInit { long ramUsage = benchmarkManager.getUsedMemory(); ramUsage /= 1e6; // bytes to MB - final Component header = Component.text("RAM USAGE: " + ramUsage + " MB"); + final Component header = Component.text("RAM USAGE: " + ramUsage + " MB") + .append(Component.newline()) + .append(Component.text("TICK TIME: " + MathUtils.round(LAST_TICK_TIME.get(), 2) + "ms")); final Component footer = benchmarkManager.getCpuMonitoringMessage(); Audiences.players().sendPlayerListHeaderAndFooter(header, footer); From 6e0202c33b5578bf0a778913ce40fae0f8f87ad0 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 11 Apr 2021 03:21:38 +0200 Subject: [PATCH 10/14] Fix math, improve physics performance --- .../server/collision/CollisionUtils.java | 32 ++++++++++++------- .../server/utils/chunk/ChunkUtils.java | 16 +++++----- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/minestom/server/collision/CollisionUtils.java b/src/main/java/net/minestom/server/collision/CollisionUtils.java index a55d0f172..fb947341f 100644 --- a/src/main/java/net/minestom/server/collision/CollisionUtils.java +++ b/src/main/java/net/minestom/server/collision/CollisionUtils.java @@ -32,21 +32,22 @@ public class CollisionUtils { @NotNull Vector velocityOut) { // TODO handle collisions with nearby entities (should it be done here?) final Instance instance = entity.getInstance(); + final Chunk originChunk = entity.getChunk(); final Position currentPosition = entity.getPosition(); final BoundingBox boundingBox = entity.getBoundingBox(); Vector intermediaryPosition = new Vector(); - final boolean yCollision = stepAxis(instance, currentPosition.toVector(), Y_AXIS, deltaPosition.getY(), + final boolean yCollision = stepAxis(instance, originChunk, currentPosition.toVector(), Y_AXIS, deltaPosition.getY(), intermediaryPosition, deltaPosition.getY() > 0 ? boundingBox.getTopFace() : boundingBox.getBottomFace() ); - final boolean xCollision = stepAxis(instance, intermediaryPosition, X_AXIS, deltaPosition.getX(), + final boolean xCollision = stepAxis(instance, originChunk, intermediaryPosition, X_AXIS, deltaPosition.getX(), intermediaryPosition, deltaPosition.getX() < 0 ? boundingBox.getLeftFace() : boundingBox.getRightFace() ); - final boolean zCollision = stepAxis(instance, intermediaryPosition, Z_AXIS, deltaPosition.getZ(), + final boolean zCollision = stepAxis(instance, originChunk, intermediaryPosition, Z_AXIS, deltaPosition.getZ(), intermediaryPosition, deltaPosition.getZ() > 0 ? boundingBox.getBackFace() : boundingBox.getFrontFace() ); @@ -80,7 +81,11 @@ public class CollisionUtils { * @param corners the corners to check against * @return true if a collision has been found */ - private static boolean stepAxis(Instance instance, Vector startPosition, Vector axis, double stepAmount, Vector positionOut, Vector... corners) { + private static boolean stepAxis(Instance instance, + Chunk originChunk, + Vector startPosition, Vector axis, + double stepAmount, Vector positionOut, + Vector... corners) { positionOut.copy(startPosition); if (corners.length == 0) return false; // avoid degeneracy in following computations @@ -99,7 +104,7 @@ public class CollisionUtils { // used to determine if 'remainingLength' should be used boolean collisionFound = false; for (int i = 0; i < Math.abs(blockLength); i++) { - if (!stepOnce(instance, axis, sign, cornersCopy, cornerPositions)) { + if (!stepOnce(instance, originChunk, axis, sign, cornersCopy, cornerPositions)) { collisionFound = true; } if (collisionFound) { @@ -111,7 +116,7 @@ public class CollisionUtils { if (!collisionFound) { Vector direction = new Vector(); direction.copy(axis); - collisionFound = !stepOnce(instance, direction, remainingLength, cornersCopy, cornerPositions); + collisionFound = !stepOnce(instance, originChunk, direction, remainingLength, cornersCopy, cornerPositions); } // find the corner which moved the least @@ -138,7 +143,9 @@ public class CollisionUtils { * @param cornerPositions the corners, converted to BlockPosition (mutable) * @return false if this method encountered a collision */ - private static boolean stepOnce(Instance instance, Vector axis, double amount, Vector[] cornersCopy, BlockPosition[] cornerPositions) { + private static boolean stepOnce(Instance instance, + Chunk originChunk, + Vector axis, double amount, Vector[] cornersCopy, BlockPosition[] cornerPositions) { final double sign = Math.signum(amount); for (int cornerIndex = 0; cornerIndex < cornersCopy.length; cornerIndex++) { Vector corner = cornersCopy[cornerIndex]; @@ -148,10 +155,13 @@ public class CollisionUtils { blockPos.setY((int) Math.floor(corner.getY())); blockPos.setZ((int) Math.floor(corner.getZ())); - final Chunk chunk = instance.getChunkAt(blockPos); - if (!ChunkUtils.isLoaded(chunk)) { - // Collision at chunk border - return false; + Chunk chunk = originChunk; + if (!ChunkUtils.same(originChunk, blockPos.getX(), blockPos.getZ())) { + chunk = instance.getChunkAt(blockPos); + if (!ChunkUtils.isLoaded(chunk)) { + // Collision at chunk border + return false; + } } final short blockStateId = chunk.getBlockStateId(blockPos.getX(), blockPos.getY(), blockPos.getZ()); diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java index f3388a0eb..eeefd3b64 100644 --- a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java +++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java @@ -85,17 +85,17 @@ public final class ChunkUtils { } public static boolean same(@NotNull Chunk chunk, double x, double z) { - final int chunkX = ChunkUtils.getChunkCoordinate(x); - final int chunkZ = ChunkUtils.getChunkCoordinate(z); - return chunk.getChunkX() != chunkX || chunk.getChunkZ() != chunkZ; + final int chunkX = getChunkCoordinate(x); + final int chunkZ = getChunkCoordinate(z); + return chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ; } public static boolean same(@NotNull Position pos1, @NotNull Position pos2) { - final int x1 = ChunkUtils.getChunkCoordinate(pos1.getX()); - final int z1 = ChunkUtils.getChunkCoordinate(pos1.getZ()); - final int x2 = ChunkUtils.getChunkCoordinate(pos2.getX()); - final int z2 = ChunkUtils.getChunkCoordinate(pos2.getZ()); - return x1 != x2 || z1 != z2; + final int x1 = getChunkCoordinate(pos1.getX()); + final int z1 = getChunkCoordinate(pos1.getZ()); + final int x2 = getChunkCoordinate(pos2.getX()); + final int z2 = getChunkCoordinate(pos2.getZ()); + return x1 == x2 && z1 == z2; } /** From 2f53388b9f819cc6074428ebcf0759c41703e84f Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 11 Apr 2021 05:46:52 +0200 Subject: [PATCH 11/14] Fix velocity IP forwarding --- .../packet/client/login/LoginPluginResponsePacket.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java b/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java index 17b40448c..6b8b93875 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java @@ -48,7 +48,7 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket { // Velocity if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) { - if (data != null) { + if (data != null && data.length > 0) { BinaryReader reader = new BinaryReader(data); success = VelocityProxy.checkIntegrity(reader); if (success) { @@ -103,7 +103,7 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket { writer.writeVarInt(messageId); writer.writeBoolean(successful); - if(successful) { + if (successful) { writer.writeBytes(data); } } From 11146186ccac142092e6c9d8cc32877899692094 Mon Sep 17 00:00:00 2001 From: Matt Worzala Date: Sun, 11 Apr 2021 00:37:00 -0400 Subject: [PATCH 12/14] add update structure block packet --- .../handler/ClientPlayPacketsHandler.java | 2 +- .../ClientUpdateStructureBlockPacket.java | 111 ++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/minestom/server/network/packet/client/play/ClientUpdateStructureBlockPacket.java diff --git a/src/main/java/net/minestom/server/network/packet/client/handler/ClientPlayPacketsHandler.java b/src/main/java/net/minestom/server/network/packet/client/handler/ClientPlayPacketsHandler.java index f044e5a0a..63e81a585 100644 --- a/src/main/java/net/minestom/server/network/packet/client/handler/ClientPlayPacketsHandler.java +++ b/src/main/java/net/minestom/server/network/packet/client/handler/ClientPlayPacketsHandler.java @@ -49,7 +49,7 @@ public class ClientPlayPacketsHandler extends ClientPacketsHandler { register(0x27, ClientUpdateCommandBlockMinecartPacket::new); register(0x28, ClientCreativeInventoryActionPacket::new); //Update Jigsaw Block?? - //Update Structure Block?? + register(0x2A, ClientUpdateStructureBlockPacket::new); register(0x2B, ClientUpdateSignPacket::new); register(0x2C, ClientAnimationPacket::new); register(0x2D, ClientSpectatePacket::new); diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientUpdateStructureBlockPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientUpdateStructureBlockPacket.java new file mode 100644 index 000000000..e7a023511 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientUpdateStructureBlockPacket.java @@ -0,0 +1,111 @@ +package net.minestom.server.network.packet.client.play; + +import net.minestom.server.network.packet.client.ClientPlayPacket; +import net.minestom.server.utils.BlockPosition; +import net.minestom.server.utils.Rotation; +import net.minestom.server.utils.binary.BinaryReader; +import net.minestom.server.utils.binary.BinaryWriter; +import org.jetbrains.annotations.NotNull; + +public class ClientUpdateStructureBlockPacket extends ClientPlayPacket { + // Flag values + public static final byte IGNORE_ENTITIES = 0x1; + public static final byte SHOW_AIR = 0x2; + /** + * Requires the player to be in creative and have a permission level higher than 2. + */ + public static final byte SHOW_BOUNDING_BOX = 0x4; + + public BlockPosition location; + public Action action = Action.UPDATE_DATA; + public Mode mode = Mode.DATA; + public String name = ""; + public BlockPosition offset = new BlockPosition(0, 1, 0); + public BlockPosition size = new BlockPosition(1, 1, 1); + public Mirror mirror = Mirror.NONE; + public Rotation rotation = Rotation.NONE; + public String metadata = ""; + public float integrity = 1.0f; + public long seed = 0; + public byte flags = 0x0; + + @Override + public void write(@NotNull BinaryWriter writer) { + writer.writeBlockPosition(location); + writer.writeVarInt(action.ordinal()); + writer.writeVarInt(mode.ordinal()); + writer.writeSizedString(name); + writer.writeByte((byte) offset.getX()); + writer.writeByte((byte) offset.getY()); + writer.writeByte((byte) offset.getZ()); + writer.writeByte((byte) size.getX()); + writer.writeByte((byte) size.getY()); + writer.writeByte((byte) size.getZ()); + writer.writeVarInt(mirror.ordinal()); + writer.writeVarInt(toRestrictedRotation(rotation)); + writer.writeSizedString(metadata); + writer.writeFloat(integrity); + writer.writeVarLong(seed); + writer.writeByte(flags); + } + + @Override + public void read(@NotNull BinaryReader reader) { + location = reader.readBlockPosition(); + action = Action.values()[reader.readVarInt()]; + mode = Mode.values()[reader.readVarInt()]; + name = reader.readSizedString(Short.MAX_VALUE); + offset = new BlockPosition( + reader.readByte(), + reader.readByte(), + reader.readByte() + ); + size = new BlockPosition( + reader.readByte(), + reader.readByte(), + reader.readByte() + ); + mirror = Mirror.values()[reader.readVarInt()]; + rotation = fromRestrictedRotation(reader.readVarInt()); + metadata = reader.readSizedString(Short.MAX_VALUE); + integrity = reader.readFloat(); + seed = reader.readVarLong(); + flags = reader.readByte(); + } + + /** + * Update action, UPDATE_DATA indicates nothing special. + */ + public enum Action { + UPDATE_DATA, SAVE, LOAD, DETECT_SIZE + } + + public enum Mode { + SAVE, LOAD, CORNER, DATA + } + + public enum Mirror { + NONE, LEFT_RIGHT, FRONT_BACK + } + + private int toRestrictedRotation(Rotation rotation) { + switch (rotation) { + case NONE: return 0; + case CLOCKWISE: return 1; + case FLIPPED: return 2; + case COUNTER_CLOCKWISE: return 3; + default: throw new IllegalArgumentException("ClientUpdateStructurePacket#rotation must be a valid 90-degree rotation."); + } + } + + private Rotation fromRestrictedRotation(int rotation) { + switch (rotation) { + case 0: return Rotation.NONE; + case 1: return Rotation.CLOCKWISE; + case 2: return Rotation.FLIPPED; + case 3: return Rotation.COUNTER_CLOCKWISE; + default: throw new IllegalArgumentException("ClientUpdateStructurePacket#rotation must be a valid 90-degree rotation."); + } + } + +} From 1f27f2e6cde6a0cfebe4a18a81979e5c110d5dec Mon Sep 17 00:00:00 2001 From: Matt Worzala Date: Sun, 11 Apr 2021 00:46:04 -0400 Subject: [PATCH 13/14] provide default location --- .../packet/client/play/ClientUpdateStructureBlockPacket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientUpdateStructureBlockPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientUpdateStructureBlockPacket.java index e7a023511..5cceadc28 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientUpdateStructureBlockPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientUpdateStructureBlockPacket.java @@ -16,7 +16,7 @@ public class ClientUpdateStructureBlockPacket extends ClientPlayPacket { */ public static final byte SHOW_BOUNDING_BOX = 0x4; - public BlockPosition location; + public BlockPosition location = new BlockPosition(0, 0, 0); public Action action = Action.UPDATE_DATA; public Mode mode = Mode.DATA; public String name = ""; From 3b37987f15667b4d14a9e2467900dfea0922a558 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 11 Apr 2021 14:55:27 +0200 Subject: [PATCH 14/14] Remove dead line --- src/main/java/net/minestom/server/entity/Entity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 0dd426fac..0747e0f05 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -463,7 +463,6 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission } // Check if the entity chunk is loaded - final Chunk currentChunk = getChunk(); if (!ChunkUtils.isLoaded(currentChunk)) { // No update for entities in unloaded chunk return;