2384 lines
89 KiB
Diff
2384 lines
89 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Space Walker <spacedoesrs@gmail.com>
|
|
Date: Wed, 8 Jun 2022 18:47:18 +0200
|
|
Subject: [PATCH] Add Alternate Current redstone implementation
|
|
|
|
Author: Space Walker <spacedoesrs@gmail.com>
|
|
|
|
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..f55c5c67b8461e9ef5614ea1a37f6e2866f39be3
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/LevelHelper.java
|
|
@@ -0,0 +1,65 @@
|
|
+package alternate.current.wire;
|
|
+
|
|
+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.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(level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), 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..3e25047fd8db65bf34bb39ae9a07fe01b5b61786
|
|
--- /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 update queue. */
|
|
+ Node prev;
|
|
+ /** The next node in the update queue. */
|
|
+ Node next;
|
|
+ /** 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 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;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * 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/UpdateQueue.java b/src/main/java/alternate/current/wire/UpdateQueue.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..3afe0ef9dfe16c2dc144b553069286b6e72eac1b
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/UpdateQueue.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 UpdateQueue 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;
|
|
+
|
|
+ public UpdateQueue() {
|
|
+ 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;
|
|
+
|
|
+ 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 = null;
|
|
+ next.prev = 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;
|
|
+
|
|
+ n.prev = null;
|
|
+ n.next = 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 != null;
|
|
+ }
|
|
+
|
|
+ private void move(Node node, int priority) {
|
|
+ remove(node);
|
|
+ insert(node, priority);
|
|
+ }
|
|
+
|
|
+ private void remove(Node node) {
|
|
+ Node prev = node.prev;
|
|
+ Node next = node.next;
|
|
+
|
|
+ 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 = next;
|
|
+ }
|
|
+ if (node == tail) {
|
|
+ tail = prev;
|
|
+ } else {
|
|
+ next.prev = prev;
|
|
+ }
|
|
+
|
|
+ node.prev = null;
|
|
+ node.next = 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 = head;
|
|
+ head.prev = node;
|
|
+ head = node;
|
|
+ }
|
|
+
|
|
+ private void linkTail(Node node) {
|
|
+ tail.next = node;
|
|
+ node.prev = tail;
|
|
+ tail = node;
|
|
+ }
|
|
+
|
|
+ private void linkAfter(Node prev, Node node) {
|
|
+ linkBetween(prev, node, prev.next);
|
|
+ }
|
|
+
|
|
+ private void linkBetween(Node prev, Node node, Node next) {
|
|
+ prev.next = node;
|
|
+ node.prev = prev;
|
|
+
|
|
+ node.next = next;
|
|
+ next.prev = 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/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..bdc4969e157de034929c8a0ffebc62075aad59f8
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/WireHandler.java
|
|
@@ -0,0 +1,1179 @@
|
|
+package alternate.current.wire;
|
|
+
|
|
+import java.util.ArrayList;
|
|
+import java.util.Iterator;
|
|
+import java.util.List;
|
|
+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 just 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 }
|
|
+ };
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * 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 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
|
|
+ };
|
|
+
|
|
+ private static final int POWER_MAX = Redstone.SIGNAL_MAX;
|
|
+ private static final int POWER_MIN = Redstone.SIGNAL_MIN;
|
|
+ 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;
|
|
+
|
|
+ /** All the wires in the network. */
|
|
+ private final List<WireNode> network;
|
|
+ /** Map of wires and neighboring blocks. */
|
|
+ private final Long2ObjectMap<Node> nodes;
|
|
+ /** The queue for updating wires and neighboring nodes. */
|
|
+ private final Queue<Node> updates;
|
|
+
|
|
+ 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;
|
|
+
|
|
+ /** Is this WireHandler currently working through the update queue? */
|
|
+ private boolean updating;
|
|
+
|
|
+ public WireHandler(ServerLevel level) {
|
|
+ this.level = level;
|
|
+
|
|
+ this.network = new ArrayList<>();
|
|
+ this.nodes = new Long2ObjectOpenHashMap<>();
|
|
+ this.updates = new UpdateQueue();
|
|
+
|
|
+ this.nodeCache = new Node[16];
|
|
+ this.fillNodeCache(0, 16);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Retrieve the {@link alternate.current.wire.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;
|
|
+ });
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Return a {@link alternate.current.wire.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 alternate.current.wire.WireNode WireNode}.
|
|
+ * Otherwise, grab the next {@link alternate.current.wire.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().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);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private Node removeNode(BlockPos pos) {
|
|
+ return nodes.remove(pos.asLong());
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * 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, 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.prepared = false;
|
|
+ wire.inNetwork = false;
|
|
+ } else {
|
|
+ node.update(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.
|
|
+ */
|
|
+ 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) {
|
|
+ invalidateNodes();
|
|
+ findRoots(pos);
|
|
+ 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();
|
|
+ tryAddRoot(wire);
|
|
+ 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 (updating && 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 (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.
|
|
+ *
|
|
+ * <p>
|
|
+ * 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) {
|
|
+ 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 (!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
|
|
+ * <br>
|
|
+ * - checks if this wire should break. Rather than break the wire right away,
|
|
+ * its effects are integrated into the power calculations.
|
|
+ * <br>
|
|
+ * - determines the 'external power' this wire receives (power from non-wire
|
|
+ * components).
|
|
+ * <br>
|
|
+ * - 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) ? POWER_MIN : getExternalPower(wire);
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * 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:
|
|
+ * <br>
|
|
+ * - 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.
|
|
+ * <br>
|
|
+ * - Power received from neighboring wires will never exceed {@code 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.
|
|
+ */
|
|
+ private void findPower(WireNode wire, boolean ignoreNetwork) {
|
|
+ if (wire.removed || wire.shouldBreak || wire.externalPower >= (POWER_MAX - POWER_STEP)) {
|
|
+ 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(POWER_MIN, neighbor.virtualPower - POWER_STEP);
|
|
+ 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 (!updating) {
|
|
+ nodes.clear();
|
|
+ nodeCount = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Propagate power changes through the network and notify neighboring blocks of
|
|
+ * these changes.
|
|
+ *
|
|
+ * <p>
|
|
+ * Power changes are done in the following 3 steps.
|
|
+ *
|
|
+ * <p>
|
|
+ * <b>1. Build up the network</b>
|
|
+ * <br>
|
|
+ * Collect all the wires around the roots that need to change their power
|
|
+ * levels.
|
|
+ *
|
|
+ * <p>
|
|
+ * <b>2. Find powered wires</b>
|
|
+ * <br>
|
|
+ * Find those wires in the network that receive power from outside the network.
|
|
+ * This can come in 2 forms:
|
|
+ * <br>
|
|
+ * - Power from non-wire components (repeaters, torches, etc.).
|
|
+ * <br>
|
|
+ * - Power from wires that are not in the network.
|
|
+ * <br>
|
|
+ * These powered wires will then queue their power changes.
|
|
+ *
|
|
+ * <p>
|
|
+ * <b>3. Let power flow</b>
|
|
+ * <br>
|
|
+ * 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.
|
|
+ updating = 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 > 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--;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * 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 (updating) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ updating = true;
|
|
+
|
|
+ while (!updates.isEmpty()) {
|
|
+ Node node = updates.poll();
|
|
+
|
|
+ if (node.isWire()) {
|
|
+ WireNode wire = node.asWire();
|
|
+
|
|
+ if (!needsPowerChange(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();
|
|
+
|
|
+ updateNeighbor(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];
|
|
+
|
|
+ updateNeighborShape(neighbor, opp, wirePos, wireState);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void updateNeighborShape(Node node, Direction fromDir, BlockPos fromPos, BlockState fromState) {
|
|
+ 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(fromDir, fromState, level, pos, fromPos);
|
|
+ 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 (needsPowerChange(wire)) {
|
|
+ updates.offer(wire);
|
|
+ } else {
|
|
+ findPowerFlow(wire);
|
|
+ transmitPower(wire);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Emit a block update to the given node.
|
|
+ */
|
|
+ private void updateNeighbor(Node node, BlockPos fromPos, Block fromBlock) {
|
|
+ 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.neighborChanged(level, pos, fromBlock, fromPos, 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..dde98a49b0f4db023386f8e4b98c99340d51c871
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/WireNode.java
|
|
@@ -0,0 +1,118 @@
|
|
+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 prepared;
|
|
+ boolean inNetwork;
|
|
+
|
|
+ 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 update(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 c8294d999cdce92b4747f1e6fbbed7fcb90ce8df..50f0fa0ebfe5b34af6ce8162d18bd0f7b1a57fb1 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -216,6 +216,7 @@ 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());
|
|
}
|
|
@@ -2481,6 +2482,13 @@ 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<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 13c4b7aee9b9802edbaf7e4df9e9355667e727bb..76aa5d1bcb2ccb554966dcb5916ae527d73f042e 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -1448,4 +1448,15 @@ 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 f9aaec28be3e7a191981d30b361e369d7fea2c9e..da7044e6affbbd364e997abe5a3c76459f4c35cf 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
|
|
@@ -153,6 +153,18 @@ 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 a0194e78913017693df7d92516dfbacb1153a1c2..f935719c569e2286d1089a7e3e5dadff22323c36 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java
|
|
@@ -159,6 +159,18 @@ 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, RandomSource 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 16504b8be08064e61b013fa943f692816612cbd0..a2016e8b3860171e0f6101f4a2a724e44dee48d7 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java
|
|
@@ -101,6 +101,13 @@ 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 aa4ed127fe463fad1bf7ddadb54e93958ee970c7..02bf8a15f9bc233361c0c2b2e0e3ec481700f706 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/DiodeBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/DiodeBlock.java
|
|
@@ -159,6 +159,18 @@ 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 5ef0705bae9b4ca2cda4f17aeefc0fe657040160..8398fd1b346c87cc1e1fe4ee93774a156a4e1945 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/LecternBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/LecternBlock.java
|
|
@@ -221,6 +221,18 @@ 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 057ff8ba1c924c032a03389dcf4f99b4b386fb0a..997ac10a5236efcb6627a90dc65259d2b93ae6d1 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/LeverBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/LeverBlock.java
|
|
@@ -166,6 +166,18 @@ 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 47625532a39e6b80c9452c75604369c0ddb78fbd..1df5679ba50c4ef6237e0e28ea80cce776b324c5 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java
|
|
@@ -166,4 +166,16 @@ 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 7b45d6b9a005036ca5051d089a7be792eb87012f..25c2c44bde443ab08734253ed7c98c81a345e573 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java
|
|
@@ -90,6 +90,18 @@ 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 0afffc33f3be221a28c62115f493808aeffb1bd8..0bd23df47085d578102a28157e309b585f4231f8 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/PoweredBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/PoweredBlock.java
|
|
@@ -16,6 +16,13 @@ public class PoweredBlock extends Block {
|
|
return true;
|
|
}
|
|
|
|
+ // Paper start - optimize redstone (Alternate Current)
|
|
+ @Override
|
|
+ public boolean isSignalSourceTo(net.minecraft.world.level.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 361ac29180bed08d5b5c15d37b4b04eb54ee6bac..0590b12ec3299c3c884b772430a97c0b90c3dc2e 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
|
|
@@ -253,7 +253,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
|
|
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().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
|
|
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
|
|
this.updateNeighborsOfNeighboringWires(world, pos);
|
|
}
|
|
}
|
|
@@ -521,8 +533,14 @@ public class RedStoneWireBlock extends Block {
|
|
@Override
|
|
public 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
|
|
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);
|
|
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 da07fce0cf7c9fbdb57d2c59e431b59bf583bf50..ac178507010a8c879ccf28764166b100c4a32788 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java
|
|
@@ -139,6 +139,18 @@ 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, RandomSource 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 e5a4525ae64454ec1400de1e4e7945b2caa675f8..cbc2dc375053e304bdcd40b517028dd931056b54 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java
|
|
@@ -76,6 +76,13 @@ 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 bdb6875dff56b7b909bfa9ddc58570d8dfafca8e..ec00dc5bcd2a5ab0dd3e70ddc6bc29673da9da10 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java
|
|
@@ -224,6 +224,13 @@ 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 69eaf1341d282c4783dab84533ea2c053deed529..f2839769d49f9158c163908ec597c5ed7e0f49f8 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/TargetBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/TargetBlock.java
|
|
@@ -112,6 +112,13 @@ 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<Block, BlockState> 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 184c70cd2954f4904518c3fee2a377d9c4e81cc3..316fd2758a3a981fd1a5e3603a4b7a064270f7fb 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/TrappedChestBlock.java
|
|
@@ -36,6 +36,18 @@ public class TrappedChestBlock extends ChestBlock {
|
|
return true;
|
|
}
|
|
|
|
+ // Paper start - optimize redstone (Alternate Current)
|
|
+ @Override
|
|
+ public boolean isSignalSourceTo(net.minecraft.world.level.Level level, BlockPos pos, BlockState state, Direction dir) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isDirectSignalSourceTo(net.minecraft.world.level.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 004dce26ff073f1de52a84cd425c4f60fdab5e50..90555e0cef5936aa5e63821e65abb84fec001fd7 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
|
|
@@ -266,6 +266,18 @@ 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 771c6cf992664b65ffbf4ae0192bc7b09f77c2e6..939aca929038b90738c9b78c2fc3611088b18e72 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
|
|
@@ -188,6 +188,16 @@ 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) {
|
|
@@ -866,6 +876,16 @@ 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);
|
|
}
|