mirror of
https://github.com/Samsuik/Sakura.git
synced 2026-01-04 15:31:43 +00:00
rework block state change tracker
This commit is contained in:
@@ -17,7 +17,7 @@ index c6efa1eefd5d47ada2caddda8c9e542571397a15..4dfd1c9bdd43d748449ea941bdb59aa5
|
||||
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
|
||||
|
||||
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
|
||||
index 0832a9edc81d6fbf87801dd6ab97c39c3ebbbea3..7cbb9af035092cf2764913c565a3a9398f09e747 100644
|
||||
index 0832a9edc81d6fbf87801dd6ab97c39c3ebbbea3..6cefbd63f3390ef373c4de91313d54200b91022e 100644
|
||||
--- a/net/minecraft/world/level/Level.java
|
||||
+++ b/net/minecraft/world/level/Level.java
|
||||
@@ -830,6 +830,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
|
||||
@@ -25,82 +25,44 @@ index 0832a9edc81d6fbf87801dd6ab97c39c3ebbbea3..7cbb9af035092cf2764913c565a3a939
|
||||
}
|
||||
// Paper end - optimise random ticking
|
||||
+ // Sakura start - track block changes and tick scheduler
|
||||
+ public final me.samsuik.sakura.listener.LevelTickScheduler levelTickScheduler = new me.samsuik.sakura.listener.LevelTickScheduler(this);
|
||||
+ public final me.samsuik.sakura.listener.BlockChangeTracker blockChangeTracker = new me.samsuik.sakura.listener.BlockChangeTracker(this);
|
||||
+ public final me.samsuik.sakura.scheduler.LevelTickScheduler levelTickScheduler = new me.samsuik.sakura.scheduler.LevelTickScheduler(this);
|
||||
+ public final me.samsuik.sakura.block_change.BlockStateChangeTracker blockStateChangeTracker = new me.samsuik.sakura.block_change.BlockStateChangeTracker(this);
|
||||
+ // Sakura end - track block changes and tick scheduler
|
||||
|
||||
protected Level(
|
||||
WritableLevelData levelData,
|
||||
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
index de59760d27280555a334bda4f436164568cffbd6..47c11ffb876019167895b3b4d1a2e455285253b2 100644
|
||||
index de59760d27280555a334bda4f436164568cffbd6..b3d7a39894a5c86898289f463c343a5547e04508 100644
|
||||
--- a/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
@@ -136,6 +136,16 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot
|
||||
@@ -136,6 +136,13 @@ 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
|
||||
+ public final void updateBlockChangeListeners(final it.unimi.dsi.fastutil.ints.Int2ObjectMap<List<me.samsuik.sakura.listener.BlockChangeTracker.Listener>> chunkSectionListeners) {
|
||||
+ chunkSectionListeners.forEach((pos, listeners) -> {
|
||||
+ final int sectionIndex = this.getSectionIndexFromSectionY(pos);
|
||||
+ if (sectionIndex >= 0 && sectionIndex < this.sections.length) {
|
||||
+ this.sections[sectionIndex].setBlockChangeListeners(listeners);
|
||||
+ }
|
||||
+ });
|
||||
+ private final me.samsuik.sakura.block_change.ChunkBlockStateWatcher blockStateWatcher = new me.samsuik.sakura.block_change.ChunkBlockStateWatcher();
|
||||
+
|
||||
+ public final me.samsuik.sakura.block_change.ChunkBlockStateWatcher getBlockStateWatcher() {
|
||||
+ return this.blockStateWatcher;
|
||||
+ }
|
||||
+ // 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 +183,7 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot
|
||||
@@ -173,6 +180,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.updateBlockChangeListeners(level.blockChangeTracker.getListenersForChunk(pos, this.minSection, this.maxSection)); // Sakura - track block changes and tick scheduler
|
||||
+ this.blockStateWatcher.load(this); // Sakura - track block changes and tick scheduler
|
||||
}
|
||||
|
||||
public LevelChunk(ServerLevel level, ProtoChunk chunk, @Nullable LevelChunk.PostLoadProcessor postLoad) {
|
||||
@@ -423,6 +434,12 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot
|
||||
@@ -423,6 +431,8 @@ 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()) {
|
||||
+ section.blockChange(this.level, pos, state, blockState);
|
||||
+ }
|
||||
+ // Sakura end - track block changes and tick scheduler
|
||||
+ this.blockStateWatcher.blockChange(this.level, pos, state, blockState); // Sakura - 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 d354fad5e9c9e4a16a52dbd8c7eb1f177a75f681..955f97ea45c7b980f0f2c4321d27050bec1eabe5 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<me.samsuik.sakura.listener.BlockChangeTracker.Listener> blockChangeListeners = java.util.List.of();
|
||||
+
|
||||
+ public void setBlockChangeListeners(final java.util.List<me.samsuik.sakura.listener.BlockChangeTracker.Listener> 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;
|
||||
|
||||
@@ -280,12 +280,12 @@ index ef61333619d772a3acba6e2f6997655016b399f9..06e2f16032d615bf0083376ce3496941
|
||||
|
||||
@Nullable
|
||||
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
|
||||
index c51231f40c31b153ac463e31656584e6c70d5a78..e7ca94521052d45bf42f614b9600f607dd9941fb 100644
|
||||
index 02d3abf1ae8e822457febb611a4156398edf1685..294f9148a54c5e0bd00e2ebe3943f216f582ff00 100644
|
||||
--- a/net/minecraft/world/level/Level.java
|
||||
+++ b/net/minecraft/world/level/Level.java
|
||||
@@ -834,6 +834,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
|
||||
public final me.samsuik.sakura.listener.LevelTickScheduler levelTickScheduler = new me.samsuik.sakura.listener.LevelTickScheduler(this);
|
||||
public final me.samsuik.sakura.listener.BlockChangeTracker blockChangeTracker = new me.samsuik.sakura.listener.BlockChangeTracker(this);
|
||||
public final me.samsuik.sakura.scheduler.LevelTickScheduler levelTickScheduler = new me.samsuik.sakura.scheduler.LevelTickScheduler(this);
|
||||
public final me.samsuik.sakura.block_change.BlockStateChangeTracker blockStateChangeTracker = new me.samsuik.sakura.block_change.BlockStateChangeTracker(this);
|
||||
// Sakura end - track block changes and tick scheduler
|
||||
+ public final me.samsuik.sakura.entity.merge.EntityMergeHandler mergeHandler = new me.samsuik.sakura.entity.merge.EntityMergeHandler(); // Sakura - merge cannon entities
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ index a42a0672d56e32baaa594c9c1b9db0e7487e7d59..60fc3ff0b7ed04a2f71d14658783d150
|
||||
|
||||
// Paper start
|
||||
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
|
||||
index e7ca94521052d45bf42f614b9600f607dd9941fb..3abf59c6f672e71509bc41e9b30964cf9c2c7e6c 100644
|
||||
index 294f9148a54c5e0bd00e2ebe3943f216f582ff00..d501e9cca821c20f6ce2366538d95b794c81ef00 100644
|
||||
--- a/net/minecraft/world/level/Level.java
|
||||
+++ b/net/minecraft/world/level/Level.java
|
||||
@@ -835,6 +835,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
|
||||
public final me.samsuik.sakura.listener.BlockChangeTracker blockChangeTracker = new me.samsuik.sakura.listener.BlockChangeTracker(this);
|
||||
public final me.samsuik.sakura.block_change.BlockStateChangeTracker blockStateChangeTracker = new me.samsuik.sakura.block_change.BlockStateChangeTracker(this);
|
||||
// Sakura end - track block changes and tick scheduler
|
||||
public final me.samsuik.sakura.entity.merge.EntityMergeHandler mergeHandler = new me.samsuik.sakura.entity.merge.EntityMergeHandler(); // Sakura - merge cannon entities
|
||||
+ public final me.samsuik.sakura.explosion.density.BlockDensityCache densityCache = new me.samsuik.sakura.explosion.density.BlockDensityCache(this); // Sakura - optimise explosion density cache
|
||||
|
||||
@@ -42,7 +42,7 @@ index 8e6f097b4d17aaaf8eccc16e11ce2bd01ad63322..4baa578a2d277676647ca60487a104f8
|
||||
|
||||
boolean isEmpty();
|
||||
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
|
||||
index d86e523ba9a3f99337dcb11a56f0eaa04da95e0a..e6d6f98627884bd4ecb1266320bc745b34dea344 100644
|
||||
index c0a1f3bbdeb5288dd4c49e00abeb45685103f744..70c192db173f91ca742a29737cf77d7efcafb9ad 100644
|
||||
--- a/net/minecraft/world/level/Level.java
|
||||
+++ b/net/minecraft/world/level/Level.java
|
||||
@@ -1436,7 +1436,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
|
||||
@@ -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 47c11ffb876019167895b3b4d1a2e455285253b2..0e3b46adf54a6d68ad5b809c92b5cc13a7cdbf57 100644
|
||||
index b3d7a39894a5c86898289f463c343a5547e04508..7fb612a6e1f55c9428f6deddfcd1967888f8c55a 100644
|
||||
--- a/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
|
||||
@@ -1019,6 +1019,13 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot
|
||||
@@ -1012,6 +1012,13 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot
|
||||
return BlockEntityType.getKey(this.blockEntity.getType()).toString();
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ index 47c11ffb876019167895b3b4d1a2e455285253b2..0e3b46adf54a6d68ad5b809c92b5cc13
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Level ticker for " + this.getType() + "@" + this.getPos();
|
||||
@@ -1067,6 +1074,13 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot
|
||||
@@ -1060,6 +1067,13 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot
|
||||
return this.ticker.getType();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ index 5d95d8747c4816d27bab6455b3bdfb7b50ae265f..079e887cac1a903af23c1a9ace47b9e3
|
||||
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 45a50997c34562facb20c311d2d88a5fa4f4f670..025502d3b5e8d61fb048fe2859eeb613faa8b68b 100644
|
||||
index 5de63d612b1c2b292e2cf3579ecea71a18fa8b96..b4612fb1d884b8bf43c9a64e36cb58e7c6dc60f3 100644
|
||||
--- a/net/minecraft/world/level/Level.java
|
||||
+++ b/net/minecraft/world/level/Level.java
|
||||
@@ -622,6 +622,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
|
||||
@@ -79,13 +79,13 @@ index 45a50997c34562facb20c311d2d88a5fa4f4f670..025502d3b5e8d61fb048fe2859eeb613
|
||||
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 955f97ea45c7b980f0f2c4321d27050bec1eabe5..db0b34e0e6b132715dd81a185a3401d80ec40708 100644
|
||||
index d354fad5e9c9e4a16a52dbd8c7eb1f177a75f681..0f6c7e63b9bd507f9e0f99986a0f9dd879b1baee 100644
|
||||
--- a/net/minecraft/world/level/chunk/LevelChunkSection.java
|
||||
+++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
|
||||
@@ -63,6 +63,13 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_
|
||||
}
|
||||
@@ -43,6 +43,13 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_
|
||||
return this.tickingBlocks;
|
||||
}
|
||||
// Sakura end - track block changes and tick scheduler
|
||||
// Paper end - block counting
|
||||
+ // Sakura start - optimise block counting for cannon entities
|
||||
+ private short movingPistonBlocks;
|
||||
+
|
||||
@@ -96,7 +96,7 @@ index 955f97ea45c7b980f0f2c4321d27050bec1eabe5..db0b34e0e6b132715dd81a185a3401d8
|
||||
|
||||
private LevelChunkSection(LevelChunkSection section) {
|
||||
this.nonEmptyBlockCount = section.nonEmptyBlockCount;
|
||||
@@ -133,6 +140,18 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_
|
||||
@@ -113,6 +120,18 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ index 955f97ea45c7b980f0f2c4321d27050bec1eabe5..db0b34e0e6b132715dd81a185a3401d8
|
||||
final boolean oldTicking = oldState.isRandomlyTicking();
|
||||
final boolean newTicking = newState.isRandomlyTicking();
|
||||
if (oldTicking != newTicking) {
|
||||
@@ -208,6 +227,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_
|
||||
@@ -188,6 +207,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 955f97ea45c7b980f0f2c4321d27050bec1eabe5..db0b34e0e6b132715dd81a185a3401d8
|
||||
this.tickingBlocks.clear();
|
||||
|
||||
if (this.maybeHas((final BlockState state) -> !state.isAir())) {
|
||||
@@ -236,6 +256,12 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_
|
||||
@@ -216,6 +236,12 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package me.samsuik.sakura.block_change;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public interface BlockFilter {
|
||||
BlockFilter ANY = (level, pos, newBlock, oldBlock) -> true;
|
||||
|
||||
BlockFilter BLOCK_TYPE = (level, pos, newBlock, oldBlock) -> !oldBlock.is(newBlock.getBlock());
|
||||
|
||||
BlockFilter MOVING_BLOCK = (level, pos, newBlock, oldBlock) -> newBlock.is(Blocks.MOVING_PISTON);
|
||||
|
||||
BlockFilter COLLISION = (level, pos, newBlock, oldBlock) -> {
|
||||
if (oldBlock.is(newBlock.getBlock())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VoxelShape oldShape = oldBlock.getCollisionShape(level, pos);
|
||||
final VoxelShape newShape = newBlock.getCollisionShape(level, pos);
|
||||
return !Shapes.equal(oldShape, newShape);
|
||||
};
|
||||
|
||||
BlockFilter REDSTONE_COMPONENT = (level, pos, oldBlock, newBlock) -> {
|
||||
if (oldBlock.is(newBlock.getBlock())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return newBlock.isRedstoneConductor(level, pos) != oldBlock.isRedstoneConductor(level, pos)
|
||||
|| newBlock.isSignalSource() != oldBlock.isSignalSource();
|
||||
};
|
||||
|
||||
boolean test(final Level level, final BlockPos pos, final BlockState newBlock, final BlockState oldBlock);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package me.samsuik.sakura.block_change;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectImmutableList;
|
||||
import me.samsuik.sakura.block_change.callback.BlockChangeCallback;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
@SuppressWarnings("ConstantValue")
|
||||
@NullMarked
|
||||
public final class BlockStateChangeTracker {
|
||||
private final Long2ObjectMap<ListeningToPositions> identifiersInUse = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectMap<LongOpenHashSet> chunkAssociatedIdentifiers = new Long2ObjectOpenHashMap<>();
|
||||
private final Level level;
|
||||
private long identifier = Long.MIN_VALUE;
|
||||
|
||||
public BlockStateChangeTracker(final Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public long firstChange(final Set<BlockPos> positions, final BlockFilter filter, final Runnable callback) {
|
||||
final LongConsumer removeAfterUse = identifier -> {
|
||||
callback.run();
|
||||
this.stopListening(identifier);
|
||||
};
|
||||
|
||||
return this.listenForChanges(positions, filter, BlockChangeCallback.identifier(removeAfterUse));
|
||||
}
|
||||
|
||||
public long allChanges(final Set<BlockPos> positions, final BlockFilter filter, final LongConsumer callback) {
|
||||
return this.listenForChanges(positions, filter, BlockChangeCallback.identifier(callback));
|
||||
}
|
||||
|
||||
public long listenForChanges(final Set<BlockPos> positions, final BlockFilter filter, final BlockChangeCallback callback) {
|
||||
final ObjectImmutableList<BlockPos> immutablePositions = new ObjectImmutableList<>(positions);
|
||||
final long identifier = this.identifier++;
|
||||
final ListeningToPositions listener = new ListeningToPositions(immutablePositions, identifier, filter, callback);
|
||||
|
||||
for (final BlockPos pos : immutablePositions) {
|
||||
final long chunkKey = ChunkPos.asLong(pos);
|
||||
final LongOpenHashSet chunkIdentifiers = this.chunkAssociatedIdentifiers.computeIfAbsent(chunkKey, k -> new LongOpenHashSet());
|
||||
|
||||
if (!chunkIdentifiers.add(identifier)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final LevelChunk chunk = this.level.getChunkIfLoaded(pos);
|
||||
if (chunk != null) {
|
||||
chunk.getBlockStateWatcher().startListening(listener);
|
||||
}
|
||||
}
|
||||
|
||||
this.identifiersInUse.put(identifier, listener);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public void stopListening(final long identifier) {
|
||||
final ListeningToPositions listener = this.identifiersInUse.remove(identifier);
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final BlockPos pos : listener.positions()) {
|
||||
final long chunkKey = ChunkPos.asLong(pos);
|
||||
final LongOpenHashSet chunkIdentifiers = this.chunkAssociatedIdentifiers.get(chunkKey);
|
||||
|
||||
if (chunkIdentifiers == null || !chunkIdentifiers.remove(identifier)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final LevelChunk chunk = this.level.getChunkIfLoaded(pos);
|
||||
if (chunk != null) {
|
||||
chunk.getBlockStateWatcher().stopListening(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ListeningToPositions> getListenersAtChunk(final LevelChunk chunk) {
|
||||
final LongOpenHashSet identifiers = this.chunkAssociatedIdentifiers.get(chunk.coordinateKey);
|
||||
if (identifiers == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final List<ListeningToPositions> listeners = new ArrayList<>();
|
||||
for (final long identifier : identifiers) {
|
||||
listeners.add(this.identifiersInUse.get(identifier));
|
||||
}
|
||||
|
||||
return listeners;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package me.samsuik.sakura.block_change;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@NullMarked
|
||||
public final class ChunkBlockStateWatcher {
|
||||
private final Map<BlockPos, List<ListeningToPositions>> positionListeners = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
public void blockChange(
|
||||
final Level level,
|
||||
final BlockPos pos,
|
||||
final BlockState newBlock,
|
||||
final BlockState oldBlock
|
||||
) {
|
||||
final List<ListeningToPositions> changes = this.positionListeners.get(pos);
|
||||
if (changes != null) {
|
||||
for (final ListeningToPositions change : changes) {
|
||||
change.onChange(level, pos, newBlock, oldBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void load(final LevelChunk chunk) {
|
||||
for (final ListeningToPositions listener : chunk.level.blockStateChangeTracker.getListenersAtChunk(chunk)) {
|
||||
this.startListening(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void startListening(final ListeningToPositions listener) {
|
||||
for (final BlockPos pos : listener.positions()) {
|
||||
this.positionListeners.computeIfAbsent(pos, p -> new CopyOnWriteArrayList<>()).add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopListening(final ListeningToPositions listener) {
|
||||
for (final BlockPos pos : listener.positions()) {
|
||||
final List<ListeningToPositions> listeners = this.positionListeners.get(pos);
|
||||
if (listeners != null) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.samsuik.sakura.block_change;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectImmutableList;
|
||||
import me.samsuik.sakura.block_change.callback.BlockChangeCallback;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public record ListeningToPositions(
|
||||
ObjectImmutableList<BlockPos> positions,
|
||||
long identifier,
|
||||
BlockFilter filter,
|
||||
BlockChangeCallback callback
|
||||
) {
|
||||
public void onChange(final Level level, final BlockPos pos, final BlockState newBlock, final BlockState oldBlock) {
|
||||
if (this.filter.test(level, pos, newBlock, oldBlock)) {
|
||||
this.callback.call(pos, newBlock, oldBlock, this.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.samsuik.sakura.block_change.callback;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
@NullMarked
|
||||
public interface BlockChangeCallback {
|
||||
static BlockChangeCallback identifier(final LongConsumer identifierConsumer) {
|
||||
return (pos, newBlock, oldBlock, identifier) -> identifierConsumer.accept(identifier);
|
||||
}
|
||||
|
||||
void call(final BlockPos pos, final BlockState newBlock, final BlockState oldBlock, final long identifier);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package me.samsuik.sakura.block_change.callback;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
@NullMarked
|
||||
public final class CallbackAfterChanges implements BlockChangeCallback {
|
||||
private final int count;
|
||||
private int changes = 0;
|
||||
private final LongConsumer callback;
|
||||
|
||||
public CallbackAfterChanges(final int count, final LongConsumer callback) {
|
||||
this.count = count;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.changes = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(
|
||||
final BlockPos pos,
|
||||
final BlockState newBlock,
|
||||
final BlockState oldBlock,
|
||||
final long identifier
|
||||
) {
|
||||
if (++this.changes >= this.count) {
|
||||
this.callback.accept(identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.samsuik.sakura.block_change.callback;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@NullMarked
|
||||
public final class TrackModifiedStates implements BlockChangeCallback {
|
||||
private final Map<BlockPos, BlockState> expectedStates = new HashMap<>();
|
||||
private int count = 0;
|
||||
|
||||
public TrackModifiedStates(final Map<BlockPos, BlockState> states) {
|
||||
this.expectedStates.putAll(states);
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return this.count != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(
|
||||
final BlockPos pos,
|
||||
final BlockState newBlock,
|
||||
final BlockState oldBlock,
|
||||
final long identifier
|
||||
) {
|
||||
final BlockState expectedState = this.expectedStates.get(pos);
|
||||
if (expectedState == newBlock) {
|
||||
this.count--;
|
||||
} else if (expectedState == oldBlock) {
|
||||
this.count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
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.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;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
@NullMarked
|
||||
public final class BlockChangeTracker {
|
||||
private final Long2ObjectMap<List<Listener>> chunkSectionListeners = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectMap<Listener> identifiersInUse = new Long2ObjectOpenHashMap<>();
|
||||
private final Level level;
|
||||
private long identifier = Long.MIN_VALUE;
|
||||
|
||||
public BlockChangeTracker(final Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public long listenForChangesOnce(final BlockChangeFilter filter, final Set<BlockPos> positions, final Runnable callback) {
|
||||
final LongConsumer singleUseCallback = identifier -> {
|
||||
callback.run();
|
||||
this.stopListening(identifier);
|
||||
};
|
||||
return this.listenForChanges(filter, positions, singleUseCallback);
|
||||
}
|
||||
|
||||
public long listenForChanges(final BlockChangeFilter filter, final Set<BlockPos> positions, final LongConsumer callback) {
|
||||
final long identifier = this.identifier++;
|
||||
final Listener listener = new Listener(filter, positions, identifier, callback);
|
||||
|
||||
for (final long sectionPos : getSectionPositions(positions)) {
|
||||
this.addListenerToSection(sectionPos, listener);
|
||||
}
|
||||
|
||||
this.identifiersInUse.put(identifier, listener);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public void stopListening(final long identifier) {
|
||||
final Listener listener = this.identifiersInUse.remove(identifier);
|
||||
//noinspection ConstantValue
|
||||
if (listener != null) {
|
||||
for (final long sectionPos : getSectionPositions(listener.positions())) {
|
||||
this.removeListenerFromSection(sectionPos, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static LongOpenHashSet getSectionPositions(final Set<BlockPos> 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<Listener> listeners = this.chunkSectionListeners.computeIfPresent(sectionPos, (k, present) -> {
|
||||
present.remove(listener);
|
||||
return present.isEmpty() ? null : present;
|
||||
});
|
||||
this.updateListeners(sectionPos, Objects.requireNonNullElse(listeners, Collections.emptyList()));
|
||||
}
|
||||
|
||||
private void addListenerToSection(final long sectionPos, final Listener listener) {
|
||||
final List<Listener> listeners = this.chunkSectionListeners.computeIfAbsent(sectionPos, k -> new ArrayList<>());
|
||||
listeners.add(listener);
|
||||
this.updateListeners(sectionPos, listeners);
|
||||
}
|
||||
|
||||
private void updateListeners(final long sectionPos, final List<Listener> 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) {
|
||||
final Int2ObjectMap<List<Listener>> sectionListeners = Int2ObjectMaps.singleton(
|
||||
SectionPos.y(sectionPos),
|
||||
List.copyOf(listeners)
|
||||
);
|
||||
chunk.updateBlockChangeListeners(sectionListeners);
|
||||
}
|
||||
}
|
||||
|
||||
public Int2ObjectMap<List<Listener>> getListenersForChunk(final ChunkPos chunkPos, final int minSection, final int maxSection) {
|
||||
final Int2ObjectOpenHashMap<List<Listener>> sectionListeners = new Int2ObjectOpenHashMap<>();
|
||||
for (int sectionY = minSection; sectionY <= maxSection; ++sectionY) {
|
||||
final long sectionPos = SectionPos.asLong(chunkPos.x, sectionY, chunkPos.z);
|
||||
final List<Listener> listeners = this.chunkSectionListeners.getOrDefault(sectionPos, Collections.emptyList());
|
||||
sectionListeners.put(sectionY, List.copyOf(listeners));
|
||||
}
|
||||
|
||||
return sectionListeners;
|
||||
}
|
||||
|
||||
public interface BlockChangeFilter {
|
||||
BlockChangeFilter ANY = (l, p, n, o) -> true;
|
||||
|
||||
BlockChangeFilter REDSTONE_COMPONENT = (level, pos, oldBlock, newBlock) -> {
|
||||
return newBlock.isRedstoneConductor(level, pos) != oldBlock.isRedstoneConductor(level, pos)
|
||||
|| newBlock.isSignalSource() != oldBlock.isSignalSource();
|
||||
};
|
||||
|
||||
boolean test(final Level level, final BlockPos pos, final BlockState newBlock, final BlockState oldBlock);
|
||||
}
|
||||
|
||||
public record Listener(BlockChangeFilter filter, Set<BlockPos> positions, long identifier, LongConsumer callback) {
|
||||
public void call() {
|
||||
this.callback.accept(this.identifier);
|
||||
}
|
||||
|
||||
public boolean test(final Level level, final BlockPos pos, final BlockState newBlock, final BlockState oldBlock) {
|
||||
return this.filter.test(level, pos, newBlock, oldBlock)
|
||||
&& this.positions.contains(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ package me.samsuik.sakura.redstone.cache;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import me.samsuik.sakura.listener.BlockChangeTracker;
|
||||
import me.samsuik.sakura.block_change.BlockFilter;
|
||||
import me.samsuik.sakura.block_change.BlockStateChangeTracker;
|
||||
import me.samsuik.sakura.utils.TickExpiry;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
@@ -57,9 +58,9 @@ public final class RedstoneNetwork {
|
||||
return this.originalWirePower.containsKey(pos);
|
||||
}
|
||||
|
||||
public void invalidate(final Level level) {
|
||||
public void invalidate(final BlockStateChangeTracker tracker) {
|
||||
for (final long identifier : this.listeners) {
|
||||
level.blockChangeTracker.stopListening(identifier);
|
||||
tracker.stopListening(identifier);
|
||||
}
|
||||
this.listeners.clear();
|
||||
}
|
||||
@@ -134,17 +135,22 @@ public final class RedstoneNetwork {
|
||||
}
|
||||
|
||||
private void addBlockListeners(final Level level) {
|
||||
ObjectOpenHashSet<BlockPos> positions = new ObjectOpenHashSet<>(this.neighborUpdates);
|
||||
final ObjectOpenHashSet<BlockPos> positions = new ObjectOpenHashSet<>(this.neighborUpdates);
|
||||
positions.addAll(this.originalWirePower.keySet());
|
||||
positions.remove(null);
|
||||
|
||||
// Register block change listeners
|
||||
this.listeners.add(level.blockChangeTracker.listenForChangesOnce(
|
||||
BlockChangeTracker.BlockChangeFilter.REDSTONE_COMPONENT, positions, () -> this.invalidate(level)
|
||||
final BlockStateChangeTracker tracker = level.blockStateChangeTracker;
|
||||
this.listeners.add(tracker.firstChange(
|
||||
positions,
|
||||
BlockFilter.REDSTONE_COMPONENT,
|
||||
() -> this.invalidate(tracker)
|
||||
));
|
||||
|
||||
this.listeners.add(level.blockChangeTracker.listenForChangesOnce(
|
||||
BlockChangeTracker.BlockChangeFilter.ANY, positions, this::allowRedundantNeighborUpdates
|
||||
this.listeners.add(tracker.firstChange(
|
||||
positions,
|
||||
BlockFilter.BLOCK_TYPE,
|
||||
this::allowRedundantNeighborUpdates
|
||||
));
|
||||
}
|
||||
|
||||
@@ -152,7 +158,7 @@ public final class RedstoneNetwork {
|
||||
for (final Object2ObjectMap.Entry<BlockPos, RedstoneOriginalPower> wireEntry : this.originalWirePower.object2ObjectEntrySet()) {
|
||||
final BlockState state = level.getBlockState(wireEntry.getKey());
|
||||
if (!state.is(Blocks.REDSTONE_WIRE)) {
|
||||
this.invalidate(level);
|
||||
this.invalidate(level.blockStateChangeTracker);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ public final class RedstoneWireCache {
|
||||
public void expire(final long tick) {
|
||||
this.networkCache.values().removeIf(network -> {
|
||||
if (network.getExpiry().isExpired(tick)) {
|
||||
network.invalidate(this.level);
|
||||
network.invalidate(this.level.blockStateChangeTracker);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.samsuik.sakura.listener;
|
||||
package me.samsuik.sakura.scheduler;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
Reference in New Issue
Block a user