diff --git a/patches/server/Add-Alternate-Current-redstone-implementation.patch b/patches/server/Add-Alternate-Current-redstone-implementation.patch new file mode 100644 index 0000000000..f5e9b30157 --- /dev/null +++ b/patches/server/Add-Alternate-Current-redstone-implementation.patch @@ -0,0 +1,2414 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Space Walker +Date: Tue, 5 Apr 2022 01:01:13 +0200 +Subject: [PATCH] Add Alternate Current redstone implementation + +Author: Space Walker + +Original license: MIT +Original project: https://github.com/SpaceWalkerRS/alternate-current + +This patch adds Alternate Current's redstone implementation as an alternative to vanilla and Eigencraft's. +Performance of (de)powering redstone dust is many times faster than vanilla, and even exceeds Eigencraft. +Similar to Eigencraft, Alternate Current heavily changes the update order of redstone dust. This means any contraption that +is location dependent in vanilla will either work everywhere or nowhere when using Alternate Current/Eigencraft. Beyond that +parity issues should be rare for both implementations, though Alternate Current has not been tested as thoroughly, so I +cannot comment on how the two compare in that aspect. + +Alternate Current is more invasive than Eigencraft, though some of the modifications can be removed with only a minor +performance penalty in a small number of cases. Alternate Current needs the following modifications: +* Level/ServerLevel: Each level has its own 'wire handler' that handles redstone dust power changes. +* RedStoneWireBlock: Replace calls to vanilla's or Eigencraft's methods for handling power changes with calls to +Alternate Current's wire handler. +* (optional) Every power emitter's block class: new methods added to these classes make it easier for Alternate Current +to check if any block is a potential power source. + +The use-faster-eigencraft-redstone config has been replaced with the redstone-implementation config, which can be used to +switch between the vanilla, Eigencraft, and Alternate Current implementations. + +diff --git a/src/main/java/alternate/current/wire/LevelHelper.java b/src/main/java/alternate/current/wire/LevelHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/LevelHelper.java +@@ -0,0 +0,0 @@ ++package alternate.current.wire; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++ ++import org.bukkit.event.block.BlockRedstoneEvent; ++ ++public class LevelHelper { ++ ++ static int doRedstoneEvent(ServerLevel level, BlockPos pos, int prevPower, int newPower) { ++ BlockRedstoneEvent event = new BlockRedstoneEvent(level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), prevPower, newPower); ++ level.getCraftServer().getPluginManager().callEvent(event); ++ ++ return event.getNewCurrent(); ++ } ++ ++ /** ++ * An optimized version of Level.setBlock. Since this method is only used to ++ * update redstone wire block states, lighting checks, height map updates, and ++ * block entity updates are omitted. ++ */ ++ static boolean setWireState(ServerLevel level, BlockPos pos, BlockState state, boolean updateNeighborShapes) { ++ int y = pos.getY(); ++ ++ if (y < level.getMinBuildHeight() || y >= level.getMaxBuildHeight()) { ++ return false; ++ } ++ ++ int x = pos.getX(); ++ int z = pos.getZ(); ++ int index = level.getSectionIndex(y); ++ ++ ChunkAccess chunk = level.getChunk(x >> 4, z >> 4, ChunkStatus.FULL, true); ++ LevelChunkSection section = chunk.getSections()[index]; ++ ++ if (section == null) { ++ return false; // we should never get here ++ } ++ ++ BlockState prevState = section.setBlockState(x & 15, y & 15, z & 15, state); ++ ++ if (state == prevState) { ++ return false; ++ } ++ ++ // notify clients of the BlockState change ++ level.getChunkSource().blockChanged(pos); ++ // mark the chunk for saving ++ chunk.setUnsaved(true); ++ ++ if (updateNeighborShapes) { ++ prevState.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_CLIENTS); ++ state.updateNeighbourShapes(level, pos, Block.UPDATE_CLIENTS); ++ state.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_CLIENTS); ++ } ++ ++ return true; ++ } ++} +diff --git a/src/main/java/alternate/current/wire/Node.java b/src/main/java/alternate/current/wire/Node.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/Node.java +@@ -0,0 +0,0 @@ ++package alternate.current.wire; ++ ++import java.util.Arrays; ++ ++import alternate.current.wire.WireHandler.Directions; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++ ++/** ++ * A Node represents a block in the world. It also holds a few other pieces of ++ * information that speed up the calculations in the WireHandler class. ++ * ++ * @author Space Walker ++ */ ++public class Node { ++ ++ // flags that encode the Node type ++ private static final int CONDUCTOR = 0b01; ++ private static final int SOURCE = 0b10; ++ ++ final ServerLevel level; ++ final Node[] neighbors; ++ ++ BlockPos pos; ++ BlockState state; ++ boolean invalid; ++ ++ private int flags; ++ ++ Node(ServerLevel level) { ++ this.level = level; ++ this.neighbors = new Node[Directions.ALL.length]; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (!(obj instanceof Node)) { ++ return false; ++ } ++ ++ Node node = (Node)obj; ++ ++ return level == node.level && pos.equals(node.pos); ++ } ++ ++ @Override ++ public int hashCode() { ++ return pos.hashCode(); ++ } ++ ++ Node update(BlockPos pos, BlockState state, boolean clearNeighbors) { ++ if (state.is(Blocks.REDSTONE_WIRE)) { ++ throw new IllegalStateException("Cannot update a regular Node to a WireNode!"); ++ } ++ ++ if (clearNeighbors) { ++ Arrays.fill(neighbors, null); ++ } ++ ++ this.pos = pos.immutable(); ++ this.state = state; ++ this.invalid = false; ++ ++ this.flags = 0; ++ ++ if (this.state.isRedstoneConductor(this.level, this.pos)) { ++ this.flags |= CONDUCTOR; ++ } ++ if (this.state.isSignalSource()) { ++ this.flags |= SOURCE; ++ } ++ ++ return this; ++ } ++ ++ public boolean isWire() { ++ return false; ++ } ++ ++ public boolean isConductor() { ++ return (flags & CONDUCTOR) != 0; ++ } ++ ++ public boolean isSignalSource() { ++ return (flags & SOURCE) != 0; ++ } ++ ++ public WireNode asWire() { ++ throw new UnsupportedOperationException("Not a WireNode!"); ++ } ++} +diff --git a/src/main/java/alternate/current/wire/PowerQueue.java b/src/main/java/alternate/current/wire/PowerQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/PowerQueue.java +@@ -0,0 +0,0 @@ ++package alternate.current.wire; ++ ++import java.util.AbstractQueue; ++import java.util.Arrays; ++import java.util.Iterator; ++ ++import net.minecraft.world.level.redstone.Redstone; ++ ++public class PowerQueue extends AbstractQueue { ++ ++ private static final int OFFSET = -Redstone.SIGNAL_MIN; ++ ++ /** The last wire for each power level. */ ++ private final WireNode[] tails; ++ ++ private WireNode head; ++ private WireNode tail; ++ ++ private int size; ++ ++ public PowerQueue() { ++ this.tails = new WireNode[(Redstone.SIGNAL_MAX + 1) - Redstone.SIGNAL_MIN]; ++ } ++ ++ @Override ++ public boolean offer(WireNode wire) { ++ if (wire == null) { ++ throw new NullPointerException(); ++ } ++ ++ int power = wire.nextPower(); ++ ++ if (contains(wire)) { ++ if (wire.power == power) { ++ // already queued for this power; exit ++ return false; ++ } else { ++ // already queued for different power; move it ++ move(wire, power); ++ } ++ } else { ++ insert(wire, power); ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public WireNode poll() { ++ if (head == null) { ++ return null; ++ } ++ ++ WireNode wire = head; ++ WireNode next = wire.next; ++ ++ if (next == null) { ++ clear(); // reset the tails array ++ } else { ++ if (wire.power != next.power) { ++ // if the head is also a tail, its entry in the array ++ // can be cleared; there is no previous wire with the ++ // same power to take its place. ++ tails[wire.power + OFFSET] = null; ++ } ++ ++ wire.next = null; ++ next.prev = null; ++ head = next; ++ ++ size--; ++ } ++ ++ return wire; ++ } ++ ++ @Override ++ public WireNode peek() { ++ return head; ++ } ++ ++ @Override ++ public void clear() { ++ for (WireNode wire = head; wire != null; ) { ++ WireNode w = wire; ++ wire = wire.next; ++ ++ w.prev = null; ++ w.next = null; ++ } ++ ++ head = null; ++ tail = null; ++ ++ Arrays.fill(tails, null); ++ ++ size = 0; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int size() { ++ return size; ++ } ++ ++ public boolean contains(WireNode wire) { ++ return wire == head || wire.prev != null; ++ } ++ ++ private void move(WireNode wire, int power) { ++ remove(wire); ++ insert(wire, power); ++ } ++ ++ private void remove(WireNode wire) { ++ if (wire == tail || wire.power != wire.next.power) { ++ // assign a new tail for this wire's power ++ if (wire == head || wire.power != wire.prev.power) { ++ // there is no other wire with the same power; clear ++ tails[wire.power + OFFSET] = null; ++ } else { ++ // the previous wire in the queue becomes the tail ++ tails[wire.power + OFFSET] = wire.prev; ++ } ++ } ++ ++ if (wire == head) { ++ head = wire.next; ++ } else { ++ wire.prev.next = wire.next; ++ } ++ if (wire == tail) { ++ tail = wire.prev; ++ } else { ++ wire.next.prev = wire.prev; ++ } ++ ++ wire.prev = null; ++ wire.next = null; ++ ++ size--; ++ } ++ ++ private void insert(WireNode wire, int power) { ++ // store the power for which this wire is queued ++ wire.power = power; ++ ++ // wires are sorted by power (highest to lowest) ++ // wires with the same power are ordered FIFO ++ if (head == null) { ++ // first element in this queue \o/ ++ head = tail = wire; ++ } else if (wire.power > head.power) { ++ linkHead(wire); ++ } else if (wire.power <= tail.power) { ++ linkTail(wire); ++ } else { ++ // since the wire is neither the head nor the tail ++ // findPrev is guaranteed to find a non-null element ++ linkAfter(findPrev(wire), wire); ++ } ++ ++ tails[power + OFFSET] = wire; ++ ++ size++; ++ } ++ ++ private void linkHead(WireNode wire) { ++ wire.next = head; ++ head.prev = wire; ++ head = wire; ++ } ++ ++ private void linkTail(WireNode wire) { ++ tail.next = wire; ++ wire.prev = tail; ++ tail = wire; ++ } ++ ++ private void linkAfter(WireNode prev, WireNode wire) { ++ linkBetween(prev, wire, prev.next); ++ } ++ ++ private void linkBetween(WireNode prev, WireNode wire, WireNode next) { ++ prev.next = wire; ++ wire.prev = prev; ++ ++ wire.next = next; ++ next.prev = wire; ++ } ++ ++ private WireNode findPrev(WireNode wire) { ++ WireNode prev = null; ++ ++ for (int i = wire.power + OFFSET; i < tails.length; i++) { ++ prev = tails[i]; ++ ++ if (prev != null) { ++ break; ++ } ++ } ++ ++ return prev; ++ } ++} +diff --git a/src/main/java/alternate/current/wire/WireConnection.java b/src/main/java/alternate/current/wire/WireConnection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireConnection.java +@@ -0,0 +0,0 @@ ++package alternate.current.wire; ++ ++/** ++ * This class represents a connection between some WireNode (the 'owner') and a ++ * neighboring WireNode. Two wires are considered to be connected if power can ++ * flow from one wire to the other (and/or vice versa). ++ * ++ * @author Space Walker ++ */ ++public class WireConnection { ++ ++ /** The connected wire. */ ++ final WireNode wire; ++ /** Cardinal direction to the connected wire. */ ++ final int iDir; ++ /** True if the owner of the connection can provide power to the connected wire. */ ++ final boolean offer; ++ /** True if the connected wire can provide power to the owner of the connection. */ ++ final boolean accept; ++ ++ /** The next connection in the sequence. */ ++ WireConnection next; ++ ++ WireConnection(WireNode wire, int iDir, boolean offer, boolean accept) { ++ this.wire = wire; ++ this.iDir = iDir; ++ this.offer = offer; ++ this.accept = accept; ++ } ++} +diff --git a/src/main/java/alternate/current/wire/WireConnectionManager.java b/src/main/java/alternate/current/wire/WireConnectionManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireConnectionManager.java +@@ -0,0 +0,0 @@ ++package alternate.current.wire; ++ ++import java.util.Arrays; ++import java.util.function.Consumer; ++ ++import alternate.current.wire.WireHandler.Directions; ++import alternate.current.wire.WireHandler.NodeProvider; ++ ++public class WireConnectionManager { ++ ++ /** The owner of these connections. */ ++ final WireNode owner; ++ ++ /** The first connection for each cardinal direction. */ ++ private final WireConnection[] heads; ++ ++ private WireConnection head; ++ private WireConnection tail; ++ ++ /** The total number of connections. */ ++ int total; ++ ++ /** ++ * A 4 bit number that encodes in which direction(s) the owner has connections ++ * to other wires. ++ */ ++ private int flowTotal; ++ /** The direction of flow based connections to other wires. */ ++ int iFlowDir; ++ ++ WireConnectionManager(WireNode owner) { ++ this.owner = owner; ++ ++ this.heads = new WireConnection[Directions.HORIZONTAL.length]; ++ ++ this.total = 0; ++ ++ this.flowTotal = 0; ++ this.iFlowDir = -1; ++ } ++ ++ void set(NodeProvider nodes) { ++ if (total > 0) { ++ clear(); ++ } ++ ++ boolean belowIsConductor = nodes.getNeighbor(owner, Directions.DOWN).isConductor(); ++ boolean aboveIsConductor = nodes.getNeighbor(owner, Directions.UP).isConductor(); ++ ++ for (int iDir = 0; iDir < Directions.HORIZONTAL.length; iDir++) { ++ Node neighbor = nodes.getNeighbor(owner, iDir); ++ ++ if (neighbor.isWire()) { ++ add(neighbor.asWire(), iDir, true, true); ++ ++ continue; ++ } ++ ++ boolean sideIsConductor = neighbor.isConductor(); ++ ++ if (!sideIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, Directions.DOWN); ++ ++ if (node.isWire()) { ++ add(node.asWire(), iDir, belowIsConductor, true); ++ } ++ } ++ if (!aboveIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, Directions.UP); ++ ++ if (node.isWire()) { ++ add(node.asWire(), iDir, true, sideIsConductor); ++ } ++ } ++ } ++ ++ if (total > 0) { ++ iFlowDir = WireHandler.FLOW_IN_TO_FLOW_OUT[flowTotal]; ++ } ++ } ++ ++ private void clear() { ++ Arrays.fill(heads, null); ++ ++ head = null; ++ tail = null; ++ ++ total = 0; ++ ++ flowTotal = 0; ++ iFlowDir = -1; ++ } ++ ++ private void add(WireNode wire, int iDir, boolean offer, boolean accept) { ++ add(new WireConnection(wire, iDir, offer, accept)); ++ } ++ ++ private void add(WireConnection connection) { ++ if (head == null) { ++ head = connection; ++ tail = connection; ++ } else { ++ tail.next = connection; ++ tail = connection; ++ } ++ ++ if (heads[connection.iDir] == null) { ++ heads[connection.iDir] = connection; ++ ++ flowTotal |= (1 << connection.iDir); ++ } ++ ++ total++; ++ } ++ ++ /** ++ * Iterate over all connections, without any specific ++ * update order in mind. Use this method if the iteration ++ * order is not important. ++ */ ++ void forEach(Consumer consumer) { ++ for (WireConnection c = head; c != null; c = c.next) { ++ consumer.accept(c); ++ } ++ } ++ ++ /** ++ * Iterate over all connections in an order determined by ++ * the given flow direction. Use this method if the iteration ++ * order is important. ++ */ ++ void forEach(Consumer consumer, int iFlowDir) { ++ for (int iDir : WireHandler.CARDINAL_UPDATE_ORDERS[iFlowDir]) { ++ for (WireConnection c = heads[iDir]; c != null && c.iDir == iDir; c = c.next) { ++ consumer.accept(c); ++ } ++ } ++ } ++} +diff --git a/src/main/java/alternate/current/wire/WireHandler.java b/src/main/java/alternate/current/wire/WireHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireHandler.java +@@ -0,0 +0,0 @@ ++package alternate.current.wire; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Queue; ++ ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; ++import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.redstone.Redstone; ++ ++/** ++ * This class handles power changes for redstone wire. The algorithm was ++ * designed with the following goals in mind: ++ *
++ * 1. Minimize the number of times a wire checks its surroundings to determine ++ * its power level. ++ *
++ * 2. Minimize the number of block and shape updates emitted. ++ *
++ * 3. Emit block and shape updates in a deterministic, non-locational order, ++ * fixing bug MC-11193. ++ * ++ *

