From d0163ae74911b926f179eef2658ba14fdc0ebb44 Mon Sep 17 00:00:00 2001 From: Matt Worzala Date: Tue, 26 Jan 2021 00:26:31 -0500 Subject: [PATCH] send individual section updates when less than half of the sections are updated. --- .../net/minestom/server/instance/Chunk.java | 18 ++++++++++++ .../server/instance/batch/ChunkBatch.java | 29 +++++++++++++------ .../java/demo/commands/CubeBatchCommand.java | 4 +-- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index e2f8a9347..d15789bed 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.stream.Collectors; // TODO light data & API @@ -546,6 +547,23 @@ public abstract class Chunk implements Viewable, DataContainer { PacketUtils.sendGroupedPacket(getViewers(), getFreshFullDataPacket()); } + /** + * Sends a single section {@link ChunkDataPacket} to all chunk viewers. + * + * @param section The section to send. + */ + public synchronized void sendChunkSectionUpdate(int section) { + Check.argCondition(!MathUtils.isBetween(section, 0, CHUNK_SECTION_COUNT), + "The chunk section " + section + " does not exist"); + PacketUtils.sendGroupedPacket( + //todo An option to filter out non-netty clients in sendGroupedPacket would be nice. + getViewers() + .stream() + .filter(PlayerUtils::isNettyClient) + .collect(Collectors.toUnmodifiableList()), + createChunkSectionUpdatePacket(section)); + } + /** * Sends a chunk section update packet to {@code player}. * diff --git a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java index 93fb4979a..21468a857 100644 --- a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java @@ -2,6 +2,8 @@ package net.minestom.server.instance.batch; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; import net.minestom.server.data.Data; @@ -18,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.CountDownLatch; +import java.util.function.IntConsumer; /** * A Batch used when all of the block changed are contained inside a single chunk. @@ -32,8 +35,10 @@ import java.util.concurrent.CountDownLatch; * @see Batch */ public class ChunkBatch implements Batch { + private static final int CHUNK_SECTION_THRESHOLD = Chunk.CHUNK_SECTION_COUNT / 2; private static final Logger LOGGER = LoggerFactory.getLogger(ChunkBatch.class); + // Need to be synchronized manually // Format: blockIndex/blockStateId/customBlockId (32/16/16 bits) private final LongList blocks; @@ -199,14 +204,16 @@ public class ChunkBatch implements Batch { return; } + final IntSet sections = new IntArraySet(); synchronized (blocks) { for (long block : blocks) { - apply(chunk, block, inverse); + final int section = apply(chunk, block, inverse); + sections.add(section); } } if (inverse != null) inverse.readyLatch.countDown(); - updateChunk(instance, chunk, callback, safeCallback); + updateChunk(instance, chunk, sections, callback, safeCallback); } catch (Exception e) { e.printStackTrace(); } @@ -217,8 +224,9 @@ public class ChunkBatch implements Batch { * * @param chunk The chunk to apply the change * @param value block index|state id|custom block id (32|16|16 bits) + * @return The chunk section which the block was placed */ - private void apply(@NotNull Chunk chunk, long value, @Nullable ChunkBatch inverse) { + private int apply(@NotNull Chunk chunk, long value, @Nullable ChunkBatch inverse) { final short customBlockId = (short) (value & 0xFFFF); final short blockId = (short) ((value >> 16) & 0xFFFF); final int index = (int) ((value >> 32) & 0xFFFFFFFFL); @@ -238,18 +246,21 @@ public class ChunkBatch implements Batch { inverse.setSeparateBlocks(x, y, z, chunk.getBlockStateId(x, y, z), chunk.getCustomBlockId(x, y, z), chunk.getBlockData(index)); chunk.UNSAFE_setBlock(x, y, z, blockId, customBlockId, data, CustomBlockUtils.hasUpdate(customBlockId)); + return ChunkUtils.getSectionAt(y); } /** * Updates the given chunk for all of its viewers, and executes the callback. */ - private void updateChunk(@NotNull Instance instance, Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { + private void updateChunk(@NotNull Instance instance, Chunk chunk, IntSet sections, @Nullable ChunkCallback callback, boolean safeCallback) { // Refresh chunk for viewers - - // Formerly this had an option to do a Chunk#sendChunkUpdate - // however Chunk#sendChunk does the same including a light update - chunk.sendChunkUpdate(); - //chunk.sendChunk(); + if (sections.size() <= CHUNK_SECTION_THRESHOLD) { + // Update only a few sections of the chunk + sections.forEach((IntConsumer) chunk::sendChunkSectionUpdate); + } else { + // Update the entire chunk + chunk.sendChunk(); + } if (instance instanceof InstanceContainer) { // FIXME: put method in Instance instead diff --git a/src/test/java/demo/commands/CubeBatchCommand.java b/src/test/java/demo/commands/CubeBatchCommand.java index 2712b6096..d1e9b272d 100644 --- a/src/test/java/demo/commands/CubeBatchCommand.java +++ b/src/test/java/demo/commands/CubeBatchCommand.java @@ -37,8 +37,8 @@ public class CubeBatchCommand extends Command { Player player = sender.asPlayer(); InstanceContainer instance = (InstanceContainer) player.getInstance(); -// applyChunkShape(instance); - applyBlockShape(instance); + applyChunkShape(instance); +// applyBlockShape(instance); // AbsoluteBlockBatch batch = new AbsoluteBlockBatch(); //