9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-25 09:59:20 +00:00

perf(core): 优化方块实体的 tick 更新机制

This commit is contained in:
jhqwqmc
2025-09-14 21:41:21 +08:00
parent 458ad6d477
commit bb6536644e
3 changed files with 120 additions and 6 deletions

View File

@@ -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);

View File

@@ -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
* <p>
* 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
* <p>
* 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
* <p>
* 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<TickingBlockEntity> {
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<? extends TickingBlockEntity> 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<Integer> 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;
}
}

View File

@@ -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<SectionPos> pendingLightSections = new ArrayList<>();
protected final Set<SectionPos> lightSections = ConcurrentHashMap.newKeySet(128);
protected final List<TickingBlockEntity> tickingBlockEntities = new ArrayList<>();
protected final BlockEntityTickersList tickingBlockEntities = new BlockEntityTickersList();
protected final List<TickingBlockEntity> 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<TickingBlockEntity> 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;
}