From a4dbf653b2949732053227f63c681ec2353da75a Mon Sep 17 00:00:00 2001 From: Samsuik <40902469+Samsuik@users.noreply.github.com> Date: Thu, 16 Nov 2023 19:53:15 +0000 Subject: [PATCH] Add redstone cache for vanilla and eigencraft --- .../0004-Sakura-Configuration-Files.patch | 11 +- ...31-Cache-Vanillia-and-Eigen-Redstone.patch | 413 ++++++++++++++++++ 2 files changed, 422 insertions(+), 2 deletions(-) create mode 100644 patches/server/0031-Cache-Vanillia-and-Eigen-Redstone.patch diff --git a/patches/server/0004-Sakura-Configuration-Files.patch b/patches/server/0004-Sakura-Configuration-Files.patch index 9620e1a..92ee547 100644 --- a/patches/server/0004-Sakura-Configuration-Files.patch +++ b/patches/server/0004-Sakura-Configuration-Files.patch @@ -597,15 +597,16 @@ index 0000000000000000000000000000000000000000..ebaa184d795dd57e97c4663f731e1284 +} diff --git a/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java b/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java new file mode 100644 -index 0000000000000000000000000000000000000000..deaee9363553df0ab5aff453c278a51987d4ebb1 +index 0000000000000000000000000000000000000000..14b9617b7da433283def16bb6f98d2abfe99bf8a --- /dev/null +++ b/src/main/java/me/samsuik/sakura/configuration/WorldConfiguration.java -@@ -0,0 +1,107 @@ +@@ -0,0 +1,114 @@ +package me.samsuik.sakura.configuration; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.configuration.Configuration; +import io.papermc.paper.configuration.ConfigurationPart; ++import io.papermc.paper.configuration.NestedSetting; +import io.papermc.paper.configuration.PaperConfigurations; +import io.papermc.paper.configuration.type.number.IntOr; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; @@ -680,6 +681,12 @@ index 0000000000000000000000000000000000000000..deaee9363553df0ab5aff453c278a519 + } + } + ++ public Technical technical; ++ public class Technical extends ConfigurationPart { ++ @NestedSetting({"redstone", "redstone-cache"}) ++ public boolean redstoneCache = false; ++ } ++ + public Players players; + public class Players extends ConfigurationPart { + public Knockback knockback = new Knockback(); diff --git a/patches/server/0031-Cache-Vanillia-and-Eigen-Redstone.patch b/patches/server/0031-Cache-Vanillia-and-Eigen-Redstone.patch new file mode 100644 index 0000000..9b2d67c --- /dev/null +++ b/patches/server/0031-Cache-Vanillia-and-Eigen-Redstone.patch @@ -0,0 +1,413 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik <40902469+Samsuik@users.noreply.github.com> +Date: Thu, 16 Nov 2023 13:38:06 +0000 +Subject: [PATCH] Cache Vanillia and Eigen Redstone + + +diff --git a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java +index 22a2547810d0c029f29685faddf7ac21cde2df0b..0fad13769f5727279430ac7f650f3d4dc0b42526 100644 +--- a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java ++++ b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java +@@ -661,6 +661,7 @@ public class RedstoneWireTurbo { + // restores old behavior, at the cost of bypassing the + // max-chained-neighbor-updates server property. + worldIn.getBlockState(upd.self).neighborChanged(worldIn, upd.self, wire, upd.parent, false); ++ worldIn.redstoneTracker.trackUpdate(upd.self, upd.currentState, upd.parent); // Sakura + } + } + +@@ -776,6 +777,17 @@ public class RedstoneWireTurbo { + // already on-going graph walk being performed by breadthFirstWalk. + return scheduleReentrantNeighborChanged(worldIn, pos, newState, source); + } ++ ++ // Sakura start ++ int oldPower = state.getValue(RedStoneWireBlock.POWER); ++ int newPower = newState.getValue(RedStoneWireBlock.POWER); ++ if (worldIn.redstoneTracker.applyFromCache(pos, oldPower, newPower)) { ++ return newState; ++ } else { ++ worldIn.redstoneTracker.beginTracking(pos, oldPower, newPower); ++ } ++ // Sakura end ++ + // If there are no on-going walks through redstone wire, then start a new walk. + + // If the source of the block update to the redstone wire at 'pos' is known, we can use +@@ -805,6 +817,8 @@ public class RedstoneWireTurbo { + // updates in a breadth first order out from the initial update received for the block at 'pos'. + breadthFirstWalk(worldIn); + ++ worldIn.redstoneTracker.endTracking(); // Sakura - we're done with the breadth walk ++ + // 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. + // In theory, we could cache the neighbor block positions, but that is a separate +@@ -912,6 +926,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.redstoneTracker.trackState(upd.self, state); // Sakura + } + } + +diff --git a/src/main/java/me/samsuik/sakura/redstone/RedstoneTracker.java b/src/main/java/me/samsuik/sakura/redstone/RedstoneTracker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f9ffc75f3c349c6a1cb614ed925b2e90b8bec06f +--- /dev/null ++++ b/src/main/java/me/samsuik/sakura/redstone/RedstoneTracker.java +@@ -0,0 +1,283 @@ ++package me.samsuik.sakura.redstone; ++ ++import it.unimi.dsi.fastutil.HashCommon; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Object2ObjectMap; ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; ++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.InstantNeighborUpdater; ++import net.minecraft.world.level.redstone.NeighborUpdater; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Predicate; ++ ++public class RedstoneTracker { ++ ++ private static final int DEPTH_LIMIT = 512; ++ ++ // this is a fucking horrible hack, you could make a method in block ++ // then override it but that is far more work than I am willing right now. ++ private static final Predicate TECHNICAL_BLOCK = (block) -> { ++ return block instanceof BaseEntityBlock ++ || block instanceof DirectionalBlock ++ || block instanceof HorizontalDirectionalBlock ++ || block instanceof GameMasterBlock ++ || block instanceof BaseRailBlock // rails themselves don't require it but it's variants do ++ || block instanceof NoteBlock ++ || block instanceof DoorBlock ++ || block instanceof RedstoneLampBlock ++ || block instanceof RedstoneTorchBlock ++ || block instanceof RedStoneWireBlock; // required to update any nearby redstone that isn't the line currently being tested ++ }; ++ ++ private final Level level; ++ private final InstantNeighborUpdater updater; ++ private final Long2ObjectOpenHashMap wireCaches = new Long2ObjectOpenHashMap<>(); ++ // important locations, wires and adjacent ++ private final Long2ObjectMap> adjacent = new Long2ObjectOpenHashMap<>(); ++ // last block of physics ++ private final Long2ObjectMap> updates = new Long2ObjectOpenHashMap<>(); ++ ++ private ConnectedWires tracking; ++ private int depth = 0; ++ ++ public RedstoneTracker(Level level) { ++ this.level = level; ++ this.updater = new InstantNeighborUpdater(level); ++ } ++ ++ private static long cacheKey(BlockPos position, int previous, int next) { ++ return position.asLong() ^ HashCommon.mix(previous + next * 31); ++ } ++ ++ private boolean stillValid(BlockPos position, BlockState previous, BlockState next) { ++ return previous.getBlock() == next.getBlock() || previous.isSignalSource() == next.isSignalSource() ++ && previous.isRedstoneConductor(this.level, position) == next.isRedstoneConductor(this.level, position); ++ } ++ ++ public void updateNeighbors(BlockPos position, Block self) { ++ this.updater.updateNeighborsAtExceptFromFacing(position, self, null); ++ } ++ ++ public boolean applyFromCache(BlockPos position, int previous, int next) { ++ if (this.level.sakuraConfig().technical.redstoneCache) { ++ long key = cacheKey(position, previous, next); ++ RedstoneCache cache = this.wireCaches.get(key); ++ ++ if (this.depth == 0 && cache != null && !cache.expiry().isExpired(MinecraftServer.currentTickLong)) { ++ cache.apply(level); ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ public void invalidate(BlockPos position, BlockState previous, BlockState next) { ++ if (!stillValid(position, previous, next)) { ++ long packed = position.asLong(); ++ this.invalidate(this.adjacent.get(packed)); ++ ++ if (TECHNICAL_BLOCK.test(next.getBlock())) { ++ this.invalidate(this.updates.get(packed)); ++ } ++ } ++ } ++ ++ private void invalidate(List caches) { ++ if (caches != null) { ++ caches.forEach(cache -> cache.expiry().refresh(-200)); ++ } ++ } ++ ++ public void trackState(BlockPos position, BlockState state) { ++ if (this.tracking != null) { ++ this.tracking.wireUpdate(position, state); ++ } ++ } ++ ++ public void trackUpdates(BlockPos position, Block block) { ++ if (this.tracking != null) { ++ this.tracking.updateNeighbors(position, block); ++ } ++ } ++ ++ public void trackUpdate(BlockPos position, BlockState state, BlockPos parent) { ++ if (this.tracking != null) { ++ this.tracking.updates().put(position.asLong(), new UpdateState(state.getBlock(), parent)); ++ } ++ } ++ ++ public boolean isTracking() { ++ return this.depth != 0 && this.depth < DEPTH_LIMIT; ++ } ++ ++ public void beginTracking(BlockPos position, int previous, int next) { ++ if (++this.depth == 1 && this.level.sakuraConfig().technical.redstoneCache) { ++ long key = cacheKey(position, previous, next); ++ ++ if (this.wireCaches.containsKey(key)) { ++ // cache already exists ++ return; ++ } ++ ++ this.tracking = new ConnectedWires( ++ position, key, ++ new Object2ObjectOpenHashMap<>(), ++ new Long2ObjectLinkedOpenHashMap<>() ++ ); ++ } ++ } ++ ++ public void endTracking() { ++ if (--this.depth == 0) { ++ if (this.tracking != null) { ++ this.createRedstoneCache(); ++ } ++ ++ this.tracking = null; ++ } ++ } ++ ++ private void createRedstoneCache() { ++ ConnectedWires connected = this.tracking; ++ ++ List updates = new ArrayList<>(connected.updates().size() / 2); ++ ++ Long2ObjectMaps.fastForEach(connected.updates(), entry -> { ++ BlockPos position = BlockPos.of(entry.getLongKey()); ++ UpdateState update = entry.getValue(); ++ ++ // do not update wires that are connected ++ if (connected.wires().containsKey(position)) return; ++ ++ // filter out blocks that do not need updates ++ BlockState state = this.level.getBlockState(position); ++ if (TECHNICAL_BLOCK.test(state.getBlock())) { ++ updates.add(new RedstoneUpdate(position, state, update)); ++ } ++ }); ++ ++ List wires = new ArrayList<>(connected.wires().size()); ++ ++ connected.wires().forEach((pos, state) -> { ++ // is it even worth taking the hit here ++ boolean shapeUpdate = updates.stream() ++ .filter(update -> update.state().is(Blocks.OBSERVER)) ++ .anyMatch(update -> pos.distManhattan(update.position()) == 1); ++ ++ wires.add(new RedstoneWire(pos, state, shapeUpdate)); ++ }); ++ ++ RedstoneCache redstoneCache = new RedstoneCache( ++ wires, updates, connected, ++ new Expiry(MinecraftServer.currentTickLong, 200) ++ ); ++ ++ // put the newly created redstone cache into use ++ this.wireCaches.put(connected.key(), redstoneCache); ++ ++ // used for cache invalidation ++ connected.updates().keySet().forEach(update -> { ++ BlockPos position = BlockPos.of(update); ++ ++ if (wires.stream().anyMatch(wire -> wire.position().distManhattan(position) == 1)) { ++ this.adjacent.computeIfAbsent(update, key -> new ArrayList<>(1)) ++ .add(redstoneCache); ++ } else { ++ this.updates.computeIfAbsent(update, key -> new ArrayList<>(1)) ++ .add(redstoneCache); ++ } ++ }); ++ ++ wires.forEach(wire -> { ++ this.adjacent.computeIfAbsent(wire.position().asLong(), key -> new ArrayList<>(1)) ++ .add(redstoneCache); ++ }); ++ } ++ ++ public void expire(long tick) { ++ if (tick % 100 != 0) return; ++ ++ this.wireCaches.values().removeIf(cache -> { ++ if (cache.expiry().isExpired(tick)) { ++ removeExpiredCache(cache); ++ return true; ++ } ++ ++ return false; ++ }); ++ } ++ ++ private void removeExpiredCache(RedstoneCache cache) { ++ cache.connected().updates().keySet().forEach(update -> { ++ this.removeFromCaches(update, cache); ++ }); ++ ++ cache.connected().wires().keySet().forEach(wire -> { ++ this.removeFromCaches(wire.asLong(), cache); ++ }); ++ } ++ ++ private void removeFromCaches(long packed, RedstoneCache cache) { ++ this.removeCache(this.adjacent, packed, cache); ++ this.removeCache(this.updates, packed, cache); ++ } ++ ++ private void removeCache(Long2ObjectMap> locationMap, long packed, RedstoneCache cache) { ++ List caches = locationMap.get(packed); ++ ++ // in case something goes wrong ++ if (caches == null) return; ++ ++ caches.remove(cache); ++ ++ if (caches.isEmpty()) { ++ locationMap.remove(packed); ++ } ++ } ++ ++ private record UpdateState(Block block, BlockPos position) {} ++ private record RedstoneWire(BlockPos position, BlockState state, boolean shapeUpdate) {} ++ private record RedstoneUpdate(BlockPos position, BlockState state, UpdateState parent) {} ++ ++ private record RedstoneCache(List wires, List updates, ConnectedWires connected, Expiry expiry) { ++ public void apply(Level level) { ++ // Apply cached wire changes ++ wires.forEach(wire -> level.setBlock(wire.position(), wire.state(), wire.shapeUpdate() ? 2 : 18)); ++ ++ // Now update the neighbors. We have to do this after applying wire changes ++ // in case the block we're affecting here, checks the surrounding power level. ++ updates.forEach(update -> { ++ level.neighborChanged(update.position(), update.parent().block(), update.parent().position()); ++ }); ++ ++ // Refresh so the cache won't expire ++ expiry.refresh(MinecraftServer.currentTickLong); ++ } ++ } ++ ++ private record ConnectedWires(BlockPos source, long key, Object2ObjectMap wires, Long2ObjectMap updates) { ++ public void updateNeighbors(BlockPos position, Block block) { ++ for (Direction direction : NeighborUpdater.UPDATE_ORDER) { ++ BlockPos neighbor = position.relative(direction); ++ this.updates.put(neighbor.asLong(), new UpdateState(block, position)); ++ } ++ } ++ ++ public void wireUpdate(BlockPos position, BlockState state) { ++ this.wires.put(position, state); ++ } ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index c061991f23748395fe07205e80b288b166c07c49..d4a0594a8ada4cfbd1f46a9dc0604c79e6ae0cd6 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1582,6 +1582,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop predicate, List into, int limit, int search) { +@@ -1011,6 +1012,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } else { + BlockState iblockdata2 = this.getBlockState(pos); + ++ this.redstoneTracker.invalidate(pos, iblockdata1, state); // Sakura + /* + if (iblockdata2 == iblockdata) { + if (iblockdata1 != iblockdata2) { +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 2b054439b7a763d5a3fbb5dbfe197cb9a9a3525c..7eb617150d278f5b40f097cd402d55fd781e0616 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -375,7 +375,15 @@ public class RedStoneWireBlock extends Block { + } + if (oldPower != i) { + // CraftBukkit end ++ // Sakura start ++ if (world.redstoneTracker.applyFromCache(pos, oldPower, i)) { ++ return; ++ } ++ ++ world.redstoneTracker.beginTracking(pos, oldPower, i); + if (world.getBlockState(pos) == state) { ++ world.redstoneTracker.trackState(pos, state.setValue(RedStoneWireBlock.POWER, i)); ++ // Sakura end + world.setBlock(pos, (BlockState) state.setValue(RedStoneWireBlock.POWER, i), 2); + } + +@@ -396,8 +404,17 @@ public class RedStoneWireBlock extends Block { + while (iterator.hasNext()) { + BlockPos blockposition1 = (BlockPos) iterator.next(); + +- world.updateNeighborsAt(blockposition1, this); ++ // Sakura start ++ world.redstoneTracker.trackUpdates(blockposition1, this); ++ if (world.redstoneTracker.isTracking()) { ++ world.redstoneTracker.updateNeighbors(blockposition1, this); ++ } else { ++ world.updateNeighborsAt(blockposition1, this); ++ } ++ // Sakura end + } ++ ++ world.redstoneTracker.endTracking(); // Sakura + } + + }