From 6bd09256f3785ed3bb783e0db28acfea87e10cda Mon Sep 17 00:00:00 2001 From: themode Date: Fri, 8 Jan 2021 07:08:10 +0100 Subject: [PATCH] Small cleanup and reduce memory usage with AbsoluteBlockBatch --- .../instance/batch/v2/AbsoluteBlockBatch.java | 44 +++++++++++-------- .../server/instance/batch/v2/Batch.java | 3 +- .../server/instance/batch/v2/ChunkBatch.java | 3 +- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/main/java/net/minestom/server/instance/batch/v2/AbsoluteBlockBatch.java b/src/main/java/net/minestom/server/instance/batch/v2/AbsoluteBlockBatch.java index e07e35f78..ea24853b4 100644 --- a/src/main/java/net/minestom/server/instance/batch/v2/AbsoluteBlockBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/v2/AbsoluteBlockBatch.java @@ -1,5 +1,7 @@ package net.minestom.server.instance.batch.v2; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minestom.server.data.Data; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.InstanceContainer; @@ -7,12 +9,11 @@ import net.minestom.server.utils.chunk.ChunkUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /** - * A Batch which can be used when changes are required across chunk borders, + * A {@link Batch} which can be used when changes are required across chunk borders, * but the changes do not need any translation. If translation is required, * use a {@link RelativeBlockBatch} instead. *

@@ -24,9 +25,10 @@ import java.util.concurrent.atomic.AtomicInteger; public class AbsoluteBlockBatch implements Batch { // In the form of - private final Map data = new HashMap<>(); + private final Long2ObjectMap chunkBatchesMap = new Long2ObjectOpenHashMap<>(); - public AbsoluteBlockBatch() {} + public AbsoluteBlockBatch() { + } @Override public void setSeparateBlocks(int x, int y, int z, short blockStateId, short customBlockId, @Nullable Data data) { @@ -34,7 +36,10 @@ public class AbsoluteBlockBatch implements Batch { final int chunkZ = ChunkUtils.getChunkCoordinate(z); final long chunkIndex = ChunkUtils.getChunkIndex(chunkX, chunkZ); - final ChunkBatch chunkBatch = this.data.computeIfAbsent(chunkIndex, i -> new ChunkBatch()); + final ChunkBatch chunkBatch; + synchronized (chunkBatchesMap) { + chunkBatch = chunkBatchesMap.computeIfAbsent(chunkIndex, i -> new ChunkBatch()); + } final int relativeX = x - (chunkX * Chunk.CHUNK_SIZE_X); final int relativeZ = z - (chunkZ * Chunk.CHUNK_SIZE_Z); @@ -43,13 +48,13 @@ public class AbsoluteBlockBatch implements Batch { @Override public void clear() { - synchronized (data) { - this.data.clear(); + synchronized (chunkBatchesMap) { + this.chunkBatchesMap.clear(); } } /** - * Apply this batch to the given instance. + * Applies this batch to the given instance. * * @param instance The instance in which the batch should be applied * @param callback The callback to be executed when the batch is applied @@ -60,7 +65,7 @@ public class AbsoluteBlockBatch implements Batch { } /** - * Apply this batch to the given instance, and execute the callback immediately when the + * Applies this batch to the given instance, and execute the callback immediately when the * blocks have been applied, in an unknown thread. * * @param instance The instance in which the batch should be applied @@ -71,32 +76,33 @@ public class AbsoluteBlockBatch implements Batch { } /** - * Apply this batch to the given instance, and execute the callback depending on safeCallback. + * Applies this batch to the given instance, and execute the callback depending on safeCallback. * - * @param instance The instance in which the batch should be applied - * @param callback The callback to be executed when the batch is applied + * @param instance The instance in which the batch should be applied + * @param callback The callback to be executed when the batch is applied * @param safeCallback If true, the callback will be executed in the next instance update. Otherwise it will be executed immediately upon completion - * */ protected void apply(@NotNull InstanceContainer instance, @Nullable Runnable callback, boolean safeCallback) { - synchronized (data) { - final AtomicInteger counter = new AtomicInteger(); - for (Map.Entry entry : data.entrySet()) { + synchronized (chunkBatchesMap) { + AtomicInteger counter = new AtomicInteger(); + for (Map.Entry entry : chunkBatchesMap.long2ObjectEntrySet()) { final long chunkIndex = entry.getKey(); final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); final ChunkBatch batch = entry.getValue(); batch.apply(instance, chunkX, chunkZ, c -> { - final boolean isLast = counter.incrementAndGet() == data.size(); + final boolean isLast = counter.incrementAndGet() == chunkBatchesMap.size(); // Execute the callback if this was the last chunk to process if (isLast) { instance.refreshLastBlockChangeTime(); if (callback != null) { - if (safeCallback) + if (safeCallback) { instance.scheduleNextTick(inst -> callback.run()); - else callback.run(); + } else { + callback.run(); + } } } }); diff --git a/src/main/java/net/minestom/server/instance/batch/v2/Batch.java b/src/main/java/net/minestom/server/instance/batch/v2/Batch.java index aaba29d05..6706098bf 100644 --- a/src/main/java/net/minestom/server/instance/batch/v2/Batch.java +++ b/src/main/java/net/minestom/server/instance/batch/v2/Batch.java @@ -16,7 +16,7 @@ import java.util.concurrent.ExecutorService; * A Batch is a tool used to cache a list of block changes, and apply the changes whenever you want. *

* Batches offer a performance benefit because clients are not notified of any change until all of - * the blocks have been placed. + * the blocks have been placed, and because changes can happen with less synchronization. *

* All batches may be rotated using {link}, however rotate operations do not mutate the batch, so the * result should be cached if used multiple times. @@ -30,6 +30,7 @@ import java.util.concurrent.ExecutorService; * @see RelativeBlockBatch */ public interface Batch extends BlockModifier { + ExecutorService BLOCK_BATCH_POOL = new MinestomThread(MinecraftServer.THREAD_COUNT_BLOCK_BATCH, MinecraftServer.THREAD_NAME_BLOCK_BATCH); @Override diff --git a/src/main/java/net/minestom/server/instance/batch/v2/ChunkBatch.java b/src/main/java/net/minestom/server/instance/batch/v2/ChunkBatch.java index 50cb12f0d..a7a461383 100644 --- a/src/main/java/net/minestom/server/instance/batch/v2/ChunkBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/v2/ChunkBatch.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; * @see Batch */ public class ChunkBatch implements Batch { + private static final Logger LOGGER = LoggerFactory.getLogger(ChunkBatch.class); // Need to be synchronized manually @@ -187,7 +188,7 @@ public class ChunkBatch implements Batch { /** * Updates the given chunk for all of its viewers, and executes the callback. */ - private void updateChunk(InstanceContainer instance, Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { + private void updateChunk(@NotNull InstanceContainer instance, @NotNull Chunk chunk, @Nullable ChunkCallback callback, boolean safeCallback) { // Refresh chunk for viewers // Formerly this had an option to do a Chunk#sendChunkUpdate