++ * In Vanilla redstone wire is laggy because it fails on points 1 and 2. ++ * ++ *

++ * Redstone wire updates recursively and each wire calculates its power level in ++ * isolation rather than in the context of the network it is a part of. This ++ * means a wire in a grid can change its power level over half a dozen times ++ * before settling on its final value. This problem used to be worse in 1.13 and ++ * below, where a wire would only decrease its power level by 1 at a time. ++ * ++ *

++ * In addition to this, a wire emits 42 block updates and up to 22 shape updates ++ * each time it changes its power level. ++ * ++ *

++ * Of those 42 block updates, 6 are to itself, which are thus not only ++ * redundant, but a big source of lag, since those cause the wire to ++ * unnecessarily re-calculate its power level. A block only has 24 neighbors ++ * within a Manhattan distance of 2, meaning 12 of the remaining 36 block ++ * updates are duplicates and thus also redundant. ++ * ++ *

++ * Of the 22 shape updates, only 6 are strictly necessary. The other 16 are sent ++ * to blocks diagonally above and below. These are necessary if a wire changes ++ * its connections, but not when it changes its power level. ++ * ++ *

++ * Redstone wire in Vanilla also fails on point 3, though this is more of a ++ * quality-of-life issue than a lag issue. The recursive nature in which it ++ * updates, combined with the location-dependent order in which each wire ++ * updates its neighbors, makes the order in which neighbors of a wire network ++ * are updated incredibly inconsistent and seemingly random. ++ * ++ *

