diff --git a/sakura-server/minecraft-patches/features/0001-Track-block-changes-and-level-tick-scheduler.patch b/sakura-server/minecraft-patches/features/0001-Track-block-changes-and-level-tick-scheduler.patch index a60e0e9..4bdba95 100644 --- a/sakura-server/minecraft-patches/features/0001-Track-block-changes-and-level-tick-scheduler.patch +++ b/sakura-server/minecraft-patches/features/0001-Track-block-changes-and-level-tick-scheduler.patch @@ -32,49 +32,75 @@ index 24f43dd7d3ffa060409c96882c0416f59dc571cc..4b2ea3c99bcd5452840b26c2ba607a4f protected Level( WritableLevelData levelData, diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 4dcec2e8a3120a3dfa078e8cf6857ba99ca01a2d..4a274798077c4ee0a504a784449a1aef1d89d325 100644 +index 4dcec2e8a3120a3dfa078e8cf6857ba99ca01a2d..c7454fc77f7b269608edf6d6f3e39d9d96e037a0 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java -@@ -136,6 +136,21 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot +@@ -136,6 +136,16 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot return this.getBlockStateFinal(x, y, z); } // Paper end - get block chunk optimisation + // Sakura start - track block changes and tick scheduler -+ private java.util.List blockChangeListeners; -+ -+ public final void updateBlockChangeListeners(final java.util.List listeners) { -+ this.blockChangeListeners = listeners; -+ } -+ -+ private void blockChange(final BlockPos pos, final BlockState newBlock, final BlockState oldBlock) { -+ for (final me.samsuik.sakura.listener.BlockChangeTracker.Listener listener : this.blockChangeListeners) { -+ if (listener.test(this.level, pos, newBlock, oldBlock)) { -+ listener.call(); ++ public final void updateBlockChangeListeners(final it.unimi.dsi.fastutil.ints.Int2ObjectMap> chunkSectionListeners) { ++ chunkSectionListeners.forEach((pos, listeners) -> { ++ final int sectionIndex = this.getSectionIndexFromSectionY(pos); ++ if (sectionIndex >= 0 && sectionIndex < this.sections.length) { ++ this.sections[sectionIndex].setBlockChangeListeners(listeners); + } -+ } ++ }); + } + // Sakura end - track block changes and tick scheduler public LevelChunk(Level level, ChunkPos pos) { this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); -@@ -173,6 +188,7 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot +@@ -173,6 +183,7 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot this.debug = !empty && this.level.isDebug(); this.defaultBlockState = empty ? VOID_AIR_BLOCKSTATE : AIR_BLOCKSTATE; // Paper end - get block chunk optimisation -+ this.blockChangeListeners = level.blockChangeTracker.getListenersForChunk(pos); // Sakura - track block changes and tick scheduler ++ this.updateBlockChangeListeners(level.blockChangeTracker.getListenersForChunk(pos, this.minSection, this.maxSection)); // Sakura - track block changes and tick scheduler } public LevelChunk(ServerLevel level, ProtoChunk chunk, @Nullable LevelChunk.PostLoadProcessor postLoad) { -@@ -421,6 +437,12 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot +@@ -421,6 +432,12 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot if (!section.getBlockState(i, i1, i2).is(block)) { return null; } else { + // Sakura start - track block changes and tick scheduler + if (state.getBlock() != blockState.getBlock()) { -+ this.blockChange(pos, state, blockState); ++ section.blockChange(this.level, pos, state, blockState); + } + // Sakura end - track block changes and tick scheduler + if (!this.level.isClientSide() && (flags & 512) == 0 && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. state.onPlace(this.level, pos, blockState, flag1); } +diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java +index 029d224557613b46f015785a5bbffe49a6f39ec6..512835828f65f496c730122091d9bd117ca5eb78 100644 +--- a/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -43,6 +43,26 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + return this.tickingBlocks; + } + // Paper end - block counting ++ // Sakura start - track block changes and tick scheduler ++ private java.util.List blockChangeListeners = java.util.List.of(); ++ ++ public void setBlockChangeListeners(final java.util.List listeners) { ++ this.blockChangeListeners = listeners; ++ } ++ ++ public void blockChange( ++ final net.minecraft.world.level.Level level, ++ final net.minecraft.core.BlockPos pos, ++ final BlockState newBlock, ++ final BlockState oldBlock ++ ) { ++ for (final me.samsuik.sakura.listener.BlockChangeTracker.Listener listener : this.blockChangeListeners) { ++ if (listener.test(level, pos, newBlock, oldBlock)) { ++ listener.call(); ++ } ++ } ++ } ++ // Sakura end - track block changes and tick scheduler + + private LevelChunkSection(LevelChunkSection section) { + this.nonEmptyBlockCount = section.nonEmptyBlockCount; diff --git a/sakura-server/minecraft-patches/features/0025-Optimise-hopper-ticking.patch b/sakura-server/minecraft-patches/features/0025-Optimise-hopper-ticking.patch index 48ca14b..9156b83 100644 --- a/sakura-server/minecraft-patches/features/0025-Optimise-hopper-ticking.patch +++ b/sakura-server/minecraft-patches/features/0025-Optimise-hopper-ticking.patch @@ -55,7 +55,7 @@ index 1da42c11174bb7eae9a827a17a57d5e7f1d80f09..7aa5ffc32835fab4a91db464d9112785 // Paper start - rewrite chunk system if ((++tickedEntities & 7) == 0) { diff --git a/net/minecraft/world/level/block/HopperBlock.java b/net/minecraft/world/level/block/HopperBlock.java -index c6bec5967f7792ad3b65c0c69fdafd36194d8823..ad097818384adab7fe4d9e83e2295d20f531a5e3 100644 +index 73b602eee0da94f657b4b4cb654147f7ba41c1a4..caa1f6dfafd7466e12efcc99aa447d285b938fe3 100644 --- a/net/minecraft/world/level/block/HopperBlock.java +++ b/net/minecraft/world/level/block/HopperBlock.java @@ -121,6 +121,12 @@ public class HopperBlock extends BaseEntityBlock { @@ -287,10 +287,10 @@ index 28e3b73507b988f7234cbf29c4024c88180d0aef..a0d247aa883553708c4b921582324255 + // Sakura end - optimise hopper ticking } diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 4a274798077c4ee0a504a784449a1aef1d89d325..4e84ead78c91b3453f2549b010de4aa8691967ba 100644 +index c7454fc77f7b269608edf6d6f3e39d9d96e037a0..4b9efc2843bb090871374f0a0bc9d8298760b7ec 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java -@@ -1022,6 +1022,13 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot +@@ -1017,6 +1017,13 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot return BlockEntityType.getKey(this.blockEntity.getType()).toString(); } @@ -304,7 +304,7 @@ index 4a274798077c4ee0a504a784449a1aef1d89d325..4e84ead78c91b3453f2549b010de4aa8 @Override public String toString() { return "Level ticker for " + this.getType() + "@" + this.getPos(); -@@ -1070,6 +1077,13 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot +@@ -1065,6 +1072,13 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot return this.ticker.getType(); } diff --git a/sakura-server/minecraft-patches/features/0030-Optimise-block-counting-for-cannon-entities.patch b/sakura-server/minecraft-patches/features/0030-Optimise-block-counting-for-cannon-entities.patch index fa5126a..88b37fd 100644 --- a/sakura-server/minecraft-patches/features/0030-Optimise-block-counting-for-cannon-entities.patch +++ b/sakura-server/minecraft-patches/features/0030-Optimise-block-counting-for-cannon-entities.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Optimise block counting for cannon entities diff --git a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java -index 54d561e1b4069df7115457accb0763fa7586dddc..29f66ef8355a95b2869988187bf74536343f2ec8 100644 +index 3231c86488806376b086bc26ceba77a509105b81..7bb7eba143f294955eb6b4033b518ea99e428466 100644 --- a/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java +++ b/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java @@ -1941,6 +1941,7 @@ public final class CollisionUtil { @@ -42,7 +42,7 @@ index 54d561e1b4069df7115457accb0763fa7586dddc..29f66ef8355a95b2869988187bf74536 for (int currY = minYIterate; currY <= maxYIterate; ++currY) { final int blockY = currY | (currChunkY << 4); diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 6804055e9344eb2ea0b2dd6318231963376791c2..3a3d22835df907a85c38cd178c1db9d96554b8e0 100644 +index a760757da12210b315baad6c3a3d6f52450e78b2..07352f9ca3e801de5cad99bb31317bc508a011cd 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -621,6 +621,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -79,13 +79,13 @@ index 6804055e9344eb2ea0b2dd6318231963376791c2..3a3d22835df907a85c38cd178c1db9d9 for (int currY = minYIterate; currY <= maxYIterate; ++currY) { final int blockY = currY | (currChunkY << 4); diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java -index 029d224557613b46f015785a5bbffe49a6f39ec6..a13771119488202fa0ad27121616f82553be968c 100644 +index 512835828f65f496c730122091d9bd117ca5eb78..6699f03354449f01291b1acaf334bfb24729f38a 100644 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -43,6 +43,13 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - return this.tickingBlocks; +@@ -63,6 +63,13 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + } } - // Paper end - block counting + // Sakura end - track block changes and tick scheduler + // Sakura start - optimise block counting for cannon entities + private short movingPistonBlocks; + @@ -96,7 +96,7 @@ index 029d224557613b46f015785a5bbffe49a6f39ec6..a13771119488202fa0ad27121616f825 private LevelChunkSection(LevelChunkSection section) { this.nonEmptyBlockCount = section.nonEmptyBlockCount; -@@ -113,6 +120,18 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ +@@ -133,6 +140,18 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ } } @@ -115,7 +115,7 @@ index 029d224557613b46f015785a5bbffe49a6f39ec6..a13771119488202fa0ad27121616f825 final boolean oldTicking = oldState.isRandomlyTicking(); final boolean newTicking = newState.isRandomlyTicking(); if (oldTicking != newTicking) { -@@ -188,6 +207,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ +@@ -208,6 +227,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ this.tickingBlockCount = (short)0; this.tickingFluidCount = (short)0; this.specialCollidingBlocks = (short)0; @@ -123,7 +123,7 @@ index 029d224557613b46f015785a5bbffe49a6f39ec6..a13771119488202fa0ad27121616f825 this.tickingBlocks.clear(); if (this.maybeHas((final BlockState state) -> !state.isAir())) { -@@ -216,6 +236,12 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ +@@ -236,6 +256,12 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ continue; } diff --git a/sakura-server/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java b/sakura-server/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java index db9d114..7b9a22b 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java @@ -1,9 +1,13 @@ package me.samsuik.sakura.listener; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; @@ -16,7 +20,7 @@ import java.util.function.LongConsumer; @NullMarked public final class BlockChangeTracker { - private final Long2ObjectMap> chunkListeners = new Long2ObjectOpenHashMap<>(); + private final Long2ObjectMap> chunkSectionListeners = new Long2ObjectOpenHashMap<>(); private final Long2ObjectMap identifiersInUse = new Long2ObjectOpenHashMap<>(); private final Level level; private long identifier = Long.MIN_VALUE; @@ -26,7 +30,7 @@ public final class BlockChangeTracker { } public long listenForChangesOnce(final BlockChangeFilter filter, final Set positions, final Runnable callback) { - final LongConsumer singleUseCallback = (identifier) -> { + final LongConsumer singleUseCallback = identifier -> { callback.run(); this.stopListening(identifier); }; @@ -36,9 +40,11 @@ public final class BlockChangeTracker { public long listenForChanges(final BlockChangeFilter filter, final Set positions, final LongConsumer callback) { final long identifier = this.identifier++; final Listener listener = new Listener(filter, positions, identifier, callback); - for (final ChunkPos chunkPos : getChunkPositions(positions)) { - this.addListenerToChunk(chunkPos, listener); + + for (final long sectionPos : getSectionPositions(positions)) { + this.addListenerToSection(sectionPos, listener); } + this.identifiersInUse.put(identifier, listener); return identifier; } @@ -47,45 +53,57 @@ public final class BlockChangeTracker { final Listener listener = this.identifiersInUse.remove(identifier); //noinspection ConstantValue if (listener != null) { - for (final ChunkPos chunkPos : getChunkPositions(listener.positions())) { - this.removeListenerFronChunk(chunkPos, listener); + for (final long sectionPos : getSectionPositions(listener.positions())) { + this.removeListenerFromSection(sectionPos, listener); } } } - private void removeListenerFronChunk(final ChunkPos chunkPos, final Listener listener) { - final long chunkKey = chunkPos.toLong(); - final List listeners = this.chunkListeners.computeIfPresent(chunkKey, (k, present) -> { + private static LongOpenHashSet getSectionPositions(final Set positions) { + final LongOpenHashSet sections = new LongOpenHashSet(); + for (final BlockPos pos : positions) { + sections.add(SectionPos.asLong(pos)); + } + return sections; + } + + private void removeListenerFromSection(final long sectionPos, final Listener listener) { + final List listeners = this.chunkSectionListeners.computeIfPresent(sectionPos, (k, present) -> { present.remove(listener); return present.isEmpty() ? null : present; }); - this.updateListeners(chunkPos, Objects.requireNonNullElse(listeners, Collections.emptyList())); + this.updateListeners(sectionPos, Objects.requireNonNullElse(listeners, Collections.emptyList())); } - private void addListenerToChunk(final ChunkPos chunkPos, final Listener listener) { - final long chunkKey = chunkPos.toLong(); - final List listeners = this.chunkListeners.computeIfAbsent(chunkKey, i -> new ArrayList<>()); + private void addListenerToSection(final long sectionPos, final Listener listener) { + final List listeners = this.chunkSectionListeners.computeIfAbsent(sectionPos, k -> new ArrayList<>()); listeners.add(listener); - this.updateListeners(chunkPos, listeners); + this.updateListeners(sectionPos, listeners); } - private void updateListeners(final ChunkPos chunkPos, final List listeners) { - final LevelChunk chunk = ((ServerLevel) this.level).chunkSource.getChunkAtIfLoadedImmediately(chunkPos.x, chunkPos.z); + private void updateListeners(final long sectionPos, final List listeners) { + final int chunkX = SectionPos.x(sectionPos); + final int chunkZ = SectionPos.x(sectionPos); + final LevelChunk chunk = ((ServerLevel) this.level).chunkSource.getChunkAtIfLoadedImmediately(chunkX, chunkZ); + if (chunk != null) { - chunk.updateBlockChangeListeners(List.copyOf(listeners)); + final Int2ObjectMap> sectionListeners = Int2ObjectMaps.singleton( + SectionPos.y(sectionPos), + List.copyOf(listeners) + ); + chunk.updateBlockChangeListeners(sectionListeners); } } - public List getListenersForChunk(final ChunkPos chunkPos) { - return List.copyOf(this.chunkListeners.getOrDefault(chunkPos.toLong(), Collections.emptyList())); - } - - private static Set getChunkPositions(final Set positions) { - final Set chunkPositions = new ObjectOpenHashSet<>(); - for (final BlockPos pos : positions) { - chunkPositions.add(new ChunkPos(pos)); + public Int2ObjectMap> getListenersForChunk(final ChunkPos chunkPos, final int minSection, final int maxSection) { + final Int2ObjectOpenHashMap> sectionListeners = new Int2ObjectOpenHashMap<>(); + for (int sectionY = minSection; sectionY <= maxSection; ++sectionY) { + final long sectionPos = SectionPos.asLong(chunkPos.x, sectionY, chunkPos.z); + final List listeners = this.chunkSectionListeners.getOrDefault(sectionPos, Collections.emptyList()); + sectionListeners.put(sectionY, List.copyOf(listeners)); } - return chunkPositions; + + return sectionListeners; } public interface BlockChangeFilter { diff --git a/sakura-server/src/main/java/me/samsuik/sakura/listener/LevelTickScheduler.java b/sakura-server/src/main/java/me/samsuik/sakura/listener/LevelTickScheduler.java index 200af46..78b42f8 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/listener/LevelTickScheduler.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/listener/LevelTickScheduler.java @@ -67,6 +67,7 @@ public final class LevelTickScheduler { if (tick > gameTime) { continue; } + final List tasks = this.scheduledTasks.remove(tick); this.runTasks(tasks, gameTime); this.removeLater.addAll(tasks);