9
0
mirror of https://github.com/Samsuik/Sakura.git synced 2025-12-29 03:39:07 +00:00

Reimplement vanilla and eigencraft redstone cache

This commit is contained in:
Samsuik
2025-02-16 23:29:18 +00:00
parent eb8afdf8b4
commit ef4fc336cd

View File

@@ -0,0 +1,798 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samsuik <kfian294ma4@gmail.com>
Date: Fri, 7 Mar 2025 18:23:04 +0000
Subject: [PATCH] Cache vanilla and eigencraft redstone wires
diff --git a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java
index 9f17170179cc99d84ad25a1e838aff3d8cc66f93..cf3b148dacaa866610edc5fe0f61b1a436bb8b8c 100644
--- a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java
+++ b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java
@@ -662,6 +662,7 @@ public class RedstoneWireTurbo {
// restores old behavior, at the cost of bypassing the
// max-chained-neighbor-updates server property.
worldIn.getBlockState(upd.self).handleNeighborChanged(worldIn, upd.self, wire, upd.parent, false);
+ worldIn.redstoneWireCache.trackNeighbor(upd.self, upd.parent); // Sakura - cache vanilla and eigencraft wires
}
}
@@ -805,6 +806,7 @@ public class RedstoneWireTurbo {
// Perform the walk over all directly reachable redstone wire blocks, propagating wire value
// updates in a breadth first order out from the initial update received for the block at 'pos'.
breadthFirstWalk(worldIn);
+ worldIn.redstoneWireCache.stopTracking(); // Sakura - cache vanilla and eigencraft wires
// With the whole search completed, clear the list of all known blocks.
// We do not want to keep around state information that may be changed by other code.
@@ -913,6 +915,7 @@ public class RedstoneWireTurbo {
// bypass the new neighbor update stack.
if (worldIn.setBlock(upd.self, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS))
updateNeighborShapes(worldIn, upd.self, state);
+ worldIn.redstoneWireCache.trackWirePower(upd.self, j, i); // Sakura - cache vanilla and eigencraft wires
}
}
diff --git a/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java b/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java
new file mode 100644
index 0000000000000000000000000000000000000000..314e19499e950265f023a3bdabd9b61319cf651a
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/listener/BlockChangeTracker.java
@@ -0,0 +1,106 @@
+package me.samsuik.sakura.listener;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import net.minecraft.core.BlockPos;
+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>> chunkListeners = new Long2ObjectOpenHashMap<>();
+ private final Long2ObjectMap<Listener> identifiersInUse = new Long2ObjectOpenHashMap<>();
+ private final Level level;
+ private long identifier = Long.MIN_VALUE;
+
+ public BlockChangeTracker(Level level) {
+ this.level = level;
+ }
+
+ public long listenForChangesOnce(BlockChangeFilter filter, Set<BlockPos> positions, Runnable callback) {
+ LongConsumer singleUseCallback = (identifier) -> {
+ callback.run();
+ this.stopListening(identifier);
+ };
+ return this.listenForChanges(filter, positions, singleUseCallback);
+ }
+
+ public long listenForChanges(BlockChangeFilter filter, Set<BlockPos> positions, LongConsumer callback) {
+ long identifier = this.identifier++;
+ Listener listener = new Listener(
+ filter, positions, identifier, callback
+ );
+ for (ChunkPos chunkPos : getChunkPositions(positions)) {
+ this.addListenerToChunk(chunkPos, listener);
+ }
+ this.identifiersInUse.put(identifier, listener);
+ return identifier;
+ }
+
+ public void stopListening(long identifier) {
+ Listener listener = this.identifiersInUse.remove(identifier);
+ if (listener != null) {
+ for (ChunkPos chunkPos : getChunkPositions(listener.positions())) {
+ this.removeListenerFronChunk(chunkPos, listener);
+ }
+ }
+ }
+
+ private void removeListenerFronChunk(ChunkPos chunkPos, Listener listener) {
+ long chunkKey = chunkPos.toLong();
+ List<Listener> listeners = this.chunkListeners.computeIfPresent(chunkKey, (k, present) -> {
+ present.remove(listener);
+ return present.isEmpty() ? null : present;
+ });
+ this.updateListeners(chunkPos, Objects.requireNonNullElse(listeners, Collections.emptyList()));
+ }
+
+ private void addListenerToChunk(ChunkPos chunkPos, Listener listener) {
+ long chunkKey = chunkPos.toLong();
+ List<Listener> listeners = this.chunkListeners.computeIfAbsent(chunkKey, i -> new ArrayList<>());
+ listeners.add(listener);
+ this.updateListeners(chunkPos, listeners);
+ }
+
+ private void updateListeners(ChunkPos chunkPos, List<Listener> listeners) {
+ LevelChunk chunk = ((ServerLevel) this.level).chunkSource.getChunkAtIfLoadedImmediately(chunkPos.x, chunkPos.z);
+ if (chunk != null) {
+ chunk.updateBlockChangeListeners(List.copyOf(listeners));
+ }
+ }
+
+ public List<Listener> getListenersForChunk(ChunkPos chunkPos) {
+ return this.chunkListeners.getOrDefault(chunkPos.toLong(), Collections.emptyList());
+ }
+
+ private static Set<ChunkPos> getChunkPositions(Set<BlockPos> positions) {
+ Set<ChunkPos> chunkPositions = new ObjectOpenHashSet<>();
+ for (BlockPos pos : positions) {
+ chunkPositions.add(new ChunkPos(pos));
+ }
+ return chunkPositions;
+ }
+
+ public interface BlockChangeFilter {
+ boolean test(Level level, BlockPos pos, BlockState newBlock, 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(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) {
+ return this.filter.test(level, pos, newBlock, oldBlock) && this.positions.contains(pos);
+ }
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/redstone/RedstoneNeighborUpdate.java b/src/main/java/me/samsuik/sakura/redstone/RedstoneNeighborUpdate.java
new file mode 100644
index 0000000000000000000000000000000000000000..1447aee927da8974c9cf7c7034fce91bc063b608
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/redstone/RedstoneNeighborUpdate.java
@@ -0,0 +1,11 @@
+package me.samsuik.sakura.redstone;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.RedStoneWireBlock;
+
+public record RedstoneNeighborUpdate(BlockPos position, BlockPos sourcePos) {
+ public void updateBlock(Level level, RedStoneWireBlock wireBlock) {
+ level.getBlockState(this.position).handleNeighborChanged(level, this.position, wireBlock, this.sourcePos, false);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java b/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c2693d74192fc55101b363475f8301ed9d4e1dd
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/redstone/RedstoneNetwork.java
@@ -0,0 +1,191 @@
+package me.samsuik.sakura.redstone;
+
+import io.papermc.paper.configuration.WorldConfiguration;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.objects.*;
+import me.samsuik.sakura.utils.objects.Expiry;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.*;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.redstone.NeighborUpdater;
+import org.jspecify.annotations.NullMarked;
+
+import java.util.BitSet;
+import java.util.List;
+
+@NullMarked
+public final class RedstoneNetwork {
+ private final List<RedstoneWireUpdate> wireUpdates;
+ private final List<RedstoneNeighborUpdate> updates;
+ private final Object2ObjectMap<BlockPos, RedstoneOriginalPower> originalWirePower;
+ private final LongArrayList listeners = new LongArrayList();
+ private final BitSet redundantUpdates = new BitSet();
+ private final Expiry expiry;
+
+ public RedstoneNetwork(List<RedstoneWireUpdate> wireUpdates, List<RedstoneNeighborUpdate> updates, Object2ObjectMap<BlockPos, RedstoneOriginalPower> originalWirePower, Expiry expiry) {
+ this.wireUpdates = new ObjectArrayList<>(wireUpdates);
+ this.updates = new ObjectArrayList<>(updates);
+ this.originalWirePower = new Object2ObjectOpenHashMap<>(originalWirePower);
+ this.expiry = expiry;
+ }
+
+ private static boolean hasRedstoneComponentChanged(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) {
+ return newBlock.isRedstoneConductor(level, pos) != oldBlock.isRedstoneConductor(level, pos)
+ || newBlock.isSignalSource() != oldBlock.isSignalSource();
+ }
+
+ private static boolean hasBlockChanged(Level level, BlockPos pos, BlockState newBlock, BlockState oldBlock) {
+ return newBlock.getBlock() != oldBlock.getBlock();
+ }
+
+ public List<BlockPos> getWirePositions() {
+ return this.wireUpdates.stream()
+ .map(RedstoneWireUpdate::getPosition)
+ .toList();
+ }
+
+ public Expiry getExpiry() {
+ return this.expiry;
+ }
+
+ public boolean isRegistered() {
+ return !this.listeners.isEmpty();
+ }
+
+ public boolean hasWire(BlockPos pos) {
+ return this.originalWirePower.containsKey(pos);
+ }
+
+ public void invalidate(Level level) {
+ for (long identifier : this.listeners) {
+ level.blockChangeTracker.stopListening(identifier);
+ }
+ this.listeners.clear();
+ }
+
+ private void markNeighboringWiresForShapeUpdates(BlockPos pos, Object2ObjectMap<BlockPos, RedstoneWireUpdate> wires) {
+ for (Direction direction : NeighborUpdater.UPDATE_ORDER) {
+ BlockPos neighborPos = pos.relative(direction);
+ RedstoneWireUpdate wireUpdate = wires.get(neighborPos);
+ //noinspection ConstantValue
+ if (wireUpdate != null) {
+ wireUpdate.updateShapes();
+ }
+ }
+ }
+
+ public boolean prepareAndRegisterListeners(Level level, RedstoneNetworkSource networkSource) {
+ Object2ObjectLinkedOpenHashMap<BlockPos, RedstoneWireUpdate> processedWires = new Object2ObjectLinkedOpenHashMap<>();
+ boolean skipWireUpdates = networkSource.redstoneImplementation() != WorldConfiguration.Misc.RedstoneImplementation.VANILLA;
+ for (RedstoneWireUpdate wireUpdate : this.wireUpdates.reversed()) {
+ BlockPos wirePos = wireUpdate.getPosition();
+ //noinspection ConstantValue
+ if (processedWires.putAndMoveToFirst(wirePos, wireUpdate) == null) {
+ // It's possible for the block below the redstone to break while the network is updating
+ BlockState state = level.getBlockState(wirePos);
+ if (state.is(Blocks.PISTON_HEAD)) {
+ return false;
+ }
+ } else if (skipWireUpdates && this.originalWirePower.get(wirePos).firstPower() != wireUpdate.getPower()) {
+ // Filter out wires updates that are not the first and last update
+ // This significantly reduces the amount of updates when unpowering
+ wireUpdate.skipWireUpdate();
+ }
+ }
+
+ for (int i = 0; i < this.updates.size(); ++i) {
+ BlockPos updatePos = this.updates.get(i).position();
+ BlockState state = level.getBlockState(updatePos);
+
+ // Filter out redundant neighbor updates
+ if (state.isAir() || state.liquid() || !state.isSpecialBlock() || processedWires.containsKey(updatePos)) {
+ this.redundantUpdates.set(i);
+ }
+
+ // Look for blocks that actually need shape updates
+ Block block = state.getBlock();
+ if (state.is(Blocks.OBSERVER) || state.liquid() || block instanceof FallingBlock || block instanceof LiquidBlockContainer) {
+ this.markNeighboringWiresForShapeUpdates(updatePos, processedWires);
+ }
+ }
+
+ this.addBlockListeners(level);
+ return true;
+ }
+
+ private void addBlockListeners(Level level) {
+ ObjectOpenHashSet<BlockPos> positions = this.updates.stream()
+ .map(RedstoneNeighborUpdate::position)
+ .collect(ObjectOpenHashSet.toSet());
+ positions.addAll(this.originalWirePower.keySet());
+
+ // Register block change listeners
+ this.listeners.add(level.blockChangeTracker.listenForChangesOnce(
+ RedstoneNetwork::hasRedstoneComponentChanged, positions, () -> this.invalidate(level)
+ ));
+
+ this.listeners.add(level.blockChangeTracker.listenForChangesOnce(
+ RedstoneNetwork::hasBlockChanged, positions, this.redundantUpdates::clear
+ ));
+ }
+
+ private boolean verifyWiresInNetwork(Level level) {
+ for (Object2ObjectMap.Entry<BlockPos, RedstoneOriginalPower> wireEntry : this.originalWirePower.object2ObjectEntrySet()) {
+ BlockState state = level.getBlockState(wireEntry.getKey());
+ if (!state.is(Blocks.REDSTONE_WIRE)) {
+ this.invalidate(level);
+ return false;
+ }
+
+ if (state.getValue(RedStoneWireBlock.POWER) != wireEntry.getValue().originalPower()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void performUpdates(Level level, RedStoneWireBlock wireBlock, int updateFrom, int updateTo) {
+ for (int updateIndex = updateFrom; updateIndex < updateTo; ++updateIndex) {
+ if (!this.redundantUpdates.get(updateIndex)) {
+ this.updates.get(updateIndex).updateBlock(level, wireBlock);
+ }
+ }
+ }
+
+ public boolean applyFromCache(Level level) {
+ this.expiry.refresh(MinecraftServer.currentTick);
+ if (!this.isRegistered() || !this.verifyWiresInNetwork(level)) {
+ return false;
+ }
+
+ RedStoneWireBlock wireBlock = (RedStoneWireBlock) Blocks.REDSTONE_WIRE;
+ int updateFrom = 0;
+
+ for (RedstoneWireUpdate wireUpdate : this.wireUpdates) {
+ if (wireUpdate.canSkipWireUpdate()) {
+ updateFrom = wireUpdate.getUpdateIndex();
+ continue;
+ }
+
+ int updateTo = wireUpdate.getUpdateIndex();
+ this.performUpdates(level, wireBlock, updateFrom, updateTo);
+ updateFrom = updateTo;
+
+ BlockPos wirePos = wireUpdate.getPosition();
+ BlockState state = level.getBlockState(wirePos);
+ BlockState newState = state.setValue(RedStoneWireBlock.POWER, wireUpdate.getPower());
+ if (level.setBlock(wirePos, newState, Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE)) {
+ if (wireUpdate.needsShapeUpdate()) {
+ wireBlock.turbo.updateNeighborShapes(level, wirePos, newState);
+ }
+ }
+ }
+
+ this.performUpdates(level, wireBlock, updateFrom, this.updates.size());
+ return true;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java b/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..018318189ca7632faf7a34094a478d0987f332e0
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/redstone/RedstoneNetworkSource.java
@@ -0,0 +1,19 @@
+package me.samsuik.sakura.redstone;
+
+import io.papermc.paper.configuration.WorldConfiguration;
+import me.samsuik.sakura.local.config.LocalValueConfig;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.Level;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public record RedstoneNetworkSource(WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation,
+ BlockPos position, int updateDepth, int newPower, int oldPower) {
+
+ public static RedstoneNetworkSource createNetworkSource(Level level, LocalValueConfig localConfig, BlockPos pos,
+ int newPower, int oldPower) {
+ WorldConfiguration.Misc.RedstoneImplementation redstoneImplementation = localConfig.redstoneImplementation;
+ int updateDepth = level.neighborUpdater.getUpdateDepth();
+ return new RedstoneNetworkSource(redstoneImplementation, pos, updateDepth, newPower, oldPower);
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/redstone/RedstoneOriginalPower.java b/src/main/java/me/samsuik/sakura/redstone/RedstoneOriginalPower.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7c70342c09c46aa8a06b66ce210536f5932ce2a
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/redstone/RedstoneOriginalPower.java
@@ -0,0 +1,5 @@
+package me.samsuik.sakura.redstone;
+
+public record RedstoneOriginalPower(int originalPower, int firstPower) {
+
+}
diff --git a/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java b/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..909fc12f653e38afccfc87b89da13d255f6e6ec7
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/redstone/RedstoneWireCache.java
@@ -0,0 +1,122 @@
+package me.samsuik.sakura.redstone;
+
+import it.unimi.dsi.fastutil.objects.*;
+import me.samsuik.sakura.local.config.LocalValueConfig;
+import me.samsuik.sakura.utils.objects.Expiry;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.redstone.NeighborUpdater;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+@NullMarked
+public final class RedstoneWireCache {
+ private final Map<RedstoneNetworkSource, RedstoneNetwork> networkCache = new Object2ObjectOpenHashMap<>();
+ private @Nullable RedstoneNetworkSource networkSource;
+ private final List<RedstoneWireUpdate> wireUpdates = new ObjectArrayList<>();
+ private final List<RedstoneNeighborUpdate> updates = new ObjectArrayList<>();
+ private final Object2ObjectMap<BlockPos, RedstoneOriginalPower> originalWirePower = new Object2ObjectOpenHashMap<>();
+ private final Level level;
+ private @Nullable RedstoneNetwork updatingNetwork;
+
+ public RedstoneWireCache(Level level) {
+ this.level = level;
+ }
+
+ public Map<RedstoneNetworkSource, RedstoneNetwork> getNetworkCache() {
+ return this.networkCache;
+ }
+
+ public boolean isWireUpdating(BlockPos pos) {
+ return this.updatingNetwork != null && this.updatingNetwork.hasWire(pos);
+ }
+
+ private boolean isTrackingWireUpdates() {
+ return this.networkSource != null;
+ }
+
+ public boolean tryApplyFromCache(BlockPos pos, int newPower, int oldPower) {
+ LocalValueConfig localConfig = this.level.localConfig().config(pos);
+ if (!localConfig.redstoneCache || this.isTrackingWireUpdates()) {
+ return false;
+ }
+
+ // Ignore any wire changes while updating the network.
+ if (this.updatingNetwork != null) {
+ return true;
+ }
+
+ RedstoneNetworkSource networkSource = RedstoneNetworkSource.createNetworkSource(this.level, localConfig, pos, newPower, oldPower);
+ RedstoneNetwork network = this.networkCache.get(networkSource);
+ if (network != null) {
+ try {
+ this.updatingNetwork = network;
+ return network.applyFromCache(this.level);
+ } finally {
+ this.updatingNetwork = null;
+ }
+ } else {
+ // Start tracking power and neighbor updates
+ this.networkSource = networkSource;
+ return false;
+ }
+ }
+
+ public void trackWirePower(BlockPos pos, int newPower, int oldPower) {
+ if (this.isTrackingWireUpdates()) {
+ this.originalWirePower.putIfAbsent(pos, new RedstoneOriginalPower(oldPower, newPower));
+ this.wireUpdates.add(new RedstoneWireUpdate(pos, newPower, this.updates.size()));
+ }
+ }
+
+ public void trackNeighbor(BlockPos pos, BlockPos sourcePos) {
+ if (this.isTrackingWireUpdates()) {
+ this.updates.add(new RedstoneNeighborUpdate(pos, sourcePos));
+ }
+ }
+
+ public void trackNeighborsAt(BlockPos pos) {
+ if (this.isTrackingWireUpdates()) {
+ for (Direction neighbor : NeighborUpdater.UPDATE_ORDER) {
+ this.updates.add(new RedstoneNeighborUpdate(pos.relative(neighbor), pos));
+ }
+ }
+ }
+
+ public void expire(int tick) {
+ if (tick % 300 != 0) return;
+ this.networkCache.values().removeIf(network -> {
+ if (network.getExpiry().isExpired(tick)) {
+ network.invalidate(this.level);
+ return true;
+ }
+ return false;
+ });
+ }
+
+ public void stopTracking() {
+ if (!this.isTrackingWireUpdates()) {
+ return;
+ }
+
+ // Cache expires if it has not been used in 600 ticks
+ Expiry expiration = new Expiry(MinecraftServer.currentTick, 600);
+ RedstoneNetwork redstoneNetwork = new RedstoneNetwork(
+ this.wireUpdates, this.updates, this.originalWirePower, expiration
+ );
+
+ if (redstoneNetwork.prepareAndRegisterListeners(this.level, this.networkSource)) {
+ this.networkCache.put(this.networkSource, redstoneNetwork);
+ }
+
+ this.wireUpdates.clear();
+ this.updates.clear();
+ this.originalWirePower.clear();
+ this.networkSource = null;
+ }
+}
diff --git a/src/main/java/me/samsuik/sakura/redstone/RedstoneWireUpdate.java b/src/main/java/me/samsuik/sakura/redstone/RedstoneWireUpdate.java
new file mode 100644
index 0000000000000000000000000000000000000000..844576c0b0cc9b0b4b5b10818c85bcb67a3848f6
--- /dev/null
+++ b/src/main/java/me/samsuik/sakura/redstone/RedstoneWireUpdate.java
@@ -0,0 +1,47 @@
+package me.samsuik.sakura.redstone;
+
+import net.minecraft.core.BlockPos;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
+public final class RedstoneWireUpdate {
+ private final BlockPos position;
+ private final int power;
+ private final int updateIndex;
+ private boolean updateShape;
+ private boolean skipWire;
+
+ public RedstoneWireUpdate(BlockPos position, int power, int updateIndex) {
+ this.position = position;
+ this.power = power;
+ this.updateIndex = updateIndex;
+ }
+
+ public BlockPos getPosition() {
+ return this.position;
+ }
+
+ public int getPower() {
+ return this.power;
+ }
+
+ public int getUpdateIndex() {
+ return this.updateIndex;
+ }
+
+ public boolean needsShapeUpdate() {
+ return this.updateShape;
+ }
+
+ public void updateShapes() {
+ this.updateShape = true;
+ }
+
+ public boolean canSkipWireUpdate() {
+ return this.skipWire;
+ }
+
+ public void skipWireUpdate() {
+ this.skipWire = true;
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index af59f640ce1f357b704cf0ce299af56b7253689c..050c569a4c0492d48780d23934df51eb3589e6b1 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1834,7 +1834,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
worldserver.localConfig().expire(currentTick); // Sakura - add local config
worldserver.mergeHandler.expire(currentTick); // Sakura - merge cannon entities
worldserver.densityCache.invalidate(); // Sakura - explosion density cache
- worldserver.redstoneTracker.expire(currentTick); // Sakura
+ worldserver.redstoneWireCache.expire(currentTick); // Sakura - cache vanilla and eigencraft wires
}
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index e97c5c64f915d22f39fd88e41ce80a17392a5159..713b9308558152d49b4beb22c637cd0a081a02f3 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -117,7 +117,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public static final int MAX_ENTITY_SPAWN_Y = 20000000;
public static final int MIN_ENTITY_SPAWN_Y = -20000000;
public final List<TickingBlockEntity> blockEntityTickers = Lists.newArrayList(); // Paper - public
- protected final NeighborUpdater neighborUpdater;
+ public final NeighborUpdater neighborUpdater; // Sakura - cache vanilla and eigencraft wires
private final List<TickingBlockEntity> pendingBlockEntityTickers = Lists.newArrayList();
private boolean tickingBlockEntities;
public final Thread thread;
@@ -862,6 +862,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
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(); // Sakura - explosion density cache
public final me.samsuik.sakura.explosion.durable.DurableBlockManager durabilityManager = new me.samsuik.sakura.explosion.durable.DurableBlockManager(); // Sakura - explosion durable blocks
+ public final me.samsuik.sakura.listener.BlockChangeTracker blockChangeTracker = new me.samsuik.sakura.listener.BlockChangeTracker(this); // Sakura - track block changes and tick scheduler
+ public final me.samsuik.sakura.redstone.RedstoneWireCache redstoneWireCache = new me.samsuik.sakura.redstone.RedstoneWireCache(this); // Sakura - cache vanilla and eigencraft wires
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, Supplier<me.samsuik.sakura.configuration.WorldConfiguration> sakuraWorldConfigCreator, java.util.concurrent.Executor executor) { // Sakura - sakura configuration files// Paper - create paper world config & Anti-Xray
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
index 0bf0ce52424b8806d82d515d61189bb0e8f243b0..d68eafbcf059a6bff81cabbb38b91e24e3645ade 100644
--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
@@ -260,7 +260,7 @@ public class RedStoneWireBlock extends Block {
// Paper start - Optimize redstone (Eigencraft)
// The bulk of the new functionality is found in RedstoneWireTurbo.java
- com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this);
+ public com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this); // Sakura - cache vanilla and eigencraft wires
/*
* Modified version of pre-existing updateSurroundingRedstone, which is called from
@@ -353,14 +353,22 @@ public class RedStoneWireBlock extends Block {
org.bukkit.event.block.BlockRedstoneEvent event = new org.bukkit.event.block.BlockRedstoneEvent(org.bukkit.craftbukkit.block.CraftBlock.at(worldIn, pos1), i, j);
worldIn.getCraftServer().getPluginManager().callEvent(event);
+
j = event.getNewCurrent();
state = state.setValue(POWER, j);
+ // Sakura start - cache vanilla and eigencraft wires
+ if (worldIn.redstoneWireCache.tryApplyFromCache(pos1, j, i)) {
+ return state;
+ }
+ // Sakura end - cache vanilla and eigencraft wires
+
if (worldIn.getBlockState(pos1) == iblockstate) {
// [Space Walker] suppress shape updates and emit those manually to
// bypass the new neighbor update stack.
if (worldIn.setBlock(pos1, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS))
turbo.updateNeighborShapes(worldIn, pos1, state);
+ worldIn.redstoneWireCache.trackWirePower(pos1, j, i); // Sakura - cache vanilla and eigencraft wires
}
}
@@ -381,7 +389,14 @@ public class RedStoneWireBlock extends Block {
}
if (oldPower != i) {
// CraftBukkit end
+ // Sakura start - cache vanilla and eigencraft wires
+ final me.samsuik.sakura.redstone.RedstoneWireCache wireCache = world.redstoneWireCache;
+ if (wireCache.tryApplyFromCache(pos, i, oldPower)) {
+ return;
+ }
if (world.getBlockState(pos) == state) {
+ wireCache.trackWirePower(pos, i, oldPower);
+ // Sakura end - cache vanilla and eigencraft wires
world.setBlock(pos, (BlockState) state.setValue(RedStoneWireBlock.POWER, i), 2);
}
@@ -402,6 +417,7 @@ public class RedStoneWireBlock extends Block {
while (iterator.hasNext()) {
BlockPos blockposition1 = (BlockPos) iterator.next();
+ wireCache.trackNeighborsAt(blockposition1); // Sakura - cache vanilla and eigencraft wires
world.updateNeighborsAt(blockposition1, this);
}
}
@@ -559,7 +575,14 @@ public class RedStoneWireBlock extends Block {
if (this.shouldSignal && direction != Direction.DOWN) {
int i = (Integer) state.getValue(RedStoneWireBlock.POWER);
- return i == 0 ? 0 : (direction != Direction.UP && !((RedstoneSide) this.getConnectionState(world, state, pos).getValue((Property) RedStoneWireBlock.PROPERTY_BY_DIRECTION.get(direction.getOpposite()))).isConnected() ? 0 : i);
+ // Sakura start - cache vanilla and eigencraft wires
+ if (i == 0 || direction == Direction.UP) {
+ return i;
+ }
+ final boolean updating = world instanceof Level level && level.redstoneWireCache.isWireUpdating(pos);
+ final BlockState connectionState = updating ? state : this.getConnectionState(world, state, pos);
+ return connectionState.getValue(PROPERTY_BY_DIRECTION.get(direction.getOpposite())).isConnected() ? i : 0;
+ // Sakura end - cache vanilla and eigencraft wires
} else {
return 0;
}
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index 61d1e3b9033a00da7cccadfcab92f0b211c28b00..d5f1e233f07be2ebb8190b9743499b16196f8e86 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -864,6 +864,13 @@ public abstract class BlockBehaviour implements FeatureElement {
return this.constantAABBCollision;
}
// Paper end - optimise collisions
+ // Sakura start - cache vanilla and eigencraft wires
+ private boolean specialBlock;
+
+ public final boolean isSpecialBlock() {
+ return this.specialBlock;
+ }
+ // Sakura end - cache vanilla and eigencraft wire
protected BlockStateBase(Block block, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<BlockState> codec) {
super(block, propertyMap, codec);
@@ -966,6 +973,7 @@ public abstract class BlockBehaviour implements FeatureElement {
this.constantAABBCollision = null;
}
// Paper end - optimise collisions
+ this.specialBlock = !Block.class.equals(this.owner.getClass()); // Sakura - cache vanilla and eigencraft wires
}
public Block getBlock() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index debd755263d92198b3bafb02cf5eb78f01f0cec1..f22ee34ba062dadc5d56a0276d0145632687ff9d 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -85,6 +85,21 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
private final Int2ObjectMap<GameEventListenerRegistry> gameEventListenerRegistrySections;
private final LevelChunkTicks<Block> blockTicks;
private final LevelChunkTicks<Fluid> fluidTicks;
+ // Sakura start - track block changes and tick scheduler
+ private java.util.List<me.samsuik.sakura.listener.BlockChangeTracker.Listener> blockChangeListeners;
+
+ public final void updateBlockChangeListeners(java.util.List<me.samsuik.sakura.listener.BlockChangeTracker.Listener> listeners) {
+ this.blockChangeListeners = listeners;
+ }
+
+ private void blockChange(BlockPos pos, BlockState newBlock, BlockState oldBlock) {
+ for (me.samsuik.sakura.listener.BlockChangeTracker.Listener listener : this.blockChangeListeners) {
+ if (listener.test(this.level, pos, newBlock, oldBlock)) {
+ listener.call();
+ }
+ }
+ }
+ // Sakura end - track block changes and tick scheduler
public LevelChunk(Level world, ChunkPos pos) {
this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null);
@@ -117,6 +132,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
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
}
// CraftBukkit start
@@ -401,6 +417,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
if (!chunksection.getBlockState(j, k, l).is(block)) {
return null;
} else {
+ this.blockChange(blockposition, iblockdata, iblockdata1); // Sakura - track block changes and tick scheduler
// CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) {
iblockdata.onPlace(this.level, blockposition, iblockdata1, flag);
diff --git a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java
index 106af2b2c7ff72c7549975aef75cdcff8d9a7d97..adc5c58e7a0155f9155282f2d9afdd4c164f5f32 100644
--- a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java
+++ b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java
@@ -25,6 +25,13 @@ public class CollectingNeighborUpdater implements NeighborUpdater {
this.maxChainedNeighborUpdates = maxChainDepth;
}
+ // Sakura start - cache vanilla and eigencraft wires
+ @Override
+ public final int getUpdateDepth() {
+ return this.count;
+ }
+ // Sakura end - cache vanilla and eigencraft wires
+
@Override
public void shapeUpdate(Direction direction, BlockState neighborState, BlockPos pos, BlockPos neighborPos, int flags, int maxUpdateDepth) {
this.addAndRun(
@@ -84,6 +91,7 @@ public class CollectingNeighborUpdater implements NeighborUpdater {
}
}
} finally {
+ this.level.redstoneWireCache.stopTracking(); // Sakura - cache vanilla and eigencraft wires
this.stack.clear();
this.addedThisLayer.clear();
this.count = 0;
diff --git a/src/main/java/net/minecraft/world/level/redstone/NeighborUpdater.java b/src/main/java/net/minecraft/world/level/redstone/NeighborUpdater.java
index e679b40b9628b0eb7152978ef641f9c918c4c8b2..409072eef8cac1670e2afc3a15b82eb882e795aa 100644
--- a/src/main/java/net/minecraft/world/level/redstone/NeighborUpdater.java
+++ b/src/main/java/net/minecraft/world/level/redstone/NeighborUpdater.java
@@ -23,6 +23,12 @@ public interface NeighborUpdater {
Direction[] UPDATE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH};
+ // Sakura start - cache vanilla and eigencraft wires
+ default int getUpdateDepth() {
+ return 0;
+ }
+ // Sakura end - cache vanilla and eigencraft wires
+
void shapeUpdate(Direction direction, BlockState neighborState, BlockPos pos, BlockPos neighborPos, int flags, int maxUpdateDepth);
void neighborChanged(BlockPos pos, Block sourceBlock, BlockPos sourcePos);