++ * Alternate Current fixes each of these problems as follows. ++ * ++ *

++ * 1. To make sure a wire calculates its power level as little as possible, we ++ * remove the recursive nature in which redstone wire updates in Vanilla. ++ * Instead, we build a network of connected wires, find those wires that receive ++ * redstone power from "outside" the network, and spread the power from there. ++ * This has a few advantages: ++ *
++ * - Each wire checks for power from non-wire components just once, and from ++ * nearby wires just twice. ++ *
++ * - Each wire only sets its power level in the world once. This is important, ++ * because calls to Level.setBlock are even more expensive than calls to ++ * Level.getBlockState. ++ * ++ *

++ * 2. There are 2 obvious ways in which we can reduce the number of block and ++ * shape updates. ++ *
++ * - Get rid of the 18 redundant block updates and 16 redundant shape updates, ++ * so each wire only emits 24 block updates and 6 shape updates whenever it ++ * changes its power level. ++ *
++ * - Only emit block updates and shape updates once a wire reaches its final ++ * power level, rather than at each intermediary stage. ++ *
++ * For an individual wire, these two optimizations are the best you can do, but ++ * for an entire grid, you can do better! ++ * ++ *

++ * Since we calculate the power of the entire network, sending block and shape ++ * updates to the wires in it is redundant. Removing those updates can reduce ++ * the number of block and shape updates by up to 20%. ++ * ++ *

++ * 3. To make the order of block updates to neighbors of a network ++ * deterministic, the first thing we must do is to replace the location- ++ * dependent order in which a wire updates its neighbors. Instead, we base it on ++ * the direction of power flow. This part of the algorithm was heavily inspired ++ * by theosib's 'RedstoneWireTurbo', which you can read more about in theosib's ++ * comment on Mojira here ++ * or by checking out its implementation in carpet mod here. ++ * ++ *

++ * The idea is to determine the direction of power flow through a wire based on ++ * the power it receives from neighboring wires. For example, if the only power ++ * a wire receives is from a neighboring wire to its west, it can be said that ++ * the direction of power flow through the wire is east. ++ * ++ *

++ * We make the order of block updates to neighbors of a wire depend on what is ++ * determined to be the direction of power flow. This not only removes ++ * locationality entirely, it even removes directionality in a large number of ++ * cases. Unlike in 'RedstoneWireTurbo', however, I have decided to keep a ++ * directional element in ambiguous cases, rather than to introduce randomness, ++ * though this is trivial to change. ++ * ++ *

++ * While this change fixes the block update order of individual wires, we must ++ * still address the overall block update order of a network. This turns out to ++ * be a simple fix, because of a change we made earlier: we search through the ++ * network for wires that receive power from outside it, and spread the power ++ * from there. If we make each wire transmit its power to neighboring wires in ++ * an order dependent on the direction of power flow, we end up with a ++ * non-locational and largely non-directional wire update order. ++ * ++ * @author Space Walker ++ */ ++public class WireHandler { ++ ++ public static class Directions { ++ ++ public static final Direction[] ALL = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.DOWN, Direction.UP }; ++ public static final Direction[] HORIZONTAL = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH }; ++ ++ // Indices for the arrays above. ++ // The cardinal directions are ordered clockwise. This allows ++ // for conversion between relative and absolute directions ++ // ('left' 'right' vs 'east' 'west') with simple arithmetic: ++ // If some Direction index 'iDir' is considered 'forward', then ++ // '(iDir + 1) & 0b11' is 'right', '(iDir + 2) & 0b11' is 'backward', etc. ++ public static final int WEST = 0b000; // 0 ++ public static final int NORTH = 0b001; // 1 ++ public static final int EAST = 0b010; // 2 ++ public static final int SOUTH = 0b011; // 3 ++ public static final int DOWN = 0b100; // 4 ++ public static final int UP = 0b101; // 5 ++ ++ public static int iOpposite(int iDir) { ++ return iDir ^ (0b10 >>> (iDir >>> 2)); ++ } ++ ++ /** ++ * Each array is placed at the index that encodes the direction that is missing ++ * from the array. ++ */ ++ private static final int[][] I_EXCEPT = { ++ { NORTH, EAST, SOUTH, DOWN, UP }, ++ { WEST, EAST, SOUTH, DOWN, UP }, ++ { WEST, NORTH, SOUTH, DOWN, UP }, ++ { WEST, NORTH, EAST, DOWN, UP }, ++ { WEST, NORTH, EAST, SOUTH, UP }, ++ { WEST, NORTH, EAST, SOUTH, DOWN } ++ }; ++ } ++ ++ /** ++ * This conversion table takes in information about incoming flow, and outputs ++ * the determined outgoing flow. ++ * ++ *

++ * The input is a 4 bit number that encodes the incoming flow. Each bit ++ * represents a cardinal direction, and when it is 'on', there is flow in that ++ * direction. ++ * ++ *

++ * The output is a single Direction index, or -1 for ambiguous cases. ++ * ++ *

++ * The outgoing flow is determined as follows: ++ * ++ *

++ * If there is just 1 direction of incoming flow, that direction will be the ++ * direction of outgoing flow. ++ * ++ *

++ * If there are 2 directions of incoming flow, and these directions are not each ++ * other's opposites, the direction that is 'more clockwise' will be the ++ * direction of outgoing flow. More precisely, the direction that is 1 clockwise ++ * turn from the other is picked. ++ * ++ *

++ * If there are 3 directions of incoming flow, the two opposing directions ++ * cancel each other out, and the remaining direction will be the direction of ++ * outgoing flow. ++ * ++ *

