From bb6536644e2e9c737fd1d6a26963771529d524ec Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 21:41:21 +0800 Subject: [PATCH 1/2] =?UTF-8?q?perf(core):=20=E4=BC=98=E5=8C=96=E6=96=B9?= =?UTF-8?q?=E5=9D=97=E5=AE=9E=E4=BD=93=E7=9A=84=20tick=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/entity/BaseParticleBlockEntity.java | 1 + .../core/util/BlockEntityTickersList.java | 111 ++++++++++++++++++ .../craftengine/core/world/CEWorld.java | 14 ++- 3 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java index fb8d63c83..9643a2bfd 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java @@ -18,6 +18,7 @@ public abstract class BaseParticleBlockEntity extends BlockEntity { } public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, BaseParticleBlockEntity particle) { + if (true) return; particle.tickCount++; if (particle.tickCount % 10 != 0) return; particle.animateTick(state, ceWorld.world(), blockPos); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java b/core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java new file mode 100644 index 000000000..124269086 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/BlockEntityTickersList.java @@ -0,0 +1,111 @@ +/** + * This implementation references the BlockEntityTickersList implementation by Winds Studio, + * available at: https://github.com/Winds-Studio/Leaf/blob/b9ebff/leaf-server/src/main/java/org/dreeam/leaf/util/list/BlockEntityTickersList.java + *

+ * This work is licensed under the GNU General Public License v3.0 (GPLv3) + */ +package net.momirealms.craftengine.core.util; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; + +import java.util.Arrays; +import java.util.Collection; + +/** + * A list for ServerLevel's blockEntityTickers + *

+ * This list behaves identically to ObjectArrayList, but it has an additional method, `removeAllByIndex`, that allows a list of integers to be passed indicating what + * indexes should be deleted from the list + *

+ * This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping thru each index manually and deleting with remove, + * since we don't need to resize the array every single remove. + */ +public final class BlockEntityTickersList extends ObjectArrayList { + + private final IntOpenHashSet toRemove = new IntOpenHashSet(); + private int startSearchFromIndex = -1; + + /** + * Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity. + */ + public BlockEntityTickersList() { + super(); + } + + /** + * Creates a new array list and fills it with a given collection. + * + * @param c a collection that will be used to fill the array list. + */ + public BlockEntityTickersList(final Collection c) { + super(c); + } + + /** + * Marks an entry as removed + * + * @param index the index of the item on the list to be marked as removed + */ + public void markAsRemoved(final int index) { + // The block entities list always loop starting from 0, so we only need to check if the startSearchFromIndex is -1 and that's it + if (this.startSearchFromIndex == -1) + this.startSearchFromIndex = index; + + this.toRemove.add(index); + } + + /** + * Removes elements that have been marked as removed. + */ + public void removeMarkedEntries() { + if (this.startSearchFromIndex == -1) // No entries in the list, skip + return; + + removeAllByIndex(startSearchFromIndex, toRemove); + toRemove.clear(); + this.startSearchFromIndex = -1; // Reset the start search index + } + + /** + * Removes elements by their index. + */ + private void removeAllByIndex(final int startSearchFromIndex, final IntOpenHashSet c) { // can't use Set because we want to avoid autoboxing when using contains + final int requiredMatches = c.size(); + if (requiredMatches == 0) + return; // exit early, we don't need to do anything + + final Object[] a = this.a; + int writeIndex = startSearchFromIndex; + int lastCopyIndex = startSearchFromIndex; + int matches = 0; + + for (int readIndex = startSearchFromIndex; readIndex < size; readIndex++) { + if (c.contains(readIndex)) { + matches++; + final int blockLength = readIndex - lastCopyIndex; + if (blockLength > 0) { + System.arraycopy(a, lastCopyIndex, a, writeIndex, blockLength); + writeIndex += blockLength; + } + lastCopyIndex = readIndex + 1; + + if (matches == requiredMatches) { + break; + } + } + } + + final int finalBlockLength = size - lastCopyIndex; + if (finalBlockLength > 0) { + System.arraycopy(a, lastCopyIndex, a, writeIndex, finalBlockLength); + writeIndex += finalBlockLength; + } + + if (writeIndex < size) { + Arrays.fill(a, writeIndex, size, null); + } + size = writeIndex; + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 38b3985ec..5d35506a7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -1,13 +1,13 @@ package net.momirealms.craftengine.core.world; import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable; -import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; import net.momirealms.craftengine.core.block.entity.tick.TickingBlockEntity; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; +import net.momirealms.craftengine.core.util.BlockEntityTickersList; import net.momirealms.craftengine.core.world.chunk.CEChunk; import net.momirealms.craftengine.core.world.chunk.storage.StorageAdaptor; import net.momirealms.craftengine.core.world.chunk.storage.WorldDataStorage; @@ -25,12 +25,14 @@ public abstract class CEWorld { protected final WorldHeight worldHeightAccessor; protected List pendingLightSections = new ArrayList<>(); protected final Set lightSections = ConcurrentHashMap.newKeySet(128); - protected final List tickingBlockEntities = new ArrayList<>(); + protected final BlockEntityTickersList tickingBlockEntities = new BlockEntityTickersList(); protected final List pendingTickingBlockEntities = new ArrayList<>(); protected volatile boolean isTickingBlockEntities = false; protected volatile boolean isUpdatingLights = false; protected SchedulerTask syncTickTask; protected SchedulerTask asyncTickTask; + @SuppressWarnings("FieldCanBeLocal") + private int tileTickPosition; public CEWorld(World world, StorageAdaptor adaptor) { this.world = world; @@ -205,15 +207,15 @@ public abstract class CEWorld { this.pendingTickingBlockEntities.clear(); } if (!this.tickingBlockEntities.isEmpty()) { - ReferenceOpenHashSet toRemove = new ReferenceOpenHashSet<>(); - for (TickingBlockEntity blockEntity : this.tickingBlockEntities) { + for (this.tileTickPosition = 0; this.tileTickPosition < this.tickingBlockEntities.size(); this.tileTickPosition++) { + TickingBlockEntity blockEntity = this.tickingBlockEntities.get(this.tileTickPosition); if (blockEntity.isValid()) { blockEntity.tick(); } else { - toRemove.add(blockEntity); + this.tickingBlockEntities.markAsRemoved(this.tileTickPosition); } } - this.tickingBlockEntities.removeAll(toRemove); + this.tickingBlockEntities.removeMarkedEntries(); } this.isTickingBlockEntities = false; } From dec48e9d8e1d338216be1506ffdc5726d73c82aa Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Sun, 14 Sep 2025 21:42:48 +0800 Subject: [PATCH 2/2] =?UTF-8?q?perf(core):=20=E4=BC=98=E5=8C=96=E6=96=B9?= =?UTF-8?q?=E5=9D=97=E5=AE=9E=E4=BD=93=E7=9A=84=20tick=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../craftengine/bukkit/block/entity/BaseParticleBlockEntity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java index 9643a2bfd..fb8d63c83 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BaseParticleBlockEntity.java @@ -18,7 +18,6 @@ public abstract class BaseParticleBlockEntity extends BlockEntity { } public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, BaseParticleBlockEntity particle) { - if (true) return; particle.tickCount++; if (particle.tickCount % 10 != 0) return; particle.animateTick(state, ceWorld.world(), blockPos);