diff options
author | Nassim Jahnke <[email protected]> | 2024-04-26 10:45:00 +0200 |
---|---|---|
committer | Nassim Jahnke <[email protected]> | 2024-04-26 10:45:00 +0200 |
commit | 813cfbf2b0c31f3468554cd6fd87bad5af03259c (patch) | |
tree | 248bb64d270c4822256a1db031b4a25dee20fe51 /patches/server/1023-Add-Alternate-Current-redstone-implementation.patch | |
parent | 7e9da43b33aab434a49743e7bc5e969a7bf3e7d2 (diff) | |
download | Paper-813cfbf2b0c31f3468554cd6fd87bad5af03259c.tar.gz Paper-813cfbf2b0c31f3468554cd6fd87bad5af03259c.zip |
Fix compile issues
Diffstat (limited to 'patches/server/1023-Add-Alternate-Current-redstone-implementation.patch')
-rw-r--r-- | patches/server/1023-Add-Alternate-Current-redstone-implementation.patch | 2114 |
1 files changed, 2114 insertions, 0 deletions
diff --git a/patches/server/1023-Add-Alternate-Current-redstone-implementation.patch b/patches/server/1023-Add-Alternate-Current-redstone-implementation.patch new file mode 100644 index 0000000000..afb3758607 --- /dev/null +++ b/patches/server/1023-Add-Alternate-Current-redstone-implementation.patch @@ -0,0 +1,2114 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Space Walker <[email protected]> +Date: Wed, 8 Jun 2022 18:47:18 +0200 +Subject: [PATCH] Add Alternate Current redstone implementation + +Author: Space Walker <[email protected]> + +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 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. + +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..8b4697421d57f81ff1794c6f845258e10df91622 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/LevelHelper.java +@@ -0,0 +1,66 @@ ++package alternate.current.wire; ++ ++import org.bukkit.craftbukkit.block.CraftBlock; ++import org.bukkit.event.block.BlockRedstoneEvent; ++ ++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.status.ChunkStatus; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++ ++public class LevelHelper { ++ ++ static int doRedstoneEvent(ServerLevel level, BlockPos pos, int prevPower, int newPower) { ++ BlockRedstoneEvent event = new BlockRedstoneEvent(CraftBlock.at(level, pos), prevPower, newPower); ++ level.getCraftServer().getPluginManager().callEvent(event); ++ ++ return event.getNewCurrent(); ++ } ++ ++ /** ++ * An optimized version of {@link net.minecraft.world.level.Level#setBlock ++ * 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..8af6c69098e64945361d116b5fd6ac21e97fcd8d +--- /dev/null ++++ b/src/main/java/alternate/current/wire/Node.java +@@ -0,0 +1,113 @@ ++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; ++ ++ /** The previous node in the priority queue. */ ++ Node prev_node; ++ /** The next node in the priority queue. */ ++ Node next_node; ++ /** The priority with which this node was queued. */ ++ int priority; ++ /** The wire that queued this node for an update. */ ++ WireNode neighborWire; ++ ++ 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 set(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; ++ } ++ ++ /** ++ * Determine the priority with which this node should be queued. ++ */ ++ int priority() { ++ return neighborWire.priority; ++ } ++ ++ 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/PriorityQueue.java b/src/main/java/alternate/current/wire/PriorityQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d71b4d0e4c44a2620b41b89475412db53bea20ed +--- /dev/null ++++ b/src/main/java/alternate/current/wire/PriorityQueue.java +@@ -0,0 +1,211 @@ ++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 PriorityQueue extends AbstractQueue<Node> { ++ ++ private static final int OFFSET = -Redstone.SIGNAL_MIN; ++ ++ /** The last node for each priority value. */ ++ private final Node[] tails; ++ ++ private Node head; ++ private Node tail; ++ ++ private int size; ++ ++ PriorityQueue() { ++ this.tails = new Node[(Redstone.SIGNAL_MAX + OFFSET) + 1]; ++ } ++ ++ @Override ++ public boolean offer(Node node) { ++ if (node == null) { ++ throw new NullPointerException(); ++ } ++ ++ int priority = node.priority(); ++ ++ if (contains(node)) { ++ if (node.priority == priority) { ++ // already queued with this priority; exit ++ return false; ++ } else { ++ // already queued with different priority; move it ++ move(node, priority); ++ } ++ } else { ++ insert(node, priority); ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public Node poll() { ++ if (head == null) { ++ return null; ++ } ++ ++ Node node = head; ++ Node next = node.next_node; ++ ++ if (next == null) { ++ clear(); // reset the tails array ++ } else { ++ if (node.priority != next.priority) { ++ // If the head is also a tail, its entry in the array ++ // can be cleared; there is no previous node with the ++ // same priority to take its place. ++ tails[node.priority + OFFSET] = null; ++ } ++ ++ node.next_node = null; ++ next.prev_node = null; ++ head = next; ++ ++ size--; ++ } ++ ++ return node; ++ } ++ ++ @Override ++ public Node peek() { ++ return head; ++ } ++ ++ @Override ++ public void clear() { ++ for (Node node = head; node != null; ) { ++ Node n = node; ++ node = node.next_node; ++ ++ n.prev_node = null; ++ n.next_node = null; ++ } ++ ++ Arrays.fill(tails, null); ++ ++ head = null; ++ tail = null; ++ ++ size = 0; ++ } ++ ++ @Override ++ public Iterator<Node> iterator() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int size() { ++ return size; ++ } ++ ++ public boolean contains(Node node) { ++ return node == head || node.prev_node != null; ++ } ++ ++ private void move(Node node, int priority) { ++ remove(node); ++ insert(node, priority); ++ } ++ ++ private void remove(Node node) { ++ Node prev = node.prev_node; ++ Node next = node.next_node; ++ ++ if (node == tail || node.priority != next.priority) { ++ // assign a new tail for this node's priority ++ if (node == head || node.priority != prev.priority) { ++ // there is no other node with the same priority; clear ++ tails[node.priority + OFFSET] = null; ++ } else { ++ // the previous node in the queue becomes the tail ++ tails[node.priority + OFFSET] = prev; ++ } ++ } ++ ++ if (node == head) { ++ head = next; ++ } else { ++ prev.next_node = next; ++ } ++ if (node == tail) { ++ tail = prev; ++ } else { ++ next.prev_node = prev; ++ } ++ ++ node.prev_node = null; ++ node.next_node = null; ++ ++ size--; ++ } ++ ++ private void insert(Node node, int priority) { ++ node.priority = priority; ++ ++ // nodes are sorted by priority (highest to lowest) ++ // nodes with the same priority are ordered FIFO ++ if (head == null) { ++ // first element in this queue \o/ ++ head = tail = node; ++ } else if (priority > head.priority) { ++ linkHead(node); ++ } else if (priority <= tail.priority) { ++ linkTail(node); ++ } else { ++ // since the node is neither the head nor the tail ++ // findPrev is guaranteed to find a non-null element ++ linkAfter(findPrev(node), node); ++ } ++ ++ tails[priority + OFFSET] = node; ++ ++ size++; ++ } ++ ++ private void linkHead(Node node) { ++ node.next_node = head; ++ head.prev_node = node; ++ head = node; ++ } ++ ++ private void linkTail(Node node) { ++ tail.next_node = node; ++ node.prev_node = tail; ++ tail = node; ++ } ++ ++ private void linkAfter(Node prev, Node node) { ++ linkBetween(prev, node, prev.next_node); ++ } ++ ++ private void linkBetween(Node prev, Node node, Node next) { ++ prev.next_node = node; ++ node.prev_node = prev; ++ ++ node.next_node = next; ++ next.prev_node = node; ++ } ++ ++ private Node findPrev(Node node) { ++ Node prev = null; ++ ++ for (int i = node.priority + OFFSET; i < tails.length; i++) { ++ prev = tails[i]; ++ ++ if (prev != null) { ++ break; ++ } ++ } ++ ++ return prev; ++ } ++} +diff --git a/src/main/java/alternate/current/wire/SimpleQueue.java b/src/main/java/alternate/current/wire/SimpleQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2b30074252551e1dc55d5be17d26fb4a2d8eb2e4 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/SimpleQueue.java +@@ -0,0 +1,112 @@ ++package alternate.current.wire; ++ ++import java.util.AbstractQueue; ++import java.util.Iterator; ++ ++public class SimpleQueue extends AbstractQueue<WireNode> { ++ ++ private WireNode head; ++ private WireNode tail; ++ ++ private int size; ++ ++ SimpleQueue() { ++ ++ } ++ ++ @Override ++ public boolean offer(WireNode node) { ++ if (node == null) { ++ throw new NullPointerException(); ++ } ++ ++ if (tail == null) { ++ head = tail = node; ++ } else { ++ tail.next_wire = node; ++ tail = node; ++ } ++ ++ size++; ++ ++ return true; ++ } ++ ++ @Override ++ public WireNode poll() { ++ if (head == null) { ++ return null; ++ } ++ ++ WireNode node = head; ++ WireNode next = node.next_wire; ++ ++ if (next == null) { ++ head = tail = null; ++ } else { ++ node.next_wire = null; ++ head = next; ++ } ++ ++ size--; ++ ++ return node; ++ } ++ ++ @Override ++ public WireNode peek() { ++ return head; ++ } ++ ++ @Override ++ public void clear() { ++ for (WireNode node = head; node != null; ) { ++ WireNode n = node; ++ node = node.next_wire; ++ ++ n.next_wire = null; ++ } ++ ++ head = null; ++ tail = null; ++ ++ size = 0; ++ } ++ ++ @Override ++ public Iterator<WireNode> iterator() { ++ return new SimpleIterator(); ++ } ++ ++ @Override ++ public int size() { ++ return size; ++ } ++ ++ private class SimpleIterator implements Iterator<WireNode> { ++ ++ private WireNode curr; ++ private WireNode next; ++ ++ private SimpleIterator() { ++ next = head; ++ } ++ ++ @Override ++ public boolean hasNext() { ++ if (next == null && curr != null) { ++ next = curr.next_wire; ++ } ++ ++ return next != null; ++ } ++ ++ @Override ++ public WireNode next() { ++ curr = next; ++ next = curr.next_wire; ++ ++ return curr; ++ } ++ } ++} +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..4fd8cb29024330397cfe4cbc1f237d285bfb7b3e +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireConnection.java +@@ -0,0 +1,30 @@ ++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..5a7209f05b549c222f6c9bc2af2a35790964947e +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireConnectionManager.java +@@ -0,0 +1,136 @@ ++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; ++ } ++ ++ total++; ++ ++ if (heads[connection.iDir] == null) { ++ heads[connection.iDir] = connection; ++ flowTotal |= (1 << connection.iDir); ++ } ++ } ++ ++ /** ++ * Iterate over all connections. Use this method if the iteration order is not ++ * important. ++ */ ++ void forEach(Consumer<WireConnection> consumer) { ++ for (WireConnection c = head; c != null; c = c.next) { ++ consumer.accept(c); ++ } ++ } ++ ++ /** ++ * Iterate over all connections. Use this method if the iteration order is ++ * important. ++ */ ++ void forEach(Consumer<WireConnection> 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..e943fdcbc15d5c17450659c2cd9e0be73ae06c0b +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireHandler.java +@@ -0,0 +1,1150 @@ ++package alternate.current.wire; ++ ++import java.util.Iterator; ++import java.util.Queue; ++import java.util.function.Consumer; ++ ++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: ++ * <br> ++ * 1. Minimize the number of times a wire checks its surroundings to determine ++ * its power level. ++ * <br> ++ * 2. Minimize the number of block and shape updates emitted. ++ * <br> ++ * 3. Emit block and shape updates in a deterministic, non-locational order, ++ * fixing bug MC-11193. ++ * ++ * <p> ++ * In Vanilla redstone wire is laggy because it fails on points 1 and 2. ++ * ++ * <p> ++ * 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. ++ * ++ * <p> ++ * In addition to this, a wire emits 42 block updates and up to 22 shape updates ++ * each time it changes its power level. ++ * ++ * <p> ++ * 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. ++ * ++ * <p> ++ * 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. ++ * ++ * <p> ++ * 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. ++ * ++ * <p> ++ * Alternate Current fixes each of these problems as follows. ++ * ++ * <p> ++ * 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: ++ * <br> ++ * - Each wire checks for power from non-wire components at most once, and from ++ * nearby wires just twice. ++ * <br> ++ * - 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. ++ * ++ * <p> ++ * 2. There are 2 obvious ways in which we can reduce the number of block and ++ * shape updates. ++ * <br> ++ * - 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. ++ * <br> ++ * - Only emit block updates and shape updates once a wire reaches its final ++ * power level, rather than at each intermediary stage. ++ * <br> ++ * For an individual wire, these two optimizations are the best you can do, but ++ * for an entire grid, you can do better! ++ * ++ * <p> ++ * 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%. ++ * ++ * <p> ++ * 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 <a href="https://bugs.mojang.com/browse/MC-81098?focusedCommentId=420777&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-420777">here</a> ++ * or by checking out its implementation in carpet mod <a href="https://github.com/gnembon/fabric-carpet/blob/master/src/main/java/carpet/helpers/RedstoneWireTurbo.java">here</a>. ++ * ++ * <p> ++ * 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. ++ * ++ * <p> ++ * 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. ++ * ++ * <p> ++ * 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 } ++ }; ++ private static final int[][] I_EXCEPT_CARDINAL = { ++ { NORTH, EAST, SOUTH }, ++ { WEST, EAST, SOUTH }, ++ { WEST, NORTH, SOUTH }, ++ { WEST, NORTH, EAST, }, ++ { WEST, NORTH, EAST, SOUTH }, ++ { WEST, NORTH, EAST, SOUTH } ++ }; ++ } ++ ++ /** ++ * This conversion table takes in information about incoming flow, and outputs ++ * the determined outgoing flow. ++ * ++ * <p> ++ * 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. ++ * ++ * <p> ++ * The output is a single Direction index, or -1 for ambiguous cases. ++ * ++ * <p> ++ * The outgoing flow is determined as follows: ++ * ++ * <p> ++ * If there is just 1 direction of incoming flow, that direction will be the ++ * direction of outgoing flow. ++ * ++ * <p> ++ * 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. ++ * ++ * <p> ++ * 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. ++ * ++ * <p> ++ * 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 orders of all directions. Given that the index encodes the direction ++ * that is to be considered 'forward', the resulting update order is ++ * { front, back, right, left, down, up }. ++ */ ++ static final int[][] FULL_UPDATE_ORDERS = { ++ { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }, ++ { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP }, ++ { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP }, ++ { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP } ++ }; ++ /** ++ * 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 = FULL_UPDATE_ORDERS[0]; ++ /** ++ * Update orders 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]; ++ ++ private static final int POWER_MIN = Redstone.SIGNAL_MIN; ++ private static final int POWER_MAX = Redstone.SIGNAL_MAX; ++ private static final int POWER_STEP = 1; ++ ++ // 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; ++ ++ /** Map of wires and neighboring blocks. */ ++ private final Long2ObjectMap<Node> nodes; ++ /** Queue for the breadth-first search through the network. */ ++ private final Queue<WireNode> search; ++ /** Queue of updates to wires and neighboring blocks. */ ++ private final Queue<Node> updates; ++ ++ // 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; ++ ++ /** Is this WireHandler currently working through the update queue? */ ++ private boolean updating; ++ ++ public WireHandler(ServerLevel level) { ++ this.level = level; ++ ++ this.nodes = new Long2ObjectOpenHashMap<>(); ++ this.search = new SimpleQueue(); ++ this.updates = new PriorityQueue(); ++ ++ this.nodeCache = new Node[16]; ++ this.fillNodeCache(0, 16); ++ } ++ ++ /** ++ * Retrieve the {@link Node Node} that represents the ++ * block at the given position in the level. ++ */ ++ 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; ++ }); ++ } ++ ++ /** ++ * Remove and return the {@link Node Node} at the given ++ * position. ++ */ ++ private Node removeNode(BlockPos pos) { ++ return nodes.remove(pos.asLong()); ++ } ++ ++ /** ++ * Return a {@link Node Node} that represents the block ++ * at the given position. ++ */ ++ private Node getNextNode(BlockPos pos) { ++ return getNextNode(pos, level.getBlockState(pos)); ++ } ++ ++ /** ++ * Return a node that represents the given position and block state. If it is a ++ * wire, then create a new {@link WireNode WireNode}. ++ * Otherwise, grab the next {@link Node Node} from the ++ * cache and update it. ++ */ ++ private Node getNextNode(BlockPos pos, BlockState state) { ++ return state.is(Blocks.REDSTONE_WIRE) ? new WireNode(level, pos, state) : getNextNode().set(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); ++ } ++ } ++ ++ /** ++ * Try to revalidate the given node by looking at the block state that is ++ * occupying its position. If the given node is a wire but the block state is ++ * not, or vice versa, a new node must be created/grabbed from the cache. ++ * Otherwise, the node can be quickly revalidated with the new block state. ++ */ ++ private Node revalidateNode(Node node) { ++ BlockPos pos = node.pos; ++ BlockState state = level.getBlockState(pos); ++ ++ boolean wasWire = node.isWire(); ++ boolean isWire = state.is(Blocks.REDSTONE_WIRE); ++ ++ if (wasWire != isWire) { ++ return getNextNode(pos, state); ++ } ++ ++ node.invalid = false; ++ ++ if (isWire) { ++ // No need to update the block state of this wire - it will grab ++ // the current block state just before setting power anyway. ++ WireNode wire = node.asWire(); ++ ++ wire.root = false; ++ wire.discovered = false; ++ wire.searched = false; ++ } else { ++ node.set(pos, state, false); ++ } ++ ++ return node; ++ } ++ ++ /** ++ * Retrieve the neighbor of a node in the given direction and create a link ++ * between the two nodes if they are not yet linked. This link makes accessing ++ * neighbors of a node signficantly faster. ++ */ ++ 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; ++ } ++ ++ /** ++ * Iterate over all neighboring nodes of the given wire. The iteration order is ++ * designed to be an extension of the default block update order, and is ++ * determined as follows: ++ * <br> ++ * 1. The direction of power flow through the wire is to be considered ++ * 'forward'. The iteration order depends on the neighbors' relative positions ++ * to the wire. ++ * <br> ++ * 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. ++ * <br> ++ * 3. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * wire. ++ * <br> ++ * 4. Neighbors are iterated over in order of their distance from the wire. This ++ * means they are iterated over in 3 groups: direct neighbors first, then ++ * diagonal neighbors, and last are the far neighbors that are 2 blocks directly ++ * out. ++ * <br> ++ * 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 forEachNeighbor(WireNode wire, Consumer<Node> consumer) { ++ int forward = wire.iFlowDir; ++ int rightward = (forward + 1) & 0b11; ++ int backward = (forward + 2) & 0b11; ++ int leftward = (forward + 3) & 0b11; ++ int downward = Directions.DOWN; ++ int upward = Directions.UP; ++ ++ Node front = getNeighbor(wire, forward); ++ Node right = getNeighbor(wire, rightward); ++ Node back = getNeighbor(wire, backward); ++ Node left = getNeighbor(wire, leftward); ++ Node below = getNeighbor(wire, downward); ++ Node above = getNeighbor(wire, upward); ++ ++ // direct neighbors (6) ++ consumer.accept(front); ++ consumer.accept(back); ++ consumer.accept(right); ++ consumer.accept(left); ++ consumer.accept(below); ++ consumer.accept(above); ++ ++ // diagonal neighbors (12) ++ consumer.accept(getNeighbor(front, rightward)); ++ consumer.accept(getNeighbor(back, leftward)); ++ consumer.accept(getNeighbor(front, leftward)); ++ consumer.accept(getNeighbor(back, rightward)); ++ consumer.accept(getNeighbor(front, downward)); ++ consumer.accept(getNeighbor(back, upward)); ++ consumer.accept(getNeighbor(front, upward)); ++ consumer.accept(getNeighbor(back, downward)); ++ consumer.accept(getNeighbor(right, downward)); ++ consumer.accept(getNeighbor(left, upward)); ++ consumer.accept(getNeighbor(right, upward)); ++ consumer.accept(getNeighbor(left, downward)); ++ ++ // far neighbors (6) ++ consumer.accept(getNeighbor(front, forward)); ++ consumer.accept(getNeighbor(back, backward)); ++ consumer.accept(getNeighbor(right, rightward)); ++ consumer.accept(getNeighbor(left, leftward)); ++ consumer.accept(getNeighbor(below, downward)); ++ consumer.accept(getNeighbor(above, upward)); ++ } ++ ++ /** ++ * This method should be called whenever a wire receives a block update. ++ */ ++ public void onWireUpdated(BlockPos pos) { ++ invalidate(); ++ findRoots(pos); ++ tryUpdate(); ++ } ++ ++ /** ++ * 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; ++ ++ invalidate(); ++ revalidateNode(wire); ++ findRoot(wire); ++ tryUpdate(); ++ } ++ ++ /** ++ * 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 (updating && wire.shouldBreak) { ++ return; ++ } ++ ++ invalidate(); ++ revalidateNode(wire); ++ findRoot(wire); ++ tryUpdate(); ++ } ++ ++ /** ++ * 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 invalidate() { ++ if (updating && !nodes.isEmpty()) { ++ Iterator<Entry<Node>> it = Long2ObjectMaps.fastIterator(nodes); ++ ++ while (it.hasNext()) { ++ Entry<Node> 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. ++ * ++ * <p> ++ * 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: ++ * ++ * <p> ++ * (top-down view, W = wire, L = lever, _ = air/other) ++ * <br> {@code _ _ W _ _ } ++ * <br> {@code _ W W W _ } ++ * <br> {@code W W L W W } ++ * <br> {@code _ W W W _ } ++ * <br> {@code _ _ W _ _ } ++ * ++ * <p> ++ * 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. ++ */ ++ private void findRoots(BlockPos pos) { ++ Node node = getOrAddNode(pos); ++ ++ if (!node.isWire()) { ++ return; // we should never get here ++ } ++ ++ WireNode wire = node.asWire(); ++ findRoot(wire); ++ ++ // If the wire at the given position is not in an invalid state ++ // we can exit early. ++ if (!wire.searched) { ++ return; ++ } ++ ++ for (int iDir : FULL_UPDATE_ORDERS[wire.iFlowDir]) { ++ Node neighbor = getNeighbor(wire, iDir); ++ ++ if (neighbor.isConductor() || neighbor.isSignalSource()) { ++ findRootsAround(neighbor, Directions.iOpposite(iDir)); ++ } ++ } ++ } ++ ++ /** ++ * Look for wires around the given node that require power changes. ++ */ ++ private void findRootsAround(Node node, int except) { ++ for (int iDir : Directions.I_EXCEPT_CARDINAL[except]) { ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (neighbor.isWire()) { ++ findRoot(neighbor.asWire()); ++ } ++ } ++ } ++ ++ /** ++ * Check if the given wire requires power changes. If it does, queue it for the ++ * breadth-first search as a root. ++ */ ++ private void findRoot(WireNode wire) { ++ // Each wire only needs to be checked once. ++ if (wire.discovered) { ++ return; ++ } ++ ++ discover(wire); ++ findExternalPower(wire); ++ findPower(wire, false); ++ ++ if (needsUpdate(wire)) { ++ searchRoot(wire); ++ } ++ } ++ ++ /** ++ * Prepare the given wire for the breadth-first search. This means: ++ * <br> ++ * - Check if the wire should break. Rather than breaking the wire right away, ++ * its effects are integrated into the power calculations. ++ * <br> ++ * - Reset the virtual and external power. ++ * <br> ++ * - Find connections to neighboring wires. ++ */ ++ private void discover(WireNode wire) { ++ if (wire.discovered) { ++ return; ++ } ++ ++ wire.discovered = true; ++ wire.searched = false; ++ ++ if (!wire.removed && !wire.shouldBreak && !wire.state.canSurvive(level, wire.pos)) { ++ wire.shouldBreak = true; ++ } ++ ++ wire.virtualPower = wire.currentPower; ++ wire.externalPower = POWER_MIN - 1; ++ ++ wire.connections.set(this::getNeighbor); ++ } ++ ++ /** ++ * Determine the power level the given wire receives from the blocks around it. ++ * Power from non-wire components only needs to be computed if power from ++ * neighboring wires has decreased, so as to determine how low the power of the ++ * wire can fall. ++ */ ++ private void findPower(WireNode wire, boolean ignoreSearched) { ++ // As wire power is (re-)computed, flow information must be reset. ++ wire.virtualPower = wire.externalPower; ++ wire.flowIn = 0; ++ ++ // 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. ++ if (wire.removed || wire.shouldBreak) { ++ return; ++ } ++ ++ // Power received from neighboring wires will never exceed POWER_MAX - ++ // POWER_STEP, so if the external power is already larger than or equal to ++ // that, there is no need to check for power from neighboring wires. ++ if (wire.externalPower < (POWER_MAX - POWER_STEP)) { ++ findWirePower(wire, ignoreSearched); ++ } ++ } ++ ++ /** ++ * Determine the power the given wire receives from connected neighboring wires ++ * and update the virtual power accordingly. ++ */ ++ private void findWirePower(WireNode wire, boolean ignoreSearched) { ++ wire.connections.forEach(connection -> { ++ if (!connection.accept) { ++ return; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ if (!ignoreSearched || !neighbor.searched) { ++ int power = Math.max(POWER_MIN, neighbor.virtualPower - POWER_STEP); ++ int iOpp = Directions.iOpposite(connection.iDir); ++ ++ wire.offerPower(power, iOpp); ++ } ++ }); ++ } ++ ++ /** ++ * Determine the redstone signal the given wire receives from non-wire ++ * components and update the virtual power accordingly. ++ */ ++ private void findExternalPower(WireNode wire) { ++ // If the wire is removed or going to break, its power level should always be ++ // the minimum value. Thus external power need not be computed. ++ // In other cases external power need only be computed once. ++ if (wire.removed || wire.shouldBreak || wire.externalPower >= POWER_MIN) { ++ return; ++ } ++ ++ wire.externalPower = getExternalPower(wire); ++ ++ if (wire.externalPower > wire.virtualPower) { ++ wire.virtualPower = wire.externalPower; ++ } ++ } ++ ++ /** ++ * Determine the redstone signal the given wire receives from non-wire ++ * components. ++ */ ++ private int getExternalPower(WireNode wire) { ++ int power = POWER_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 >= POWER_MAX) { ++ return POWER_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 = POWER_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 >= POWER_MAX) { ++ return POWER_MAX; ++ } ++ } ++ } ++ ++ return power; ++ } ++ ++ /** ++ * Check if the given wire needs to update its state in the world. ++ */ ++ private boolean needsUpdate(WireNode wire) { ++ return wire.removed || wire.shouldBreak || wire.virtualPower != wire.currentPower; ++ } ++ ++ /** ++ * Queue the given wire for the breadth-first search as a root. ++ */ ++ private void searchRoot(WireNode wire) { ++ int iBackupFlowDir; ++ ++ if (wire.connections.iFlowDir < 0) { ++ iBackupFlowDir = 0; ++ } else { ++ iBackupFlowDir = wire.connections.iFlowDir; ++ } ++ ++ search(wire, true, iBackupFlowDir); ++ } ++ ++ /** ++ * Queue the given wire for the breadth-first search and set a backup flow ++ * direction. ++ */ ++ private void search(WireNode wire, boolean root, int iBackupFlowDir) { ++ search.offer(wire); ++ ++ wire.root = root; ++ wire.searched = 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; ++ } ++ ++ private void tryUpdate() { ++ if (!search.isEmpty()) { ++ update(); ++ } ++ if (!updating) { ++ nodes.clear(); ++ nodeCount = 0; ++ } ++ } ++ ++ /** ++ * Update the network and neighboring blocks. This is done in 3 steps. ++ * ++ * <p> ++ * <b>1. Search through the network</b> ++ * <br> ++ * Conduct a breadth-first search around the roots to find wires that are in an ++ * invalid state and need power changes. ++ * ++ * <p> ++ * <b>2. Depower the network</b> ++ * <br> ++ * Depower all wires in the network. This allows power to be spread most ++ * efficiently. ++ * ++ * <p> ++ * <b>3. Power the network</b> ++ * <br> ++ * Work through the update queue, setting the new power level of each wire and ++ * updating neighboring blocks. After a wire has updated its power level, it ++ * will emit shape updates and queue updates for neighboring wires and blocks. ++ */ ++ private void update() { ++ // Search through the network for wires that need power changes. This includes ++ // the roots as well as any wires that will be affected by power changes to ++ // those roots. ++ searchNetwork(); ++ ++ // Depower all the wires in the network. ++ depowerNetwork(); ++ ++ // Bring each wire up to its new power level and update neighboring blocks. ++ try { ++ powerNetwork(); ++ } 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. ++ updating = false; ++ ++ throw t; ++ } ++ } ++ ++ /** ++ * Search through the network for wires that are in an invalid state and need ++ * power changes. These wires are added to the end of the queue, so that their ++ * neighbors can be searched next. ++ */ ++ private void searchNetwork() { ++ for (WireNode wire : search) { ++ // The order in which wires are searched will influence the order in ++ // which they update their power levels. ++ wire.connections.forEach(connection -> { ++ if (!connection.offer) { ++ return; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ if (neighbor.searched) { ++ return; ++ } ++ ++ discover(neighbor); ++ findPower(neighbor, false); ++ ++ // If power from neighboring wires has decreased, check for power ++ // from non-wire components so as to determine how low power can ++ // fall. ++ if (neighbor.virtualPower < neighbor.currentPower) { ++ findExternalPower(neighbor); ++ } ++ ++ if (needsUpdate(neighbor)) { ++ search(neighbor, false, connection.iDir); ++ } ++ }, wire.iFlowDir); ++ } ++ } ++ ++ /** ++ * Depower all wires in the network so that power can be spread from the power ++ * sources. ++ */ ++ private void depowerNetwork() { ++ while (!search.isEmpty()) { ++ WireNode wire = search.poll(); ++ findPower(wire, true); ++ ++ if (wire.root || wire.removed || wire.shouldBreak || wire.virtualPower > POWER_MIN) { ++ queueWire(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--; ++ } ++ } ++ } ++ ++ /** ++ * Work through the update queue, setting the new power level of each wire, then ++ * queueing updates to connected wires and neighboring blocks. ++ */ ++ private void powerNetwork() { ++ // 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 (updating) { ++ return; ++ } ++ ++ updating = true; ++ ++ while (!updates.isEmpty()) { ++ Node node = updates.poll(); ++ ++ if (node.isWire()) { ++ WireNode wire = node.asWire(); ++ ++ if (!needsUpdate(wire)) { ++ continue; ++ } ++ ++ findPowerFlow(wire); ++ transmitPower(wire); ++ ++ if (wire.setPower()) { ++ queueNeighbors(wire); ++ ++ // If the wire was newly placed or removed, shape updates have ++ // already been emitted. However, unlike before 1.19, neighbor ++ // updates are now queued, so to preserve behavior parity with ++ // previous versions, we emit extra shape updates here to ++ // notify neighboring observers. ++ updateNeighborShapes(wire); ++ } ++ } else { ++ WireNode neighborWire = node.neighborWire; ++ ++ if (neighborWire != null) { ++ BlockPos neighborPos = neighborWire.pos; ++ Block neighborBlock = neighborWire.state.getBlock(); ++ ++ updateBlock(node, neighborPos, neighborBlock); ++ } ++ } ++ } ++ ++ updating = false; ++ } ++ ++ /** ++ * 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 and queue updates to ++ * those wires. ++ */ ++ private void transmitPower(WireNode wire) { ++ wire.connections.forEach(connection -> { ++ if (!connection.offer) { ++ return; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ int power = Math.max(POWER_MIN, wire.virtualPower - POWER_STEP); ++ int iDir = connection.iDir; ++ ++ if (neighbor.offerPower(power, iDir)) { ++ queueWire(neighbor); ++ } ++ }, wire.iFlowDir); ++ } ++ ++ /** ++ * Emit shape updates around the given wire. ++ */ ++ private void updateNeighborShapes(WireNode wire) { ++ BlockPos wirePos = wire.pos; ++ BlockState wireState = wire.state; ++ ++ for (int iDir : DEFAULT_FULL_UPDATE_ORDER) { ++ Node neighbor = getNeighbor(wire, iDir); ++ ++ if (!neighbor.isWire()) { ++ int iOpp = Directions.iOpposite(iDir); ++ Direction opp = Directions.ALL[iOpp]; ++ ++ updateShape(neighbor, opp, wirePos, wireState); ++ } ++ } ++ } ++ ++ private void updateShape(Node node, Direction dir, BlockPos neighborPos, BlockState neighborState) { ++ BlockPos pos = node.pos; ++ 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(dir, neighborState, level, pos, neighborPos); ++ Block.updateOrDestroy(state, newState, level, pos, Block.UPDATE_CLIENTS); ++ } ++ } ++ ++ /** ++ * Queue block updates to nodes around the given wire. ++ */ ++ private void queueNeighbors(WireNode wire) { ++ forEachNeighbor(wire, neighbor -> { ++ queueNeighbor(neighbor, wire); ++ }); ++ } ++ ++ /** ++ * Queue the given node for an update from the given neighboring wire. ++ */ ++ private void queueNeighbor(Node node, WireNode neighborWire) { ++ // Updates to wires are queued when power is transmitted. ++ if (!node.isWire()) { ++ node.neighborWire = neighborWire; ++ updates.offer(node); ++ } ++ } ++ ++ /** ++ * Queue the given wire for a power change. If the wire does not need a power ++ * change (perhaps because its power has already changed), transmit power to ++ * neighboring wires. ++ */ ++ private void queueWire(WireNode wire) { ++ if (needsUpdate(wire)) { ++ updates.offer(wire); ++ } else { ++ findPowerFlow(wire); ++ transmitPower(wire); ++ } ++ } ++ ++ /** ++ * Emit a block update to the given node. ++ */ ++ private void updateBlock(Node node, BlockPos neighborPos, Block neighborBlock) { ++ BlockPos pos = node.pos; ++ 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.handleNeighborChanged(level, pos, neighborBlock, neighborPos, false); ++ } ++ } ++ ++ @FunctionalInterface ++ public static 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..33cd90c30c22200a4e1ae64f40a0bf7864546b33 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireNode.java +@@ -0,0 +1,122 @@ ++package alternate.current.wire; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.util.Mth; ++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 root; ++ boolean discovered; ++ boolean searched; ++ ++ /** The next wire in the simple queue. */ ++ WireNode next_wire; ++ ++ 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); ++ this.priority = priority(); ++ } ++ ++ @Override ++ Node set(BlockPos pos, BlockState state, boolean clearNeighbors) { ++ throw new UnsupportedOperationException("Cannot update a WireNode!"); ++ } ++ ++ @Override ++ int priority() { ++ return Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX); ++ } ++ ++ @Override ++ public boolean isWire() { ++ return true; ++ } ++ ++ @Override ++ public WireNode asWire() { ++ return this; ++ } ++ ++ 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 (!state.is(Blocks.REDSTONE_WIRE)) { ++ return false; // we should never get here ++ } ++ ++ if (shouldBreak) { ++ Block.dropResources(state, level, pos); ++ level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS); ++ ++ return true; ++ } ++ ++ currentPower = LevelHelper.doRedstoneEvent(level, pos, currentPower, Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX)); ++ state = state.setValue(RedStoneWireBlock.POWER, currentPower); ++ ++ return LevelHelper.setWireState(level, pos, state, added); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index aabaaf89f5e344791604a7e05122d9865bb0224e..2bfebbe0bc5a7f72c9ebe60fded260b3f94ac749 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -228,6 +228,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent + public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent ++ private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + + public LevelChunk getChunkIfLoaded(int x, int z) { + return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately +@@ -2566,6 +2567,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + return crashreportsystemdetails; + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public alternate.current.wire.WireHandler getWireHandler() { ++ return wireHandler; ++ } ++ // Paper end - optimize redstone (Alternate Current) ++ + private final class EntityCallbacks implements LevelCallback<Entity> { + + EntityCallbacks() {} +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 07281d73fcbca2ea5f8bce25f6bf961d258bf8a0..0a8eeebb2d702ebcefd9f26cc0f41d1eab497902 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1881,4 +1881,14 @@ 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 - optimize redstone (Alternate Current) + } +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 18ed178223cca85dbba65b1e07741622e266d318..c131734cad123a35456d18f8a161f77a4ac9ac99 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -258,7 +258,7 @@ 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 + com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this); + +@@ -460,7 +460,13 @@ public class RedStoneWireBlock extends Block { + @Override + protected 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().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ world.getWireHandler().onWireAdded(pos); // Alternate Current ++ } else { ++ this.updateSurroundingRedstone(world, pos, state, null); // vanilla/Eigencraft ++ } ++ // Paper end - optimize redstone + Iterator iterator = Direction.Plane.VERTICAL.iterator(); + + while (iterator.hasNext()) { +@@ -487,7 +493,13 @@ 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().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ world.getWireHandler().onWireRemoved(pos, state); // Alternate Current ++ } else { ++ this.updateSurroundingRedstone(world, pos, state, null); // vanilla/Eigencraft ++ } ++ // Paper end - optimize redstone + this.updateNeighborsOfNeighboringWires(world, pos); + } + } +@@ -521,8 +533,14 @@ public class RedStoneWireBlock extends Block { + @Override + protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { + if (!world.isClientSide) { ++ // Paper start - optimize redstone (Alternate Current) ++ // Alternate Current handles breaking of redstone wires in the WireHandler. ++ if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ world.getWireHandler().onWireUpdated(pos); ++ } else ++ // Paper end - optimize redstone (Alternate Current) + if (state.canSurvive(world, pos)) { +- this.updateSurroundingRedstone(world, pos, state, sourcePos); // Paper - Optimize redstone ++ this.updateSurroundingRedstone(world, pos, state, sourcePos); // Paper - Optimize redstone (Eigencraft) + } else { + dropResources(state, world, pos); + world.removeBlock(pos, false); |