++ * In all other cases, the flow is completely ambiguous. ++ */ ++ static final int[] FLOW_IN_TO_FLOW_OUT = { ++ -1, // 0b0000: - -> x ++ Directions.WEST, // 0b0001: west -> west ++ Directions.NORTH, // 0b0010: north -> north ++ Directions.NORTH, // 0b0011: west/north -> north ++ Directions.EAST, // 0b0100: east -> east ++ -1, // 0b0101: west/east -> x ++ Directions.EAST, // 0b0110: north/east -> east ++ Directions.NORTH, // 0b0111: west/north/east -> north ++ Directions.SOUTH, // 0b1000: south -> south ++ Directions.WEST, // 0b1001: west/south -> west ++ -1, // 0b1010: north/south -> x ++ Directions.WEST, // 0b1011: west/north/south -> west ++ Directions.SOUTH, // 0b1100: east/south -> south ++ Directions.SOUTH, // 0b1101: west/east/south -> south ++ Directions.EAST, // 0b1110: north/east/south -> east ++ -1, // 0b1111: west/north/east/south -> x ++ }; ++ /** ++ * Update order of cardinal directions. Given that the index encodes the ++ * direction that is to be considered 'forward', the resulting update order is { ++ * front, back, right, left }. ++ */ ++ static final int[][] CARDINAL_UPDATE_ORDERS = { ++ { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ }; ++ /** ++ * The default update order of all cardinal directions. ++ */ ++ static final int[] DEFAULT_CARDINAL_UPDATE_ORDER = CARDINAL_UPDATE_ORDERS[0]; ++ /** ++ * The default update order of all directions. It is equivalent to the order of ++ * shape updates in vanilla Minecraft. ++ */ ++ static final int[] DEFAULT_FULL_UPDATE_ORDER = { ++ Directions.WEST, ++ Directions.EAST, ++ Directions.NORTH, ++ Directions.SOUTH, ++ Directions.DOWN, ++ Directions.UP ++ }; ++ ++ // If Vanilla will ever multi-thread the ticking of levels, there should ++ // be only one WireHandler per level, in case redstone updates in multiple ++ // levels at the same time. There are already mods that add multi-threading ++ // as well. ++ private final ServerLevel level; ++ ++ /** All the wires in the network. */ ++ private final List network; ++ /** Map of wires and neighboring blocks. */ ++ private final Long2ObjectMap nodes; ++ /** All the power changes that need to happen. */ ++ private final Queue powerChanges; ++ ++ private int rootCount; ++ // Rather than creating new nodes every time a network is updated we keep ++ // a cache of nodes that can be re-used. ++ private Node[] nodeCache; ++ private int nodeCount; ++ ++ private boolean updatingPower; ++ ++ public WireHandler(ServerLevel level) { ++ this.level = level; ++ ++ this.network = new ArrayList<>(); ++ this.nodes = new Long2ObjectOpenHashMap<>(); ++ this.powerChanges = new PowerQueue(); ++ ++ this.nodeCache = new Node[16]; ++ this.fillNodeCache(0, 16); ++ } ++ ++ private Node getOrAddNode(BlockPos pos) { ++ return nodes.compute(pos.asLong(), (key, node) -> { ++ if (node == null) { ++ // If there is not yet a node at this position, retrieve and ++ // update one from the cache. ++ return getNextNode(pos); ++ } ++ if (node.invalid) { ++ return revalidateNode(node); ++ } ++ ++ return node; ++ }); ++ } ++ ++ /** ++ * Retrieve the neighbor of a node in the given direction and create a link ++ * between the two nodes. ++ */ ++ private Node getNeighbor(Node node, int iDir) { ++ Node neighbor = node.neighbors[iDir]; ++ ++ if (neighbor == null || neighbor.invalid) { ++ Direction dir = Directions.ALL[iDir]; ++ BlockPos pos = node.pos.relative(dir); ++ ++ Node oldNeighbor = neighbor; ++ neighbor = getOrAddNode(pos); ++ ++ if (neighbor != oldNeighbor) { ++ int iOpp = Directions.iOpposite(iDir); ++ ++ node.neighbors[iDir] = neighbor; ++ neighbor.neighbors[iOpp] = node; ++ } ++ } ++ ++ return neighbor; ++ } ++ ++ private Node removeNode(BlockPos pos) { ++ return nodes.remove(pos.asLong()); ++ } ++ ++ private Node revalidateNode(Node node) { ++ node.invalid = false; ++ ++ if (node.isWire()) { ++ WireNode wire = node.asWire(); ++ ++ wire.prepared = false; ++ wire.inNetwork = false; ++ } else { ++ BlockPos pos = node.pos; ++ BlockState state = level.getBlockState(pos); ++ ++ node.update(pos, state, false); ++ } ++ ++ return node; ++ } ++ ++ /** ++ * Check the BlockState that occupies the given position. If it is a wire, then ++ * create a new WireNode. Otherwise, grab the next Node from the cache and ++ * update it. ++ */ ++ private Node getNextNode(BlockPos pos) { ++ BlockState state = level.getBlockState(pos); ++ ++ if (state.is(Blocks.REDSTONE_WIRE)) { ++ return new WireNode(level, pos, state); ++ } ++ ++ return getNextNode().update(pos, state, true); ++ } ++ ++ /** ++ * Grab the first unused Node from the cache. If all of the cache is already in ++ * use, increase it in size first. ++ */ ++ private Node getNextNode() { ++ if (nodeCount == nodeCache.length) { ++ increaseNodeCache(); ++ } ++ ++ return nodeCache[nodeCount++]; ++ } ++ ++ private void increaseNodeCache() { ++ Node[] oldCache = nodeCache; ++ nodeCache = new Node[oldCache.length << 1]; ++ ++ for (int index = 0; index < oldCache.length; index++) { ++ nodeCache[index] = oldCache[index]; ++ } ++ ++ fillNodeCache(oldCache.length, nodeCache.length); ++ } ++ ++ private void fillNodeCache(int start, int end) { ++ for (int index = start; index < end; index++) { ++ nodeCache[index] = new Node(level); ++ } ++ } ++ ++ /** ++ * This method should be called whenever a wire receives a block update. ++ */ ++ public void onWireUpdated(BlockPos pos) { ++ invalidateNodes(); ++ findRoots(pos, true); ++ tryUpdatePower(); ++ } ++ ++ /** ++ * This method should be called whenever a wire is placed. ++ */ ++ public void onWireAdded(BlockPos pos) { ++ Node node = getOrAddNode(pos); ++ ++ if (!node.isWire()) { ++ return; // we should never get here ++ } ++ ++ WireNode wire = node.asWire(); ++ wire.added = true; ++ ++ invalidateNodes(); ++ findRoots(pos, false); ++ tryUpdatePower(); ++ } ++ ++ /** ++ * This method should be called whenever a wire is removed. ++ */ ++ public void onWireRemoved(BlockPos pos, BlockState state) { ++ Node node = removeNode(pos); ++ WireNode wire; ++ ++ if (node == null || !node.isWire()) { ++ wire = new WireNode(level, pos, state); ++ } else { ++ wire = node.asWire(); ++ } ++ ++ wire.invalid = true; ++ wire.removed = true; ++ ++ // If these fields are set to 'true', the removal of this wire was part of ++ // already ongoing power changes, so we can exit early here. ++ if (updatingPower && wire.shouldBreak) { ++ return; ++ } ++ ++ invalidateNodes(); ++ tryAddRoot(wire); ++ tryUpdatePower(); ++ } ++ ++ /** ++ * The nodes map is a snapshot of the state of the world. It becomes invalid ++ * when power changes are carried out, since the block and shape updates can ++ * lead to block changes. If these block changes cause the network to be updated ++ * again every node must be invalidated, and revalidated before it is used ++ * again. This ensures the power calculations of the network are accurate. ++ */ ++ private void invalidateNodes() { ++ if (updatingPower && !nodes.isEmpty()) { ++ Iterator> it = Long2ObjectMaps.fastIterator(nodes); ++ ++ while (it.hasNext()) { ++ Entry entry = it.next(); ++ Node node = entry.getValue(); ++ ++ node.invalid = true; ++ } ++ } ++ } ++ ++ /** ++ * Look for wires at and around the given position that are in an invalid state ++ * and require power changes. These wires are called 'roots' because it is only ++ * when these wires change power level that neighboring wires must adjust as ++ * well. ++ * ++ *

++ * While it is strictly only necessary to check the wire at the given position, ++ * if that wire is part of a network, it is beneficial to check its surroundings ++ * for other wires that require power changes. This is because a network can ++ * receive power at multiple points. Consider the following setup: ++ * ++ *

++ * (top-down view, W = wire, L = lever, _ = air/other) ++ *
{@code _ _ W _ _ } ++ *
{@code _ W W W _ } ++ *
{@code W W L W W } ++ *
{@code _ W W W _ } ++ *
{@code _ _ W _ _ } ++ * ++ *

++ * The lever powers four wires in the network at once. If this is identified ++ * correctly, the entire network can (un)power at once. While it is not ++ * practical to cover every possible situation where a network is (un)powered ++ * from multiple points at once, checking for common cases like the one ++ * described above is relatively straight-forward. ++ * ++ *

++ * While these extra checks can provide significant performance gains in some ++ * cases, in the majority of cases they will have little to no effect, but do ++ * require extra code modifications to all redstone power emitters. Removing ++ * these optimizations would limit code modifications to the RedStoneWireBlock ++ * and ServerLevel classes while leaving the performance mostly intact. ++ */ ++ private void findRoots(BlockPos pos, boolean checkNeighbors) { ++ Node node = getOrAddNode(pos); ++ ++ if (!node.isWire()) { ++ return; // we should never get here ++ } ++ ++ WireNode wire = node.asWire(); ++ tryAddRoot(wire); ++ ++ // If the wire at the given position is not in an invalid state or is not ++ // part of a larger network, we can exit early. ++ if (!checkNeighbors || !wire.inNetwork || wire.connections.total == 0) { ++ return; ++ } ++ ++ for (int iDir : DEFAULT_FULL_UPDATE_ORDER) { ++ Node neighbor = getNeighbor(wire, iDir); ++ ++ if (neighbor.isConductor()) { ++ // Redstone components can power multiple wires through solid ++ // blocks. ++ findSignalSourcesAround(neighbor, Directions.iOpposite(iDir)); ++ } else if (neighbor.state.isSignalSourceTo(level, neighbor.pos, Directions.ALL[iDir])) { ++ // Redstone components can also power multiple wires directly. ++ findRootsAroundSignalSource(neighbor, Directions.iOpposite(iDir)); ++ } ++ } ++ } ++ ++ /** ++ * Find signal sources around the given node that can provide direct signals ++ * to that node, and then search for wires that require power changes around ++ * those signal sources. ++ */ ++ private void findSignalSourcesAround(Node node, int except) { ++ for (int iDir : Directions.I_EXCEPT[except]) { ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (neighbor.state.isDirectSignalSourceTo(level, neighbor.pos, Directions.ALL[iDir])) { ++ findRootsAroundSignalSource(neighbor, iDir); ++ } ++ } ++ } ++ ++ /** ++ * Find wires around the given signal source that require power changes. ++ */ ++ private void findRootsAroundSignalSource(Node node, int except) { ++ for (int iDir : Directions.I_EXCEPT[except]) { ++ // Directions are backwards for redstone related methods, so we must ++ // check for power emitted in the opposite direction that we are ++ // interested in. ++ int iOpp = Directions.iOpposite(iDir); ++ Direction opp = Directions.ALL[iOpp]; ++ ++ boolean signal = node.state.isSignalSourceTo(level, node.pos, opp); ++ boolean directSignal = node.state.isDirectSignalSourceTo(level, node.pos, opp); ++ ++ // If the signal source does not emit any power in this direction, ++ // move on to the next direction. ++ if (!signal && !directSignal) { ++ continue; ++ } ++ ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (signal && neighbor.isWire()) { ++ tryAddRoot(neighbor.asWire()); ++ } else if (directSignal && neighbor.isConductor()) { ++ findRootsAround(neighbor, iOpp); ++ } ++ } ++ } ++ ++ /** ++ * Look for wires around the given node that require power changes. ++ */ ++ private void findRootsAround(Node node, int except) { ++ for (int iDir : Directions.I_EXCEPT[except]) { ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (neighbor.isWire()) { ++ tryAddRoot(neighbor.asWire()); ++ } ++ } ++ } ++ ++ /** ++ * Check if the given wire is in an illegal state and needs power changes. ++ */ ++ private void tryAddRoot(WireNode wire) { ++ // Each potential root needs to be checked only once. ++ if (wire.prepared) { ++ return; ++ } ++ ++ prepare(wire); ++ findPower(wire, false); ++ ++ if (needsPowerChange(wire)) { ++ addRoot(wire); ++ } ++ } ++ ++ /** ++ * Add the given wire to the network as a root. ++ */ ++ private void addRoot(WireNode wire) { ++ network.add(wire); ++ rootCount++; ++ ++ wire.inNetwork = true; ++ ++ if (wire.connections.iFlowDir >= 0) { ++ wire.iFlowDir = wire.connections.iFlowDir; ++ } ++ } ++ ++ /** ++ * Before a wire can be added to the network, it must be properly prepared. ++ * This method ++ *
++ * - checks if this wire should break. Rather than break the wire right away, ++ * its effects are integrated into the power calculations. ++ *
++ * - determines the 'external power' this wire receives (power from non-wire ++ * components). ++ *
++ * - finds connections this wire has to neighboring wires. ++ */ ++ private void prepare(WireNode wire) { ++ // Each wire only needs to be prepared once. ++ if (wire.prepared) { ++ return; ++ } ++ ++ wire.prepared = true; ++ wire.inNetwork = false; ++ ++ if (!wire.removed && !wire.shouldBreak && !wire.state.canSurvive(level, wire.pos)) { ++ wire.shouldBreak = true; ++ } ++ ++ wire.virtualPower = wire.externalPower = getInitialPower(wire); ++ wire.connections.set(this::getNeighbor); ++ } ++ ++ private int getInitialPower(WireNode wire) { ++ return (wire.removed || wire.shouldBreak) ? Redstone.SIGNAL_MIN : getExternalPower(wire); ++ } ++ ++ private int getExternalPower(WireNode wire) { ++ int power = Redstone.SIGNAL_MIN; ++ ++ for (int iDir = 0; iDir < Directions.ALL.length; iDir++) { ++ Node neighbor = getNeighbor(wire, iDir); ++ ++ // Power from wires is handled separately. ++ if (neighbor.isWire()) { ++ continue; ++ } ++ ++ // Since 1.16 there is a block that is both a conductor and a signal ++ // source: the target block! ++ if (neighbor.isConductor()) { ++ power = Math.max(power, getDirectSignalTo(wire, neighbor, Directions.iOpposite(iDir))); ++ } ++ if (neighbor.isSignalSource()) { ++ power = Math.max(power, neighbor.state.getSignal(level, neighbor.pos, Directions.ALL[iDir])); ++ } ++ ++ if (power >= Redstone.SIGNAL_MAX) { ++ return Redstone.SIGNAL_MAX; ++ } ++ } ++ ++ return power; ++ } ++ ++ /** ++ * Determine the direct signal the given wire receives from neighboring blocks ++ * through the given conductor node. ++ */ ++ private int getDirectSignalTo(WireNode wire, Node node, int except) { ++ int power = Redstone.SIGNAL_MIN; ++ ++ for (int iDir : Directions.I_EXCEPT[except]) { ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (neighbor.isSignalSource()) { ++ power = Math.max(power, neighbor.state.getDirectSignal(level, neighbor.pos, Directions.ALL[iDir])); ++ ++ if (power >= Redstone.SIGNAL_MAX) { ++ return Redstone.SIGNAL_MAX; ++ } ++ } ++ } ++ ++ return power; ++ } ++ ++ /** ++ * Determine the power level the given wire receives from the blocks around it. ++ * Power from non-wire components has already been determined, so only power ++ * received from other wires needs to be checked. There are a few exceptions: ++ *
++ * - If the wire is removed or going to break, its power level should always be ++ * the minimum value. This is because it (effectively) no longer exists, so ++ * cannot provide any power to neighboring wires. ++ *
++ * - Power received from neighboring wires will never exceed {@code maxPower - 1}, ++ * so if the external power is already larger than or equal to that, there is no ++ * need to check for power from neighboring wires. ++ */ ++ private void findPower(WireNode wire, boolean ignoreNetwork) { ++ if (wire.removed || wire.shouldBreak || wire.externalPower >= (Redstone.SIGNAL_MAX - 1)) { ++ return; ++ } ++ ++ // The virtual power is reset to the external power, so the flow information ++ // must be reset as well. ++ wire.virtualPower = wire.externalPower; ++ wire.flowIn = 0; ++ ++ findWirePower(wire, ignoreNetwork); ++ } ++ ++ /** ++ * Determine the power level the given wire receives from connected wires. ++ */ ++ private void findWirePower(WireNode wire, boolean ignoreNetwork) { ++ wire.connections.forEach(connection -> { ++ if (!connection.accept) { ++ return; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ if (!ignoreNetwork || !neighbor.inNetwork) { ++ int power = Math.max(Redstone.SIGNAL_MIN, neighbor.virtualPower - 1); ++ int iOpp = Directions.iOpposite(connection.iDir); ++ ++ wire.offerPower(power, iOpp); ++ } ++ }); ++ } ++ ++ private boolean needsPowerChange(WireNode wire) { ++ return wire.removed || wire.shouldBreak || wire.virtualPower != wire.currentPower; ++ } ++ ++ private void tryUpdatePower() { ++ if (rootCount > 0) { ++ updatePower(); ++ } ++ if (!updatingPower) { ++ nodes.clear(); ++ nodeCount = 0; ++ } ++ } ++ ++ /** ++ * Propagate power changes through the network and notify neighboring blocks of ++ * these changes. ++ * ++ *

++ * Power changes are done in the following 3 steps. ++ * ++ *

++ * 1. Build up the network ++ *
++ * Collect all the wires around the roots that need to change their power ++ * levels. ++ * ++ *

++ * 2. Find powered wires ++ *
++ * Find those wires in the network that receive power from outside the network. ++ * This can come in 2 forms: ++ *
++ * - Power from non-wire components (repeaters, torches, etc.). ++ *
++ * - Power from wires that are not in the network. ++ *
++ * These powered wires will then queue their power changes. ++ * ++ *

++ * 3. Let power flow ++ *
++ * Work through the queue of power changes. After each wire's power change, emit ++ * shape and block updates to neighboring blocks, then queue power changes for ++ * connected wires. ++ */ ++ private void updatePower() { ++ // Build a network of wires that need power changes. This includes the roots ++ // as well as any wires that will be affected by power changes to those roots. ++ buildNetwork(); ++ ++ // Find those wires in the network that receive power from outside it. ++ // Remember that the power changes for those wires are already queued here! ++ findPoweredWires(); ++ ++ // Once the powered wires have been found, the network is no longer needed. In ++ // fact, it should be cleared before block and shape updates are emitted, in ++ // case a different network is updated that needs power changes. ++ network.clear(); ++ rootCount = 0; ++ ++ // Carry out the power changes and emit shape and block updates. ++ try { ++ letPowerFlow(); ++ } catch (Throwable t) { ++ // If anything goes wrong while carrying out power changes, this field must ++ // be reset to 'false', or the wire handler will be locked out of carrying ++ // out power changes until the world is reloaded. ++ updatingPower = false; ++ ++ throw t; ++ } ++ } ++ ++ /** ++ * Build up a network of wires that need power changes. This includes the roots ++ * that were already added and any wires powered by those roots that will need ++ * power changes as a result of power changes to the roots. ++ */ ++ private void buildNetwork() { ++ for (int index = 0; index < network.size(); index++) { ++ WireNode wire = network.get(index); ++ ++ // The order in which wires are added to the network can influence the ++ // order in which they update their power levels. ++ wire.connections.forEach(connection -> { ++ if (!connection.offer) { ++ return; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ if (neighbor.inNetwork) { ++ return; ++ } ++ ++ prepare(neighbor); ++ findPower(neighbor, false); ++ ++ if (needsPowerChange(neighbor)) { ++ addToNetwork(neighbor, connection.iDir); ++ } ++ }, wire.iFlowDir); ++ } ++ } ++ ++ /** ++ * Add the given wire to the network and set its outgoing flow to some backup ++ * value. This avoids directionality in redstone grids. ++ */ ++ private void addToNetwork(WireNode wire, int iBackupFlowDir) { ++ network.add(wire); ++ ++ wire.inNetwork = true; ++ // Normally the flow is not set until the power level is updated. However, ++ // in networks with multiple power sources the update order between them ++ // depends on which was discovered first. To make this less prone to ++ // directionality, each wire node is given a 'backup' flow. For roots, this ++ // is the determined flow of their connections. For non-roots this is the ++ // direction from which they were discovered. ++ wire.iFlowDir = iBackupFlowDir; ++ } ++ ++ /** ++ * Find those wires in the network that receive power from outside it, either ++ * from non-wire components or from wires that are not in the network, and queue ++ * the power changes for those wires. ++ */ ++ private void findPoweredWires() { ++ for (int index = 0; index < network.size(); index++) { ++ WireNode wire = network.get(index); ++ findPower(wire, true); ++ ++ if (index < rootCount || wire.removed || wire.shouldBreak || wire.virtualPower > Redstone.SIGNAL_MIN) { ++ queuePowerChange(wire); ++ } else { ++ // Wires that do not receive any power do not queue power changes ++ // until they are offered power from a neighboring wire. To ensure ++ // that they accept any power from neighboring wires and thus queue ++ // their power changes, their virtual power is set to below the ++ // minimum. ++ wire.virtualPower--; ++ } ++ } ++ } ++ ++ /** ++ * Queue the power change for the given wire. If the wire does not need a power ++ * change (perhaps because its power has already changed), transmit power to ++ * neighboring wires. ++ */ ++ private void queuePowerChange(WireNode wire) { ++ if (needsPowerChange(wire)) { ++ powerChanges.offer(wire); ++ } else { ++ findPowerFlow(wire); ++ transmitPower(wire); ++ } ++ } ++ ++ /** ++ * Use the information of incoming power flow to determine the direction of ++ * power flow through this wire. If that flow is ambiguous, try to use a flow ++ * direction based on connections to neighboring wires. If that is also ++ * ambiguous, use the backup value that was set when the wire was first added ++ * to the network. ++ */ ++ private void findPowerFlow(WireNode wire) { ++ int flow = FLOW_IN_TO_FLOW_OUT[wire.flowIn]; ++ ++ if (flow >= 0) { ++ wire.iFlowDir = flow; ++ } else if (wire.connections.iFlowDir >= 0) { ++ wire.iFlowDir = wire.connections.iFlowDir; ++ } ++ } ++ ++ /** ++ * Transmit power from the given wire to neighboring wires. ++ */ ++ private void transmitPower(WireNode wire) { ++ int nextPower = Math.max(Redstone.SIGNAL_MIN, wire.virtualPower - 1); ++ ++ wire.connections.forEach(connection -> { ++ if (!connection.offer) { ++ return; ++ } ++ ++ WireNode neighbor = connection.wire; ++ int iDir = connection.iDir; ++ ++ if (neighbor.offerPower(nextPower, iDir)) { ++ queuePowerChange(neighbor); ++ } ++ }, wire.iFlowDir); ++ } ++ ++ /** ++ * Carry out power changes, setting the new power of each wire in the world, ++ * notifying neighbors of the power change, then queueing power changes of ++ * connected wires. ++ */ ++ private void letPowerFlow() { ++ // If an instantaneous update chain causes updates to another network ++ // (or the same network in another place), new power changes will be ++ // integrated into the already ongoing power queue, so we can exit early ++ // here. ++ if (updatingPower) { ++ return; ++ } ++ ++ updatingPower = true; ++ ++ while (!powerChanges.isEmpty()) { ++ WireNode wire = powerChanges.poll(); ++ ++ if (!needsPowerChange(wire)) { ++ continue; ++ } ++ ++ findPowerFlow(wire); ++ ++ if (wire.setPower()) { ++ // If the wire was newly placed or removed, shape updates have ++ // already been emitted. ++ if (!wire.added && !wire.shouldBreak) { ++ updateNeighborShapes(wire); ++ } ++ ++ updateNeighborBlocks(wire); ++ } ++ ++ transmitPower(wire); ++ } ++ ++ updatingPower = false; ++ } ++ ++ /** ++ * Emit shape updates around the given wire. ++ */ ++ private void updateNeighborShapes(WireNode wire) { ++ BlockPos wirePos = wire.pos; ++ BlockState wireState = wire.state; ++ ++ for (Direction dir : Block.UPDATE_SHAPE_ORDER) { ++ updateNeighborShape(wirePos.relative(dir), dir.getOpposite(), wirePos, wireState); ++ } ++ } ++ ++ private void updateNeighborShape(BlockPos pos, Direction fromDir, BlockPos fromPos, BlockState fromState) { ++ BlockState state = level.getBlockState(pos); ++ ++ // Shape updates to redstone wire are very expensive, and should never happen ++ // as a result of power changes anyway. ++ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { ++ BlockState newState = state.updateShape(fromDir, fromState, level, pos, fromPos); ++ Block.updateOrDestroy(state, newState, level, pos, Block.UPDATE_CLIENTS); ++ } ++ } ++ ++ /** ++ * Emit block updates around the given wire. The order in which neighbors are ++ * updated is determined as follows: ++ *
++ * 1. The direction of power flow through the wire is to be considered 'forward'. ++ * The order in which neighbors are updated depends on their relative positions ++ * to the wire. ++ *
++ * 2. Each neighbor is identified by the step(s) you must take, starting at the ++ * wire, to reach it. Each step is 1 block, thus the position of a neighbor is ++ * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left), ++ * etc. ++ *
++ * 3. Neighbors are updated in pairs that lie on opposite sides of the wire. ++ *
++ * 4. Neighbors are updated in order of their distance from the wire. This means ++ * they are updated in 3 groups: direct neighbors are updated first, then ++ * diagonal neighbors, and last are the far neighbors that are 2 blocks directly ++ * out. ++ *
++ * 5. The order within each group is determined using the following basic order: ++ * { front, back, right, left, down, up }. This order was chosen because it ++ * converts to the following order of absolute directions when west is said to ++ * be 'forward': { west, east, north, south, down, up } - this is the order of ++ * shape updates. ++ */ ++ private void updateNeighborBlocks(WireNode wire) { ++ int iDir = wire.iFlowDir; ++ ++ Direction forward = Directions.HORIZONTAL[ iDir ]; ++ Direction rightward = Directions.HORIZONTAL[(iDir + 1) & 0b11]; ++ Direction backward = Directions.HORIZONTAL[(iDir + 2) & 0b11]; ++ Direction leftward = Directions.HORIZONTAL[(iDir + 3) & 0b11]; ++ Direction downward = Direction.DOWN; ++ Direction upward = Direction.UP; ++ ++ BlockPos self = wire.pos; ++ BlockPos front = self.relative(forward); ++ BlockPos right = self.relative(rightward); ++ BlockPos back = self.relative(backward); ++ BlockPos left = self.relative(leftward); ++ BlockPos below = self.relative(downward); ++ BlockPos above = self.relative(upward); ++ ++ // direct neighbors (6) ++ updateNeighbor(front, self); ++ updateNeighbor(back, self); ++ updateNeighbor(right, self); ++ updateNeighbor(left, self); ++ updateNeighbor(below, self); ++ updateNeighbor(above, self); ++ ++ // diagonal neighbors (12) ++ updateNeighbor(front.relative(rightward), self); ++ updateNeighbor(back.relative(leftward), self); ++ updateNeighbor(front.relative(leftward), self); ++ updateNeighbor(back.relative(rightward), self); ++ updateNeighbor(front.relative(downward), self); ++ updateNeighbor(back.relative(upward), self); ++ updateNeighbor(front.relative(upward), self); ++ updateNeighbor(back.relative(downward), self); ++ updateNeighbor(right.relative(downward), self); ++ updateNeighbor(left.relative(upward), self); ++ updateNeighbor(right.relative(upward), self); ++ updateNeighbor(left.relative(downward), self); ++ ++ // far neighbors (6) ++ updateNeighbor(front.relative(forward), self); ++ updateNeighbor(back.relative(backward), self); ++ updateNeighbor(right.relative(rightward), self); ++ updateNeighbor(left.relative(leftward), self); ++ updateNeighbor(below.relative(downward), self); ++ updateNeighbor(above.relative(upward), self); ++ } ++ ++ private void updateNeighbor(BlockPos pos, BlockPos fromPos) { ++ BlockState state = level.getBlockState(pos); ++ ++ // While this check makes sure wires in the network are not given block ++ // updates, it also prevents block updates to wires in neighboring networks. ++ // While this should not make a difference in theory, in practice, it is ++ // possible to force a network into an invalid state without updating it, even ++ // if it is relatively obscure. ++ // While I was willing to make this compromise in return for some significant ++ // performance gains in certain setups, if you are not, you can add all the ++ // positions of the network to a set and filter out block updates to wires in ++ // the network that way. ++ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { ++ state.neighborChanged(level, pos, Blocks.REDSTONE_WIRE, fromPos, false); ++ } ++ } ++ ++ @FunctionalInterface ++ public interface NodeProvider { ++ ++ public Node getNeighbor(Node node, int iDir); ++ ++ } ++} +diff --git a/src/main/java/alternate/current/wire/WireNode.java b/src/main/java/alternate/current/wire/WireNode.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireNode.java +@@ -0,0 +0,0 @@ ++package alternate.current.wire; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.util.Mth; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.RedStoneWireBlock; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.redstone.Redstone; ++ ++/** ++ * A WireNode is a Node that represents a wire in the world. It stores ++ * all the information about the wire that the WireHandler needs to ++ * calculate power changes. ++ * ++ * @author Space Walker ++ */ ++public class WireNode extends Node { ++ ++ final WireConnectionManager connections; ++ ++ /** The power level this wire currently holds in the world. */ ++ int currentPower; ++ /** ++ * While calculating power changes for a network, this field is used to keep ++ * track of the power level this wire should have. ++ */ ++ int virtualPower; ++ /** The power level received from non-wire components. */ ++ int externalPower; ++ /** ++ * A 4-bit number that keeps track of the power flow of the wires that give this ++ * wire its power level. ++ */ ++ int flowIn; ++ /** The direction of power flow, based on the incoming flow. */ ++ int iFlowDir; ++ boolean added; ++ boolean removed; ++ boolean shouldBreak; ++ boolean prepared; ++ boolean inNetwork; ++ ++ /** The power for which this wire was queued. */ ++ int power; ++ /** The previous wire in the power queue. */ ++ WireNode prev; ++ /** The next wire in the power queue. */ ++ WireNode next; ++ ++ WireNode(ServerLevel level, BlockPos pos, BlockState state) { ++ super(level); ++ ++ this.pos = pos.immutable(); ++ this.state = state; ++ ++ this.connections = new WireConnectionManager(this); ++ ++ this.virtualPower = this.currentPower = this.state.getValue(RedStoneWireBlock.POWER); ++ } ++ ++ @Override ++ public Node update(BlockPos pos, BlockState state, boolean clearNeighbors) { ++ throw new UnsupportedOperationException("Cannot update a WireNode!"); ++ } ++ ++ @Override ++ public boolean isWire() { ++ return true; ++ } ++ ++ @Override ++ public WireNode asWire() { ++ return this; ++ } ++ ++ int nextPower() { ++ return Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX); ++ } ++ ++ boolean offerPower(int power, int iDir) { ++ if (removed || shouldBreak) { ++ return false; ++ } ++ if (power == virtualPower) { ++ flowIn |= (1 << iDir); ++ return false; ++ } ++ if (power > virtualPower) { ++ virtualPower = power; ++ flowIn = (1 << iDir); ++ ++ return true; ++ } ++ ++ return false; ++ } ++ ++ boolean setPower() { ++ if (removed) { ++ return true; ++ } ++ ++ state = level.getBlockState(pos); ++ ++ if (shouldBreak) { ++ Block.dropResources(state, level, pos); ++ return level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS); ++ } ++ ++ currentPower = LevelHelper.doRedstoneEvent(level, pos, currentPower, power); ++ state = state.setValue(RedStoneWireBlock.POWER, currentPower); ++ ++ return LevelHelper.setWireState(level, pos, state, added); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -0,0 +0,0 @@ public class PaperWorldConfig { + piglinsGuardChests = getBoolean("piglins-guard-chests", piglinsGuardChests); + } + +- public boolean useEigencraftRedstone = false; +- private void useEigencraftRedstone() { +- useEigencraftRedstone = this.getBoolean("use-faster-eigencraft-redstone", false); +- if (useEigencraftRedstone) { +- log("Using Eigencraft redstone algorithm by theosib."); ++ public enum RedstoneImplementation { ++ VANILLA, EIGENCRAFT, ALTERNATE_CURRENT ++ } ++ public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA; ++ private void redstoneImplementation() { ++ String implementation; ++ if (PaperConfig.version < 27) { ++ implementation = "vanilla"; ++ if (config.contains("world-settings.default.use-faster-eigencraft-redstone")) { ++ implementation = config.getBoolean("world-settings.default.use-faster-eigencraft-redstone") ? "eigencraft" : "vanilla"; ++ config.set("world-settings.default.redstone-implementation", implementation); ++ } ++ if (config.contains("world-settings." + worldName + ".use-faster-eigencraft-redstone")) { ++ implementation = config.getBoolean("world-settings." + worldName + ".use-faster-eigencraft-redstone") ? "eigencraft" : "vanilla"; ++ config.set("world-settings." + worldName + ".redstone-implementation", implementation); ++ } ++ remove("use-faster-eigencraft-redstone"); + } else { +- log("Using vanilla redstone algorithm."); ++ implementation = this.getString("redstone-implementation", "vanilla").toLowerCase().trim(); ++ } ++ switch (implementation) { ++ default: ++ logError("Invalid redstone-implementation config " + implementation + " - must be one of: vanilla, eigencraft, alternate-current"); ++ case "vanilla": ++ redstoneImplementation = RedstoneImplementation.VANILLA; ++ log("Using the Vanilla redstone implementation."); ++ break; ++ case "eigencraft": ++ redstoneImplementation = RedstoneImplementation.EIGENCRAFT; ++ log("Using Eigencraft's redstone implementation by theosib."); ++ break; ++ case "alternate-current": ++ redstoneImplementation = RedstoneImplementation.ALTERNATE_CURRENT; ++ log("Using Alternate Current's redstone implementation by Space Walker."); ++ break; + } + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel { + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper + public boolean hasEntityMoveEvent = false; // Paper ++ private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + public static Throwable getAddToWorldStackTrace(Entity entity) { + return new Throwable(entity + " Added to world at " + new java.util.Date()); + } +@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel { + return this.entityManager.canPositionTick(pos.toLong()); // Paper + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public alternate.current.wire.WireHandler getWireHandler() { ++ return wireHandler; ++ } ++ // Paper end ++ + private final class EntityCallbacks implements LevelCallback { + + EntityCallbacks() {} +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return ret; + } + // Paper end ++ ++ // Paper start - optimize redstone (Alternate Current) ++ public alternate.current.wire.WireHandler getWireHandler() { ++ // This method is overridden in ServerLevel. ++ // Since Paper is a server platform there is no risk ++ // of this implementation being called. It is here ++ // only so this method can be called without casting ++ // an instance of Level to ServerLevel. ++ return null; ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +@@ -0,0 +0,0 @@ public abstract class BasePressurePlateBlock extends Block { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return dir == Direction.UP; ++ } ++ // Paper end ++ + @Override + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.DESTROY; +diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +@@ -0,0 +0,0 @@ public abstract class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return getConnectedDirection(state) == dir; ++ } ++ // Paper end ++ + @Override + public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { + if ((Boolean) state.getValue(ButtonBlock.POWERED)) { +diff --git a/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java b/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java +@@ -0,0 +0,0 @@ public class DaylightDetectorBlock extends BaseEntityBlock { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ // Paper end ++ + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new DaylightDetectorBlockEntity(pos, state); +diff --git a/src/main/java/net/minecraft/world/level/block/DiodeBlock.java b/src/main/java/net/minecraft/world/level/block/DiodeBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/DiodeBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DiodeBlock.java +@@ -0,0 +0,0 @@ public abstract class DiodeBlock extends HorizontalDirectionalBlock { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(FACING) == dir; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(FACING) == dir; ++ } ++ // Paper end ++ + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { + return (BlockState) this.defaultBlockState().setValue(DiodeBlock.FACING, ctx.getHorizontalDirection().getOpposite()); +diff --git a/src/main/java/net/minecraft/world/level/block/LecternBlock.java b/src/main/java/net/minecraft/world/level/block/LecternBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/LecternBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LecternBlock.java +@@ -0,0 +0,0 @@ public class LecternBlock extends BaseEntityBlock { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return dir == Direction.UP; ++ } ++ // Paper end; ++ + @Override + public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction direction) { + return (Boolean) state.getValue(LecternBlock.POWERED) ? 15 : 0; +diff --git a/src/main/java/net/minecraft/world/level/block/LeverBlock.java b/src/main/java/net/minecraft/world/level/block/LeverBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/LeverBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LeverBlock.java +@@ -0,0 +0,0 @@ public class LeverBlock extends FaceAttachedHorizontalDirectionalBlock { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return getConnectedDirection(state) == dir; ++ } ++ // Paper end ++ + private void updateNeighbours(BlockState state, Level world, BlockPos pos) { + world.updateNeighborsAt(pos, this); + world.updateNeighborsAt(pos.relative(getConnectedDirection(state).getOpposite()), this); +diff --git a/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java b/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java +@@ -0,0 +0,0 @@ public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBloc + public boolean isSignalSource(BlockState state) { + return true; + } ++ ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(FACING) == dir; ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java +@@ -0,0 +0,0 @@ public class ObserverBlock extends DirectionalBlock { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(FACING) == dir; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(FACING) == dir; ++ } ++ // Paper end ++ + @Override + public int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Direction direction) { + return state.getSignal(world, pos, direction); +diff --git a/src/main/java/net/minecraft/world/level/block/PoweredBlock.java b/src/main/java/net/minecraft/world/level/block/PoweredBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/PoweredBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PoweredBlock.java +@@ -0,0 +0,0 @@ package net.minecraft.world.level.block; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; + +@@ -0,0 +0,0 @@ public class PoweredBlock extends Block { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ // Paper end ++ + @Override + public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction direction) { + return 15; +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -0,0 +0,0 @@ + package net.minecraft.world.level.block; + + import com.destroystokyo.paper.PaperConfig; ++import com.destroystokyo.paper.PaperWorldConfig.RedstoneImplementation; + import com.destroystokyo.paper.util.RedstoneWireTurbo; + import com.google.common.collect.ImmutableMap; + import com.google.common.collect.Maps; +@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { + return floor.isFaceSturdy(world, pos, Direction.UP) || floor.is(Blocks.HOPPER); + } + +- // Paper start - Optimize redstone ++ // Paper start - Optimize redstone (Eigencraft) + // The bulk of the new functionality is found in RedstoneWireTurbo.java + RedstoneWireTurbo turbo = new RedstoneWireTurbo(this); + +@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { + * Note: Added 'source' argument so as to help determine direction of information flow + */ + private void updateSurroundingRedstone(Level worldIn, BlockPos pos, BlockState state, BlockPos source) { +- if (worldIn.paperConfig.useEigencraftRedstone) { ++ if (worldIn.paperConfig.redstoneImplementation == RedstoneImplementation.EIGENCRAFT) { + turbo.updateSurroundingRedstone(worldIn, pos, state, source); + return; + } +@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { + int k = worldIn.getBestNeighborSignal(pos1); + this.shouldSignal = true; + +- if (!worldIn.paperConfig.useEigencraftRedstone) { ++ if (worldIn.paperConfig.redstoneImplementation == RedstoneImplementation.VANILLA) { + // This code is totally redundant to if statements just below the loop. + if (k > 0 && k > j - 1) { + j = k; +@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { + // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the + // following loop can affect the power level of the wire. Therefore, the loop is + // skipped if k is already 15. +- if (!worldIn.paperConfig.useEigencraftRedstone || k < 15) { ++ if (worldIn.paperConfig.redstoneImplementation == RedstoneImplementation.VANILLA || k < 15) { + for (Direction enumfacing : Direction.Plane.HORIZONTAL) { + BlockPos blockpos = pos1.relative(enumfacing); + boolean flag = blockpos.getX() != pos2.getX() || blockpos.getZ() != pos2.getZ(); +@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { + } + } + +- if (!worldIn.paperConfig.useEigencraftRedstone) { ++ if (worldIn.paperConfig.redstoneImplementation == RedstoneImplementation.VANILLA) { + // The old code would decrement the wire value only by 1 at a time. + if (l > j) { + j = l - 1; +@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { + @Override + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (!oldState.is(state.getBlock()) && !world.isClientSide) { +- this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone ++ // Paper start - optimize redstone - replace call to updatePowerStrength ++ if (world.paperConfig.redstoneImplementation == RedstoneImplementation.ALTERNATE_CURRENT) { ++ world.getWireHandler().onWireAdded(pos); // Alternate Current ++ } else { ++ this.updateSurroundingRedstone(world, pos, state, null); // vanilla/Eigencraft ++ } ++ // Paper end + Iterator iterator = Direction.Plane.VERTICAL.iterator(); + + while (iterator.hasNext()) { +@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { + world.updateNeighborsAt(pos.relative(enumdirection), this); + } + +- this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone ++ // Paper start - optimize redstone - replace call to updatePowerStrength ++ if (world.paperConfig.redstoneImplementation == RedstoneImplementation.ALTERNATE_CURRENT) { ++ world.getWireHandler().onWireRemoved(pos, state); // Alternate Current ++ } else { ++ this.updateSurroundingRedstone(world, pos, state, null); // vanilla/Eigencraft ++ } ++ // Paper end + this.updateNeighborsOfNeighboringWires(world, pos); + } + } +@@ -0,0 +0,0 @@ public class RedStoneWireBlock extends Block { + @Override + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean notify) { + if (!world.isClientSide) { ++ // Paper start - optimize redstone (Alternate Current) ++ // Alternate Current handles breaking of redstone wires in the WireHandler. ++ if (world.paperConfig.redstoneImplementation == RedstoneImplementation.ALTERNATE_CURRENT) { ++ world.getWireHandler().onWireUpdated(pos); ++ } else ++ // Paper end + if (state.canSurvive(world, pos)) { +- this.updateSurroundingRedstone(world, pos, state, fromPos); // Paper - Optimize redstone ++ this.updateSurroundingRedstone(world, pos, state, fromPos); // Paper - optimize redstone (Eigencraft) + } else { + dropResources(state, world, pos); + world.removeBlock(pos, false); +diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java +@@ -0,0 +0,0 @@ public class RedstoneTorchBlock extends TorchBlock { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return dir != Direction.UP; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return dir == Direction.DOWN; ++ } ++ // Paper end ++ + @Override + public void animateTick(BlockState state, Level world, BlockPos pos, Random random) { + if ((Boolean) state.getValue(RedstoneTorchBlock.LIT)) { +diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java +@@ -0,0 +0,0 @@ public class RedstoneWallTorchBlock extends RedstoneTorchBlock { + return state.getValue(LIT) && state.getValue(FACING) != direction ? 15 : 0; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(FACING) != dir; ++ } ++ // Paper end ++ + @Override + public BlockState rotate(BlockState state, Rotation rotation) { + return Blocks.WALL_TORCH.rotate(state, rotation); +diff --git a/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java b/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java +@@ -0,0 +0,0 @@ public class SculkSensorBlock extends BaseEntityBlock implements SimpleWaterlogg + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ // Paper end ++ + @Override + public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction direction) { + return (Integer) state.getValue(SculkSensorBlock.POWER); +diff --git a/src/main/java/net/minecraft/world/level/block/TargetBlock.java b/src/main/java/net/minecraft/world/level/block/TargetBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/TargetBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TargetBlock.java +@@ -0,0 +0,0 @@ public class TargetBlock extends Block { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ // Paper end ++ + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(OUTPUT_POWER); +diff --git a/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java b/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java +@@ -0,0 +0,0 @@ import net.minecraft.stats.Stat; + import net.minecraft.stats.Stats; + import net.minecraft.util.Mth; + import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.entity.BlockEntityType; + import net.minecraft.world.level.block.entity.ChestBlockEntity; +@@ -0,0 +0,0 @@ public class TrappedChestBlock extends ChestBlock { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return dir == Direction.UP; ++ } ++ // Paper end ++ + @Override + public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction direction) { + return Mth.clamp(ChestBlockEntity.getOpenCount(world, pos), 0, 15); +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +@@ -0,0 +0,0 @@ public class TripWireHookBlock extends Block { + return true; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return true; ++ } ++ ++ @Override ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return state.getValue(FACING) == dir; ++ } ++ // Paper end ++ + @Override + public BlockState rotate(BlockState state, Rotation rotation) { + return (BlockState) state.setValue(TripWireHookBlock.FACING, rotation.rotate((Direction) state.getValue(TripWireHookBlock.FACING))); +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 +@@ -0,0 +0,0 @@ import net.minecraft.world.phys.shapes.VoxelShape; + + public abstract class BlockBehaviour { + +- protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP}; ++ public static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP}; + protected final Material material; + public final boolean hasCollision; + protected final float explosionResistance; +@@ -0,0 +0,0 @@ public abstract class BlockBehaviour { + return false; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ public boolean isSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return false; ++ } ++ ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, BlockState state, Direction dir) { ++ return false; ++ } ++ // Paper end ++ + /** @deprecated */ + @Deprecated + public PushReaction getPistonPushReaction(BlockState state) { +@@ -0,0 +0,0 @@ public abstract class BlockBehaviour { + return this.getBlock().isSignalSource(this.asState()); + } + ++ // Paper start - optimize redstone (Alternate Current) ++ public boolean isSignalSourceTo(Level level, BlockPos pos, Direction dir) { ++ return this.getBlock().isSignalSourceTo(level, pos, this.asState(), dir); ++ } ++ ++ public boolean isDirectSignalSourceTo(Level level, BlockPos pos, Direction dir) { ++ return this.getBlock().isDirectSignalSourceTo(level, pos, this.asState(), dir); ++ } ++ // Paper end ++ + public int getSignal(BlockGetter world, BlockPos pos, Direction direction) { + return this.getBlock().getSignal(this.asState(), world, pos, direction); + } diff --git a/patches/server/Add-tick-times-API-and-mspt-command.patch b/patches/server/Add-tick-times-API-and-mspt-command.patch index 7f82fbd857..ca1c84bf80 100644 --- a/patches/server/Add-tick-times-API-and-mspt-command.patch +++ b/patches/server/Add-tick-times-API-and-mspt-command.patch @@ -84,8 +84,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 commands.put("paper", new PaperCommand("paper")); + commands.put("mspt", new MSPTCommand("mspt")); - version = getInt("config-version", 26); - set("config-version", 26); + version = getInt("config-version", 27); + set("config-version", 27); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java diff --git a/patches/server/Paper-config-files.patch b/patches/server/Paper-config-files.patch index aaf2be7d82..dbf68a4e26 100644 --- a/patches/server/Paper-config-files.patch +++ b/patches/server/Paper-config-files.patch @@ -364,8 +364,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + commands = new HashMap(); + commands.put("paper", new PaperCommand("paper")); + -+ version = getInt("config-version", 26); -+ set("config-version", 26); ++ version = getInt("config-version", 27); ++ set("config-version", 27); + readConfig(PaperConfig.class, null); + } + @@ -536,6 +536,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + ++ private void remove(String path) { ++ config.addDefault("world-settings.default." + path, null); ++ set(path, null); ++ } ++ + public void removeOldValues() { + boolean needsSave = false; +