2336 lines
88 KiB
Diff
2336 lines
88 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Space Walker <spacedoesrs@gmail.com>
|
|
Date: Tue, 5 Apr 2022 01:01:13 +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..186e0a86ceaed007d394378157ce1596a0601908
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/LevelHelper.java
|
|
@@ -0,0 +1,64 @@
|
|
+package alternate.current.wire;
|
|
+
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.world.level.block.Block;
|
|
+import net.minecraft.world.level.block.state.BlockState;
|
|
+import net.minecraft.world.level.chunk.ChunkAccess;
|
|
+import net.minecraft.world.level.chunk.ChunkStatus;
|
|
+import net.minecraft.world.level.chunk.LevelChunkSection;
|
|
+
|
|
+import org.bukkit.event.block.BlockRedstoneEvent;
|
|
+
|
|
+public class LevelHelper {
|
|
+
|
|
+ static int doRedstoneEvent(ServerLevel level, BlockPos pos, int prevPower, int newPower) {
|
|
+ BlockRedstoneEvent event = new BlockRedstoneEvent(level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), prevPower, newPower);
|
|
+ level.getCraftServer().getPluginManager().callEvent(event);
|
|
+
|
|
+ return event.getNewCurrent();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * An optimized version of Level.setBlock. Since this method is only used to
|
|
+ * update redstone wire block states, lighting checks, height map updates, and
|
|
+ * block entity updates are omitted.
|
|
+ */
|
|
+ static boolean setWireState(ServerLevel level, BlockPos pos, BlockState state, boolean updateNeighborShapes) {
|
|
+ int y = pos.getY();
|
|
+
|
|
+ if (y < level.getMinBuildHeight() || y >= level.getMaxBuildHeight()) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ int x = pos.getX();
|
|
+ int z = pos.getZ();
|
|
+ int index = level.getSectionIndex(y);
|
|
+
|
|
+ ChunkAccess chunk = level.getChunk(x >> 4, z >> 4, ChunkStatus.FULL, true);
|
|
+ LevelChunkSection section = chunk.getSections()[index];
|
|
+
|
|
+ if (section == null) {
|
|
+ return false; // we should never get here
|
|
+ }
|
|
+
|
|
+ BlockState prevState = section.setBlockState(x & 15, y & 15, z & 15, state);
|
|
+
|
|
+ if (state == prevState) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ // notify clients of the BlockState change
|
|
+ level.getChunkSource().blockChanged(pos);
|
|
+ // mark the chunk for saving
|
|
+ chunk.setUnsaved(true);
|
|
+
|
|
+ if (updateNeighborShapes) {
|
|
+ prevState.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_CLIENTS);
|
|
+ state.updateNeighbourShapes(level, pos, Block.UPDATE_CLIENTS);
|
|
+ state.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_CLIENTS);
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/alternate/current/wire/Node.java b/src/main/java/alternate/current/wire/Node.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..acfe97d62ef863a14e2e43db30917bd57bbd5171
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/Node.java
|
|
@@ -0,0 +1,97 @@
|
|
+package alternate.current.wire;
|
|
+
|
|
+import java.util.Arrays;
|
|
+
|
|
+import alternate.current.wire.WireHandler.Directions;
|
|
+
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.world.level.block.Blocks;
|
|
+import net.minecraft.world.level.block.state.BlockState;
|
|
+
|
|
+/**
|
|
+ * A Node represents a block in the world. It also holds a few other pieces of
|
|
+ * information that speed up the calculations in the WireHandler class.
|
|
+ *
|
|
+ * @author Space Walker
|
|
+ */
|
|
+public class Node {
|
|
+
|
|
+ // flags that encode the Node type
|
|
+ private static final int CONDUCTOR = 0b01;
|
|
+ private static final int SOURCE = 0b10;
|
|
+
|
|
+ final ServerLevel level;
|
|
+ final Node[] neighbors;
|
|
+
|
|
+ BlockPos pos;
|
|
+ BlockState state;
|
|
+ boolean invalid;
|
|
+
|
|
+ private int flags;
|
|
+
|
|
+ Node(ServerLevel level) {
|
|
+ this.level = level;
|
|
+ this.neighbors = new Node[Directions.ALL.length];
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean equals(Object obj) {
|
|
+ if (this == obj) {
|
|
+ return true;
|
|
+ }
|
|
+ if (!(obj instanceof Node)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ Node node = (Node)obj;
|
|
+
|
|
+ return level == node.level && pos.equals(node.pos);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int hashCode() {
|
|
+ return pos.hashCode();
|
|
+ }
|
|
+
|
|
+ Node update(BlockPos pos, BlockState state, boolean clearNeighbors) {
|
|
+ if (state.is(Blocks.REDSTONE_WIRE)) {
|
|
+ throw new IllegalStateException("Cannot update a regular Node to a WireNode!");
|
|
+ }
|
|
+
|
|
+ if (clearNeighbors) {
|
|
+ Arrays.fill(neighbors, null);
|
|
+ }
|
|
+
|
|
+ this.pos = pos.immutable();
|
|
+ this.state = state;
|
|
+ this.invalid = false;
|
|
+
|
|
+ this.flags = 0;
|
|
+
|
|
+ if (this.state.isRedstoneConductor(this.level, this.pos)) {
|
|
+ this.flags |= CONDUCTOR;
|
|
+ }
|
|
+ if (this.state.isSignalSource()) {
|
|
+ this.flags |= SOURCE;
|
|
+ }
|
|
+
|
|
+ return this;
|
|
+ }
|
|
+
|
|
+ public boolean isWire() {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public boolean isConductor() {
|
|
+ return (flags & CONDUCTOR) != 0;
|
|
+ }
|
|
+
|
|
+ public boolean isSignalSource() {
|
|
+ return (flags & SOURCE) != 0;
|
|
+ }
|
|
+
|
|
+ public WireNode asWire() {
|
|
+ throw new UnsupportedOperationException("Not a WireNode!");
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/alternate/current/wire/PowerQueue.java b/src/main/java/alternate/current/wire/PowerQueue.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..9c293bef09ad1c95ba24ea6c41a27c5489b548dd
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/PowerQueue.java
|
|
@@ -0,0 +1,209 @@
|
|
+package alternate.current.wire;
|
|
+
|
|
+import java.util.AbstractQueue;
|
|
+import java.util.Arrays;
|
|
+import java.util.Iterator;
|
|
+
|
|
+import net.minecraft.world.level.redstone.Redstone;
|
|
+
|
|
+public class PowerQueue extends AbstractQueue<WireNode> {
|
|
+
|
|
+ private static final int OFFSET = -Redstone.SIGNAL_MIN;
|
|
+
|
|
+ /** The last wire for each power level. */
|
|
+ private final WireNode[] tails;
|
|
+
|
|
+ private WireNode head;
|
|
+ private WireNode tail;
|
|
+
|
|
+ private int size;
|
|
+
|
|
+ public PowerQueue() {
|
|
+ this.tails = new WireNode[(Redstone.SIGNAL_MAX + 1) - Redstone.SIGNAL_MIN];
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean offer(WireNode wire) {
|
|
+ if (wire == null) {
|
|
+ throw new NullPointerException();
|
|
+ }
|
|
+
|
|
+ int power = wire.nextPower();
|
|
+
|
|
+ if (contains(wire)) {
|
|
+ if (wire.power == power) {
|
|
+ // already queued for this power; exit
|
|
+ return false;
|
|
+ } else {
|
|
+ // already queued for different power; move it
|
|
+ move(wire, power);
|
|
+ }
|
|
+ } else {
|
|
+ insert(wire, power);
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public WireNode poll() {
|
|
+ if (head == null) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ WireNode wire = head;
|
|
+ WireNode next = wire.next;
|
|
+
|
|
+ if (next == null) {
|
|
+ clear(); // reset the tails array
|
|
+ } else {
|
|
+ if (wire.power != next.power) {
|
|
+ // if the head is also a tail, its entry in the array
|
|
+ // can be cleared; there is no previous wire with the
|
|
+ // same power to take its place.
|
|
+ tails[wire.power + OFFSET] = null;
|
|
+ }
|
|
+
|
|
+ wire.next = null;
|
|
+ next.prev = null;
|
|
+ head = next;
|
|
+
|
|
+ size--;
|
|
+ }
|
|
+
|
|
+ return wire;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public WireNode peek() {
|
|
+ return head;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void clear() {
|
|
+ for (WireNode wire = head; wire != null; ) {
|
|
+ WireNode w = wire;
|
|
+ wire = wire.next;
|
|
+
|
|
+ w.prev = null;
|
|
+ w.next = null;
|
|
+ }
|
|
+
|
|
+ head = null;
|
|
+ tail = null;
|
|
+
|
|
+ Arrays.fill(tails, null);
|
|
+
|
|
+ size = 0;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Iterator<WireNode> iterator() {
|
|
+ throw new UnsupportedOperationException();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int size() {
|
|
+ return size;
|
|
+ }
|
|
+
|
|
+ public boolean contains(WireNode wire) {
|
|
+ return wire == head || wire.prev != null;
|
|
+ }
|
|
+
|
|
+ private void move(WireNode wire, int power) {
|
|
+ remove(wire);
|
|
+ insert(wire, power);
|
|
+ }
|
|
+
|
|
+ private void remove(WireNode wire) {
|
|
+ if (wire == tail || wire.power != wire.next.power) {
|
|
+ // assign a new tail for this wire's power
|
|
+ if (wire == head || wire.power != wire.prev.power) {
|
|
+ // there is no other wire with the same power; clear
|
|
+ tails[wire.power + OFFSET] = null;
|
|
+ } else {
|
|
+ // the previous wire in the queue becomes the tail
|
|
+ tails[wire.power + OFFSET] = wire.prev;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (wire == head) {
|
|
+ head = wire.next;
|
|
+ } else {
|
|
+ wire.prev.next = wire.next;
|
|
+ }
|
|
+ if (wire == tail) {
|
|
+ tail = wire.prev;
|
|
+ } else {
|
|
+ wire.next.prev = wire.prev;
|
|
+ }
|
|
+
|
|
+ wire.prev = null;
|
|
+ wire.next = null;
|
|
+
|
|
+ size--;
|
|
+ }
|
|
+
|
|
+ private void insert(WireNode wire, int power) {
|
|
+ // store the power for which this wire is queued
|
|
+ wire.power = power;
|
|
+
|
|
+ // wires are sorted by power (highest to lowest)
|
|
+ // wires with the same power are ordered FIFO
|
|
+ if (head == null) {
|
|
+ // first element in this queue \o/
|
|
+ head = tail = wire;
|
|
+ } else if (wire.power > head.power) {
|
|
+ linkHead(wire);
|
|
+ } else if (wire.power <= tail.power) {
|
|
+ linkTail(wire);
|
|
+ } else {
|
|
+ // since the wire is neither the head nor the tail
|
|
+ // findPrev is guaranteed to find a non-null element
|
|
+ linkAfter(findPrev(wire), wire);
|
|
+ }
|
|
+
|
|
+ tails[power + OFFSET] = wire;
|
|
+
|
|
+ size++;
|
|
+ }
|
|
+
|
|
+ private void linkHead(WireNode wire) {
|
|
+ wire.next = head;
|
|
+ head.prev = wire;
|
|
+ head = wire;
|
|
+ }
|
|
+
|
|
+ private void linkTail(WireNode wire) {
|
|
+ tail.next = wire;
|
|
+ wire.prev = tail;
|
|
+ tail = wire;
|
|
+ }
|
|
+
|
|
+ private void linkAfter(WireNode prev, WireNode wire) {
|
|
+ linkBetween(prev, wire, prev.next);
|
|
+ }
|
|
+
|
|
+ private void linkBetween(WireNode prev, WireNode wire, WireNode next) {
|
|
+ prev.next = wire;
|
|
+ wire.prev = prev;
|
|
+
|
|
+ wire.next = next;
|
|
+ next.prev = wire;
|
|
+ }
|
|
+
|
|
+ private WireNode findPrev(WireNode wire) {
|
|
+ WireNode prev = null;
|
|
+
|
|
+ for (int i = wire.power + OFFSET; i < tails.length; i++) {
|
|
+ prev = tails[i];
|
|
+
|
|
+ if (prev != null) {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return prev;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/alternate/current/wire/WireConnection.java b/src/main/java/alternate/current/wire/WireConnection.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..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..1dae7c0b896300ac9f0dd360dca23ca1de24dc66
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/WireConnectionManager.java
|
|
@@ -0,0 +1,139 @@
|
|
+package alternate.current.wire;
|
|
+
|
|
+import java.util.Arrays;
|
|
+import java.util.function.Consumer;
|
|
+
|
|
+import alternate.current.wire.WireHandler.Directions;
|
|
+import alternate.current.wire.WireHandler.NodeProvider;
|
|
+
|
|
+public class WireConnectionManager {
|
|
+
|
|
+ /** The owner of these connections. */
|
|
+ final WireNode owner;
|
|
+
|
|
+ /** The first connection for each cardinal direction. */
|
|
+ private final WireConnection[] heads;
|
|
+
|
|
+ private WireConnection head;
|
|
+ private WireConnection tail;
|
|
+
|
|
+ /** The total number of connections. */
|
|
+ int total;
|
|
+
|
|
+ /**
|
|
+ * A 4 bit number that encodes in which direction(s) the owner has connections
|
|
+ * to other wires.
|
|
+ */
|
|
+ private int flowTotal;
|
|
+ /** The direction of flow based connections to other wires. */
|
|
+ int iFlowDir;
|
|
+
|
|
+ WireConnectionManager(WireNode owner) {
|
|
+ this.owner = owner;
|
|
+
|
|
+ this.heads = new WireConnection[Directions.HORIZONTAL.length];
|
|
+
|
|
+ this.total = 0;
|
|
+
|
|
+ this.flowTotal = 0;
|
|
+ this.iFlowDir = -1;
|
|
+ }
|
|
+
|
|
+ void set(NodeProvider nodes) {
|
|
+ if (total > 0) {
|
|
+ clear();
|
|
+ }
|
|
+
|
|
+ boolean belowIsConductor = nodes.getNeighbor(owner, Directions.DOWN).isConductor();
|
|
+ boolean aboveIsConductor = nodes.getNeighbor(owner, Directions.UP).isConductor();
|
|
+
|
|
+ for (int iDir = 0; iDir < Directions.HORIZONTAL.length; iDir++) {
|
|
+ Node neighbor = nodes.getNeighbor(owner, iDir);
|
|
+
|
|
+ if (neighbor.isWire()) {
|
|
+ add(neighbor.asWire(), iDir, true, true);
|
|
+
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ boolean sideIsConductor = neighbor.isConductor();
|
|
+
|
|
+ if (!sideIsConductor) {
|
|
+ Node node = nodes.getNeighbor(neighbor, Directions.DOWN);
|
|
+
|
|
+ if (node.isWire()) {
|
|
+ add(node.asWire(), iDir, belowIsConductor, true);
|
|
+ }
|
|
+ }
|
|
+ if (!aboveIsConductor) {
|
|
+ Node node = nodes.getNeighbor(neighbor, Directions.UP);
|
|
+
|
|
+ if (node.isWire()) {
|
|
+ add(node.asWire(), iDir, true, sideIsConductor);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (total > 0) {
|
|
+ iFlowDir = WireHandler.FLOW_IN_TO_FLOW_OUT[flowTotal];
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void clear() {
|
|
+ Arrays.fill(heads, null);
|
|
+
|
|
+ head = null;
|
|
+ tail = null;
|
|
+
|
|
+ total = 0;
|
|
+
|
|
+ flowTotal = 0;
|
|
+ iFlowDir = -1;
|
|
+ }
|
|
+
|
|
+ private void add(WireNode wire, int iDir, boolean offer, boolean accept) {
|
|
+ add(new WireConnection(wire, iDir, offer, accept));
|
|
+ }
|
|
+
|
|
+ private void add(WireConnection connection) {
|
|
+ if (head == null) {
|
|
+ head = connection;
|
|
+ tail = connection;
|
|
+ } else {
|
|
+ tail.next = connection;
|
|
+ tail = connection;
|
|
+ }
|
|
+
|
|
+ if (heads[connection.iDir] == null) {
|
|
+ heads[connection.iDir] = connection;
|
|
+
|
|
+ flowTotal |= (1 << connection.iDir);
|
|
+ }
|
|
+
|
|
+ total++;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Iterate over all connections, without any specific
|
|
+ * update order in mind. Use this method if the iteration
|
|
+ * order is not important.
|
|
+ */
|
|
+ void forEach(Consumer<WireConnection> consumer) {
|
|
+ for (WireConnection c = head; c != null; c = c.next) {
|
|
+ consumer.accept(c);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Iterate over all connections in an order determined by
|
|
+ * the given flow direction. Use this method if the iteration
|
|
+ * order is important.
|
|
+ */
|
|
+ void forEach(Consumer<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..ef84b462aa4a0cedbf7c3cfb54ba43f31b8f4e2f
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/WireHandler.java
|
|
@@ -0,0 +1,1106 @@
|
|
+package alternate.current.wire;
|
|
+
|
|
+import java.util.ArrayList;
|
|
+import java.util.Iterator;
|
|
+import java.util.List;
|
|
+import java.util.Queue;
|
|
+
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
+
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.core.Direction;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.world.level.block.Block;
|
|
+import net.minecraft.world.level.block.Blocks;
|
|
+import net.minecraft.world.level.block.state.BlockState;
|
|
+import net.minecraft.world.level.redstone.Redstone;
|
|
+
|
|
+/**
|
|
+ * This class handles power changes for redstone wire. The algorithm was
|
|
+ * designed with the following goals in mind:
|
|
+ * <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
|
|
+ };
|
|
+
|
|
+ // 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;
|
|
+ /** All the power changes that need to happen. */
|
|
+ private final Queue<WireNode> powerChanges;
|
|
+
|
|
+ private int rootCount;
|
|
+ // Rather than creating new nodes every time a network is updated we keep
|
|
+ // a cache of nodes that can be re-used.
|
|
+ private Node[] nodeCache;
|
|
+ private int nodeCount;
|
|
+
|
|
+ private boolean updatingPower;
|
|
+
|
|
+ public WireHandler(ServerLevel level) {
|
|
+ this.level = level;
|
|
+
|
|
+ this.network = new ArrayList<>();
|
|
+ this.nodes = new Long2ObjectOpenHashMap<>();
|
|
+ this.powerChanges = new PowerQueue();
|
|
+
|
|
+ this.nodeCache = new Node[16];
|
|
+ this.fillNodeCache(0, 16);
|
|
+ }
|
|
+
|
|
+ private Node getOrAddNode(BlockPos pos) {
|
|
+ return nodes.compute(pos.asLong(), (key, node) -> {
|
|
+ if (node == null) {
|
|
+ // If there is not yet a node at this position, retrieve and
|
|
+ // update one from the cache.
|
|
+ return getNextNode(pos);
|
|
+ }
|
|
+ if (node.invalid) {
|
|
+ return revalidateNode(node);
|
|
+ }
|
|
+
|
|
+ return node;
|
|
+ });
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Retrieve the neighbor of a node in the given direction and create a link
|
|
+ * between the two nodes.
|
|
+ */
|
|
+ private Node getNeighbor(Node node, int iDir) {
|
|
+ Node neighbor = node.neighbors[iDir];
|
|
+
|
|
+ if (neighbor == null || neighbor.invalid) {
|
|
+ Direction dir = Directions.ALL[iDir];
|
|
+ BlockPos pos = node.pos.relative(dir);
|
|
+
|
|
+ Node oldNeighbor = neighbor;
|
|
+ neighbor = getOrAddNode(pos);
|
|
+
|
|
+ if (neighbor != oldNeighbor) {
|
|
+ int iOpp = Directions.iOpposite(iDir);
|
|
+
|
|
+ node.neighbors[iDir] = neighbor;
|
|
+ neighbor.neighbors[iOpp] = node;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return neighbor;
|
|
+ }
|
|
+
|
|
+ private Node removeNode(BlockPos pos) {
|
|
+ return nodes.remove(pos.asLong());
|
|
+ }
|
|
+
|
|
+ private Node revalidateNode(Node node) {
|
|
+ node.invalid = false;
|
|
+
|
|
+ if (node.isWire()) {
|
|
+ WireNode wire = node.asWire();
|
|
+
|
|
+ wire.prepared = false;
|
|
+ wire.inNetwork = false;
|
|
+ } else {
|
|
+ BlockPos pos = node.pos;
|
|
+ BlockState state = level.getBlockState(pos);
|
|
+
|
|
+ node.update(pos, state, false);
|
|
+ }
|
|
+
|
|
+ return node;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Check the BlockState that occupies the given position. If it is a wire, then
|
|
+ * create a new WireNode. Otherwise, grab the next Node from the cache and
|
|
+ * update it.
|
|
+ */
|
|
+ private Node getNextNode(BlockPos pos) {
|
|
+ BlockState state = level.getBlockState(pos);
|
|
+
|
|
+ if (state.is(Blocks.REDSTONE_WIRE)) {
|
|
+ return new WireNode(level, pos, state);
|
|
+ }
|
|
+
|
|
+ return getNextNode().update(pos, state, true);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Grab the first unused Node from the cache. If all of the cache is already in
|
|
+ * use, increase it in size first.
|
|
+ */
|
|
+ private Node getNextNode() {
|
|
+ if (nodeCount == nodeCache.length) {
|
|
+ increaseNodeCache();
|
|
+ }
|
|
+
|
|
+ return nodeCache[nodeCount++];
|
|
+ }
|
|
+
|
|
+ private void increaseNodeCache() {
|
|
+ Node[] oldCache = nodeCache;
|
|
+ nodeCache = new Node[oldCache.length << 1];
|
|
+
|
|
+ for (int index = 0; index < oldCache.length; index++) {
|
|
+ nodeCache[index] = oldCache[index];
|
|
+ }
|
|
+
|
|
+ fillNodeCache(oldCache.length, nodeCache.length);
|
|
+ }
|
|
+
|
|
+ private void fillNodeCache(int start, int end) {
|
|
+ for (int index = start; index < end; index++) {
|
|
+ nodeCache[index] = new Node(level);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * This method should be called whenever a wire receives a block update.
|
|
+ */
|
|
+ public void onWireUpdated(BlockPos pos) {
|
|
+ invalidateNodes();
|
|
+ findRoots(pos, true);
|
|
+ tryUpdatePower();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * This method should be called whenever a wire is placed.
|
|
+ */
|
|
+ public void onWireAdded(BlockPos pos) {
|
|
+ Node node = getOrAddNode(pos);
|
|
+
|
|
+ if (!node.isWire()) {
|
|
+ return; // we should never get here
|
|
+ }
|
|
+
|
|
+ WireNode wire = node.asWire();
|
|
+ wire.added = true;
|
|
+
|
|
+ invalidateNodes();
|
|
+ findRoots(pos, false);
|
|
+ tryUpdatePower();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * This method should be called whenever a wire is removed.
|
|
+ */
|
|
+ public void onWireRemoved(BlockPos pos, BlockState state) {
|
|
+ Node node = removeNode(pos);
|
|
+ WireNode wire;
|
|
+
|
|
+ if (node == null || !node.isWire()) {
|
|
+ wire = new WireNode(level, pos, state);
|
|
+ } else {
|
|
+ wire = node.asWire();
|
|
+ }
|
|
+
|
|
+ wire.invalid = true;
|
|
+ wire.removed = true;
|
|
+
|
|
+ // If these fields are set to 'true', the removal of this wire was part of
|
|
+ // already ongoing power changes, so we can exit early here.
|
|
+ if (updatingPower && wire.shouldBreak) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ invalidateNodes();
|
|
+ tryAddRoot(wire);
|
|
+ tryUpdatePower();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * The nodes map is a snapshot of the state of the world. It becomes invalid
|
|
+ * when power changes are carried out, since the block and shape updates can
|
|
+ * lead to block changes. If these block changes cause the network to be updated
|
|
+ * again every node must be invalidated, and revalidated before it is used
|
|
+ * again. This ensures the power calculations of the network are accurate.
|
|
+ */
|
|
+ private void invalidateNodes() {
|
|
+ if (updatingPower && !nodes.isEmpty()) {
|
|
+ Iterator<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, boolean checkNeighbors) {
|
|
+ Node node = getOrAddNode(pos);
|
|
+
|
|
+ if (!node.isWire()) {
|
|
+ return; // we should never get here
|
|
+ }
|
|
+
|
|
+ WireNode wire = node.asWire();
|
|
+ tryAddRoot(wire);
|
|
+
|
|
+ // If the wire at the given position is not in an invalid state or is not
|
|
+ // part of a larger network, we can exit early.
|
|
+ if (!checkNeighbors || !wire.inNetwork || wire.connections.total == 0) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (int iDir : DEFAULT_FULL_UPDATE_ORDER) {
|
|
+ Node neighbor = getNeighbor(wire, iDir);
|
|
+
|
|
+ if (neighbor.isConductor()) {
|
|
+ // Redstone components can power multiple wires through solid
|
|
+ // blocks.
|
|
+ findSignalSourcesAround(neighbor, Directions.iOpposite(iDir));
|
|
+ } else if (neighbor.state.isSignalSourceTo(level, neighbor.pos, Directions.ALL[iDir])) {
|
|
+ // Redstone components can also power multiple wires directly.
|
|
+ findRootsAroundSignalSource(neighbor, Directions.iOpposite(iDir));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Find signal sources around the given node that can provide direct signals
|
|
+ * to that node, and then search for wires that require power changes around
|
|
+ * those signal sources.
|
|
+ */
|
|
+ private void findSignalSourcesAround(Node node, int except) {
|
|
+ for (int iDir : Directions.I_EXCEPT[except]) {
|
|
+ Node neighbor = getNeighbor(node, iDir);
|
|
+
|
|
+ if (neighbor.state.isDirectSignalSourceTo(level, neighbor.pos, Directions.ALL[iDir])) {
|
|
+ findRootsAroundSignalSource(neighbor, iDir);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Find wires around the given signal source that require power changes.
|
|
+ */
|
|
+ private void findRootsAroundSignalSource(Node node, int except) {
|
|
+ for (int iDir : Directions.I_EXCEPT[except]) {
|
|
+ // Directions are backwards for redstone related methods, so we must
|
|
+ // check for power emitted in the opposite direction that we are
|
|
+ // interested in.
|
|
+ int iOpp = Directions.iOpposite(iDir);
|
|
+ Direction opp = Directions.ALL[iOpp];
|
|
+
|
|
+ boolean signal = node.state.isSignalSourceTo(level, node.pos, opp);
|
|
+ boolean directSignal = node.state.isDirectSignalSourceTo(level, node.pos, opp);
|
|
+
|
|
+ // If the signal source does not emit any power in this direction,
|
|
+ // move on to the next direction.
|
|
+ if (!signal && !directSignal) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ Node neighbor = getNeighbor(node, iDir);
|
|
+
|
|
+ if (signal && neighbor.isWire()) {
|
|
+ tryAddRoot(neighbor.asWire());
|
|
+ } else if (directSignal && neighbor.isConductor()) {
|
|
+ findRootsAround(neighbor, iOpp);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Look for wires around the given node that require power changes.
|
|
+ */
|
|
+ private void findRootsAround(Node node, int except) {
|
|
+ for (int iDir : Directions.I_EXCEPT[except]) {
|
|
+ Node neighbor = getNeighbor(node, iDir);
|
|
+
|
|
+ if (neighbor.isWire()) {
|
|
+ tryAddRoot(neighbor.asWire());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Check if the given wire is in an illegal state and needs power changes.
|
|
+ */
|
|
+ private void tryAddRoot(WireNode wire) {
|
|
+ // Each potential root needs to be checked only once.
|
|
+ if (wire.prepared) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ prepare(wire);
|
|
+ findPower(wire, false);
|
|
+
|
|
+ if (needsPowerChange(wire)) {
|
|
+ addRoot(wire);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Add the given wire to the network as a root.
|
|
+ */
|
|
+ private void addRoot(WireNode wire) {
|
|
+ network.add(wire);
|
|
+ rootCount++;
|
|
+
|
|
+ wire.inNetwork = true;
|
|
+
|
|
+ if (wire.connections.iFlowDir >= 0) {
|
|
+ wire.iFlowDir = wire.connections.iFlowDir;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Before a wire can be added to the network, it must be properly prepared.
|
|
+ * This method
|
|
+ * <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) ? Redstone.SIGNAL_MIN : getExternalPower(wire);
|
|
+ }
|
|
+
|
|
+ private int getExternalPower(WireNode wire) {
|
|
+ int power = Redstone.SIGNAL_MIN;
|
|
+
|
|
+ for (int iDir = 0; iDir < Directions.ALL.length; iDir++) {
|
|
+ Node neighbor = getNeighbor(wire, iDir);
|
|
+
|
|
+ // Power from wires is handled separately.
|
|
+ if (neighbor.isWire()) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // Since 1.16 there is a block that is both a conductor and a signal
|
|
+ // source: the target block!
|
|
+ if (neighbor.isConductor()) {
|
|
+ power = Math.max(power, getDirectSignalTo(wire, neighbor, Directions.iOpposite(iDir)));
|
|
+ }
|
|
+ if (neighbor.isSignalSource()) {
|
|
+ power = Math.max(power, neighbor.state.getSignal(level, neighbor.pos, Directions.ALL[iDir]));
|
|
+ }
|
|
+
|
|
+ if (power >= Redstone.SIGNAL_MAX) {
|
|
+ return Redstone.SIGNAL_MAX;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return power;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Determine the direct signal the given wire receives from neighboring blocks
|
|
+ * through the given conductor node.
|
|
+ */
|
|
+ private int getDirectSignalTo(WireNode wire, Node node, int except) {
|
|
+ int power = Redstone.SIGNAL_MIN;
|
|
+
|
|
+ for (int iDir : Directions.I_EXCEPT[except]) {
|
|
+ Node neighbor = getNeighbor(node, iDir);
|
|
+
|
|
+ if (neighbor.isSignalSource()) {
|
|
+ power = Math.max(power, neighbor.state.getDirectSignal(level, neighbor.pos, Directions.ALL[iDir]));
|
|
+
|
|
+ if (power >= Redstone.SIGNAL_MAX) {
|
|
+ return Redstone.SIGNAL_MAX;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return power;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Determine the power level the given wire receives from the blocks around it.
|
|
+ * Power from non-wire components has already been determined, so only power
|
|
+ * received from other wires needs to be checked. There are a few exceptions:
|
|
+ * <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 maxPower - 1},
|
|
+ * so if the external power is already larger than or equal to that, there is no
|
|
+ * need to check for power from neighboring wires.
|
|
+ */
|
|
+ private void findPower(WireNode wire, boolean ignoreNetwork) {
|
|
+ if (wire.removed || wire.shouldBreak || wire.externalPower >= (Redstone.SIGNAL_MAX - 1)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // The virtual power is reset to the external power, so the flow information
|
|
+ // must be reset as well.
|
|
+ wire.virtualPower = wire.externalPower;
|
|
+ wire.flowIn = 0;
|
|
+
|
|
+ findWirePower(wire, ignoreNetwork);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Determine the power level the given wire receives from connected wires.
|
|
+ */
|
|
+ private void findWirePower(WireNode wire, boolean ignoreNetwork) {
|
|
+ wire.connections.forEach(connection -> {
|
|
+ if (!connection.accept) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ WireNode neighbor = connection.wire;
|
|
+
|
|
+ if (!ignoreNetwork || !neighbor.inNetwork) {
|
|
+ int power = Math.max(Redstone.SIGNAL_MIN, neighbor.virtualPower - 1);
|
|
+ int iOpp = Directions.iOpposite(connection.iDir);
|
|
+
|
|
+ wire.offerPower(power, iOpp);
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private boolean needsPowerChange(WireNode wire) {
|
|
+ return wire.removed || wire.shouldBreak || wire.virtualPower != wire.currentPower;
|
|
+ }
|
|
+
|
|
+ private void tryUpdatePower() {
|
|
+ if (rootCount > 0) {
|
|
+ updatePower();
|
|
+ }
|
|
+ if (!updatingPower) {
|
|
+ nodes.clear();
|
|
+ nodeCount = 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Propagate power changes through the network and notify neighboring blocks of
|
|
+ * these changes.
|
|
+ *
|
|
+ * <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.
|
|
+ updatingPower = false;
|
|
+
|
|
+ throw t;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Build up a network of wires that need power changes. This includes the roots
|
|
+ * that were already added and any wires powered by those roots that will need
|
|
+ * power changes as a result of power changes to the roots.
|
|
+ */
|
|
+ private void buildNetwork() {
|
|
+ for (int index = 0; index < network.size(); index++) {
|
|
+ WireNode wire = network.get(index);
|
|
+
|
|
+ // The order in which wires are added to the network can influence the
|
|
+ // order in which they update their power levels.
|
|
+ wire.connections.forEach(connection -> {
|
|
+ if (!connection.offer) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ WireNode neighbor = connection.wire;
|
|
+
|
|
+ if (neighbor.inNetwork) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ prepare(neighbor);
|
|
+ findPower(neighbor, false);
|
|
+
|
|
+ if (needsPowerChange(neighbor)) {
|
|
+ addToNetwork(neighbor, connection.iDir);
|
|
+ }
|
|
+ }, wire.iFlowDir);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Add the given wire to the network and set its outgoing flow to some backup
|
|
+ * value. This avoids directionality in redstone grids.
|
|
+ */
|
|
+ private void addToNetwork(WireNode wire, int iBackupFlowDir) {
|
|
+ network.add(wire);
|
|
+
|
|
+ wire.inNetwork = true;
|
|
+ // Normally the flow is not set until the power level is updated. However,
|
|
+ // in networks with multiple power sources the update order between them
|
|
+ // depends on which was discovered first. To make this less prone to
|
|
+ // directionality, each wire node is given a 'backup' flow. For roots, this
|
|
+ // is the determined flow of their connections. For non-roots this is the
|
|
+ // direction from which they were discovered.
|
|
+ wire.iFlowDir = iBackupFlowDir;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Find those wires in the network that receive power from outside it, either
|
|
+ * from non-wire components or from wires that are not in the network, and queue
|
|
+ * the power changes for those wires.
|
|
+ */
|
|
+ private void findPoweredWires() {
|
|
+ for (int index = 0; index < network.size(); index++) {
|
|
+ WireNode wire = network.get(index);
|
|
+ findPower(wire, true);
|
|
+
|
|
+ if (index < rootCount || wire.removed || wire.shouldBreak || wire.virtualPower > Redstone.SIGNAL_MIN) {
|
|
+ queuePowerChange(wire);
|
|
+ } else {
|
|
+ // Wires that do not receive any power do not queue power changes
|
|
+ // until they are offered power from a neighboring wire. To ensure
|
|
+ // that they accept any power from neighboring wires and thus queue
|
|
+ // their power changes, their virtual power is set to below the
|
|
+ // minimum.
|
|
+ wire.virtualPower--;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Queue the power change for the given wire. If the wire does not need a power
|
|
+ * change (perhaps because its power has already changed), transmit power to
|
|
+ * neighboring wires.
|
|
+ */
|
|
+ private void queuePowerChange(WireNode wire) {
|
|
+ if (needsPowerChange(wire)) {
|
|
+ powerChanges.offer(wire);
|
|
+ } else {
|
|
+ findPowerFlow(wire);
|
|
+ transmitPower(wire);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Use the information of incoming power flow to determine the direction of
|
|
+ * power flow through this wire. If that flow is ambiguous, try to use a flow
|
|
+ * direction based on connections to neighboring wires. If that is also
|
|
+ * ambiguous, use the backup value that was set when the wire was first added
|
|
+ * to the network.
|
|
+ */
|
|
+ private void findPowerFlow(WireNode wire) {
|
|
+ int flow = FLOW_IN_TO_FLOW_OUT[wire.flowIn];
|
|
+
|
|
+ if (flow >= 0) {
|
|
+ wire.iFlowDir = flow;
|
|
+ } else if (wire.connections.iFlowDir >= 0) {
|
|
+ wire.iFlowDir = wire.connections.iFlowDir;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Transmit power from the given wire to neighboring wires.
|
|
+ */
|
|
+ private void transmitPower(WireNode wire) {
|
|
+ int nextPower = Math.max(Redstone.SIGNAL_MIN, wire.virtualPower - 1);
|
|
+
|
|
+ wire.connections.forEach(connection -> {
|
|
+ if (!connection.offer) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ WireNode neighbor = connection.wire;
|
|
+ int iDir = connection.iDir;
|
|
+
|
|
+ if (neighbor.offerPower(nextPower, iDir)) {
|
|
+ queuePowerChange(neighbor);
|
|
+ }
|
|
+ }, wire.iFlowDir);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Carry out power changes, setting the new power of each wire in the world,
|
|
+ * notifying neighbors of the power change, then queueing power changes of
|
|
+ * connected wires.
|
|
+ */
|
|
+ private void letPowerFlow() {
|
|
+ // If an instantaneous update chain causes updates to another network
|
|
+ // (or the same network in another place), new power changes will be
|
|
+ // integrated into the already ongoing power queue, so we can exit early
|
|
+ // here.
|
|
+ if (updatingPower) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ updatingPower = true;
|
|
+
|
|
+ while (!powerChanges.isEmpty()) {
|
|
+ WireNode wire = powerChanges.poll();
|
|
+
|
|
+ if (!needsPowerChange(wire)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ findPowerFlow(wire);
|
|
+
|
|
+ if (wire.setPower()) {
|
|
+ // If the wire was newly placed or removed, shape updates have
|
|
+ // already been emitted.
|
|
+ if (!wire.added && !wire.shouldBreak) {
|
|
+ updateNeighborShapes(wire);
|
|
+ }
|
|
+
|
|
+ updateNeighborBlocks(wire);
|
|
+ }
|
|
+
|
|
+ transmitPower(wire);
|
|
+ }
|
|
+
|
|
+ updatingPower = false;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Emit shape updates around the given wire.
|
|
+ */
|
|
+ private void updateNeighborShapes(WireNode wire) {
|
|
+ BlockPos wirePos = wire.pos;
|
|
+ BlockState wireState = wire.state;
|
|
+
|
|
+ for (Direction dir : Block.UPDATE_SHAPE_ORDER) {
|
|
+ updateNeighborShape(wirePos.relative(dir), dir.getOpposite(), wirePos, wireState);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void updateNeighborShape(BlockPos pos, Direction fromDir, BlockPos fromPos, BlockState fromState) {
|
|
+ BlockState state = level.getBlockState(pos);
|
|
+
|
|
+ // Shape updates to redstone wire are very expensive, and should never happen
|
|
+ // as a result of power changes anyway.
|
|
+ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) {
|
|
+ BlockState newState = state.updateShape(fromDir, fromState, level, pos, fromPos);
|
|
+ Block.updateOrDestroy(state, newState, level, pos, Block.UPDATE_CLIENTS);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Emit block updates around the given wire. The order in which neighbors are
|
|
+ * updated is determined as follows:
|
|
+ * <br>
|
|
+ * 1. The direction of power flow through the wire is to be considered 'forward'.
|
|
+ * The order in which neighbors are updated depends on their relative positions
|
|
+ * to the wire.
|
|
+ * <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 updated in pairs that lie on opposite sides of the wire.
|
|
+ * <br>
|
|
+ * 4. Neighbors are updated in order of their distance from the wire. This means
|
|
+ * they are updated in 3 groups: direct neighbors are updated first, then
|
|
+ * diagonal neighbors, and last are the far neighbors that are 2 blocks directly
|
|
+ * out.
|
|
+ * <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 updateNeighborBlocks(WireNode wire) {
|
|
+ int iDir = wire.iFlowDir;
|
|
+
|
|
+ Direction forward = Directions.HORIZONTAL[ iDir ];
|
|
+ Direction rightward = Directions.HORIZONTAL[(iDir + 1) & 0b11];
|
|
+ Direction backward = Directions.HORIZONTAL[(iDir + 2) & 0b11];
|
|
+ Direction leftward = Directions.HORIZONTAL[(iDir + 3) & 0b11];
|
|
+ Direction downward = Direction.DOWN;
|
|
+ Direction upward = Direction.UP;
|
|
+
|
|
+ BlockPos self = wire.pos;
|
|
+ BlockPos front = self.relative(forward);
|
|
+ BlockPos right = self.relative(rightward);
|
|
+ BlockPos back = self.relative(backward);
|
|
+ BlockPos left = self.relative(leftward);
|
|
+ BlockPos below = self.relative(downward);
|
|
+ BlockPos above = self.relative(upward);
|
|
+
|
|
+ // direct neighbors (6)
|
|
+ updateNeighbor(front, self);
|
|
+ updateNeighbor(back, self);
|
|
+ updateNeighbor(right, self);
|
|
+ updateNeighbor(left, self);
|
|
+ updateNeighbor(below, self);
|
|
+ updateNeighbor(above, self);
|
|
+
|
|
+ // diagonal neighbors (12)
|
|
+ updateNeighbor(front.relative(rightward), self);
|
|
+ updateNeighbor(back.relative(leftward), self);
|
|
+ updateNeighbor(front.relative(leftward), self);
|
|
+ updateNeighbor(back.relative(rightward), self);
|
|
+ updateNeighbor(front.relative(downward), self);
|
|
+ updateNeighbor(back.relative(upward), self);
|
|
+ updateNeighbor(front.relative(upward), self);
|
|
+ updateNeighbor(back.relative(downward), self);
|
|
+ updateNeighbor(right.relative(downward), self);
|
|
+ updateNeighbor(left.relative(upward), self);
|
|
+ updateNeighbor(right.relative(upward), self);
|
|
+ updateNeighbor(left.relative(downward), self);
|
|
+
|
|
+ // far neighbors (6)
|
|
+ updateNeighbor(front.relative(forward), self);
|
|
+ updateNeighbor(back.relative(backward), self);
|
|
+ updateNeighbor(right.relative(rightward), self);
|
|
+ updateNeighbor(left.relative(leftward), self);
|
|
+ updateNeighbor(below.relative(downward), self);
|
|
+ updateNeighbor(above.relative(upward), self);
|
|
+ }
|
|
+
|
|
+ private void updateNeighbor(BlockPos pos, BlockPos fromPos) {
|
|
+ BlockState state = level.getBlockState(pos);
|
|
+
|
|
+ // While this check makes sure wires in the network are not given block
|
|
+ // updates, it also prevents block updates to wires in neighboring networks.
|
|
+ // While this should not make a difference in theory, in practice, it is
|
|
+ // possible to force a network into an invalid state without updating it, even
|
|
+ // if it is relatively obscure.
|
|
+ // While I was willing to make this compromise in return for some significant
|
|
+ // performance gains in certain setups, if you are not, you can add all the
|
|
+ // positions of the network to a set and filter out block updates to wires in
|
|
+ // the network that way.
|
|
+ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) {
|
|
+ state.neighborChanged(level, pos, Blocks.REDSTONE_WIRE, fromPos, false);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @FunctionalInterface
|
|
+ public interface NodeProvider {
|
|
+
|
|
+ public Node getNeighbor(Node node, int iDir);
|
|
+
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/alternate/current/wire/WireNode.java b/src/main/java/alternate/current/wire/WireNode.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..6b5bffd288e2f815d8c3788e73530e69d549e25e
|
|
--- /dev/null
|
|
+++ b/src/main/java/alternate/current/wire/WireNode.java
|
|
@@ -0,0 +1,117 @@
|
|
+package alternate.current.wire;
|
|
+
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.world.level.block.Block;
|
|
+import net.minecraft.world.level.block.Blocks;
|
|
+import net.minecraft.world.level.block.RedStoneWireBlock;
|
|
+import net.minecraft.world.level.block.state.BlockState;
|
|
+import net.minecraft.world.level.redstone.Redstone;
|
|
+
|
|
+/**
|
|
+ * A WireNode is a Node that represents a wire in the world. It stores
|
|
+ * all the information about the wire that the WireHandler needs to
|
|
+ * calculate power changes.
|
|
+ *
|
|
+ * @author Space Walker
|
|
+ */
|
|
+public class WireNode extends Node {
|
|
+
|
|
+ final WireConnectionManager connections;
|
|
+
|
|
+ /** The power level this wire currently holds in the world. */
|
|
+ int currentPower;
|
|
+ /**
|
|
+ * While calculating power changes for a network, this field is used to keep
|
|
+ * track of the power level this wire should have.
|
|
+ */
|
|
+ int virtualPower;
|
|
+ /** The power level received from non-wire components. */
|
|
+ int externalPower;
|
|
+ /**
|
|
+ * A 4-bit number that keeps track of the power flow of the wires that give this
|
|
+ * wire its power level.
|
|
+ */
|
|
+ int flowIn;
|
|
+ /** The direction of power flow, based on the incoming flow. */
|
|
+ int iFlowDir;
|
|
+ boolean added;
|
|
+ boolean removed;
|
|
+ boolean shouldBreak;
|
|
+ boolean prepared;
|
|
+ boolean inNetwork;
|
|
+
|
|
+ /** The power for which this wire was queued. */
|
|
+ int power;
|
|
+ /** The previous wire in the power queue. */
|
|
+ WireNode prev;
|
|
+ /** The next wire in the power queue. */
|
|
+ WireNode next;
|
|
+
|
|
+ WireNode(ServerLevel level, BlockPos pos, BlockState state) {
|
|
+ super(level);
|
|
+
|
|
+ this.pos = pos.immutable();
|
|
+ this.state = state;
|
|
+
|
|
+ this.connections = new WireConnectionManager(this);
|
|
+
|
|
+ this.virtualPower = this.currentPower = this.state.getValue(RedStoneWireBlock.POWER);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Node update(BlockPos pos, BlockState state, boolean clearNeighbors) {
|
|
+ throw new UnsupportedOperationException("Cannot update a WireNode!");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isWire() {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public WireNode asWire() {
|
|
+ return this;
|
|
+ }
|
|
+
|
|
+ int nextPower() {
|
|
+ return Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX);
|
|
+ }
|
|
+
|
|
+ boolean offerPower(int power, int iDir) {
|
|
+ if (removed || shouldBreak) {
|
|
+ return false;
|
|
+ }
|
|
+ if (power == virtualPower) {
|
|
+ flowIn |= (1 << iDir);
|
|
+ return false;
|
|
+ }
|
|
+ if (power > virtualPower) {
|
|
+ virtualPower = power;
|
|
+ flowIn = (1 << iDir);
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ boolean setPower() {
|
|
+ if (removed) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ state = level.getBlockState(pos);
|
|
+
|
|
+ if (shouldBreak) {
|
|
+ Block.dropResources(state, level, pos);
|
|
+ return level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS);
|
|
+ }
|
|
+
|
|
+ currentPower = LevelHelper.doRedstoneEvent(level, pos, currentPower, power);
|
|
+ state = state.setValue(RedStoneWireBlock.POWER, currentPower);
|
|
+
|
|
+ return LevelHelper.setWireState(level, pos, state, added);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
index 20586d7b336d87ae3888b90da3ca39a4df08ed8f..246cfce07c74f42f8c1efb2b4e2b8ec086d5e58f 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
@@ -83,7 +83,7 @@ public class PaperWorldConfig {
|
|
}
|
|
|
|
public enum RedstoneImplementation {
|
|
- VANILLA, EIGENCRAFT
|
|
+ VANILLA, EIGENCRAFT, ALTERNATE_CURRENT
|
|
}
|
|
public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA;
|
|
private void redstoneImplementation() {
|
|
@@ -104,7 +104,7 @@ public class PaperWorldConfig {
|
|
}
|
|
switch (implementation) {
|
|
default:
|
|
- logError("Invalid redstone-implementation config " + implementation + " - must be one of: vanilla, eigencraft");
|
|
+ logError("Invalid redstone-implementation config " + implementation + " - must be one of: vanilla, eigencraft, alternate-current");
|
|
case "vanilla":
|
|
redstoneImplementation = RedstoneImplementation.VANILLA;
|
|
log("Using the Vanilla redstone implementation.");
|
|
@@ -113,6 +113,10 @@ public class PaperWorldConfig {
|
|
redstoneImplementation = RedstoneImplementation.EIGENCRAFT;
|
|
log("Using Eigencraft's redstone implementation by theosib.");
|
|
break;
|
|
+ case "alternate-current":
|
|
+ redstoneImplementation = RedstoneImplementation.ALTERNATE_CURRENT;
|
|
+ log("Using Alternate Current's redstone implementation by Space Walker.");
|
|
+ break;
|
|
}
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index 62ec40de8ed8acb293ef21c8d2c624060d51cfe8..98209532ad3e692d7e459640123f78bbd9a65889 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -212,6 +212,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());
|
|
}
|
|
@@ -2407,6 +2408,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 160c0f37aa3aaf7598f852acf9bd444f79444c97..aece6a47d978e9d3579334bbedadfeb9f4770894 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -1499,4 +1499,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 2036006b934ba1f27da606320b4c456af019a361..9d1d59fe26eff0640906037aba93e73dda433d0d 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 b7f37475192bf79252482314080c9ba08e9aefdb..8b6eeb1ccad3a2e3858b70e59819c79485a2e538 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, Random random) {
|
|
if ((Boolean) state.getValue(ButtonBlock.POWERED)) {
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java b/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java
|
|
index 40b0380aa6fd052bf6376a15939c08e603f2f60c..e57c5242866165e589277bd0184098c7806538ba 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/DaylightDetectorBlock.java
|
|
@@ -99,6 +99,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 9c764d2273d70b8dffcaa7f324544cb48f12acc3..7f0aa1e8b53a749459fb8cb99c73c6c2c60ea4e9 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 25ed6dfef2887612a02fcf8884dc8dac4fbd64ff..7f6f14e7858c2577e44ace8370621114dce29ea8 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/LecternBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/LecternBlock.java
|
|
@@ -220,6 +220,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 1093dc8595e42a90e74e19f74965f5be07a1d6cf..6e7320f5b8e2cf030b96f2ab80c8fb885b0fe1e3 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/LeverBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/LeverBlock.java
|
|
@@ -165,6 +165,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 b7605dda79c4d907f5822a0ded694b080e08dae5..ff24cbf03e937572a0c21fe298557d4eab8760b9 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 4a34a08a1d46e4d3020644a51d9e30a36a18791a..358a40eb9b4be4d2b5446270f90b51138a19b051 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 0acc3b550a5bb809fe99708368114eaccb227312..b2522849d34bbd530c7086b6fbb0582f2d38fef7 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
|
|
@@ -255,7 +255,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);
|
|
|
|
@@ -462,7 +462,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.redstoneImplementation == com.destroystokyo.paper.PaperWorldConfig.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()) {
|
|
@@ -489,7 +495,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.redstoneImplementation == com.destroystokyo.paper.PaperWorldConfig.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);
|
|
}
|
|
}
|
|
@@ -523,8 +535,14 @@ public class RedStoneWireBlock extends Block {
|
|
@Override
|
|
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean notify) {
|
|
if (!world.isClientSide) {
|
|
+ // Paper start - optimize redstone (Alternate Current)
|
|
+ // Alternate Current handles breaking of redstone wires in the WireHandler.
|
|
+ if (world.paperConfig.redstoneImplementation == com.destroystokyo.paper.PaperWorldConfig.RedstoneImplementation.ALTERNATE_CURRENT) {
|
|
+ world.getWireHandler().onWireUpdated(pos);
|
|
+ } else
|
|
+ // Paper end
|
|
if (state.canSurvive(world, pos)) {
|
|
- this.updateSurroundingRedstone(world, pos, state, fromPos); // Paper - Optimize redstone
|
|
+ this.updateSurroundingRedstone(world, pos, state, fromPos); // Paper - optimize redstone (Eigencraft)
|
|
} else {
|
|
dropResources(state, world, pos);
|
|
world.removeBlock(pos, false);
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java
|
|
index 954b86bea345a8e0e3a8dd425f356db6f5cd496f..3731749436c86210954e4213f893a51eea06230a 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, Random random) {
|
|
if ((Boolean) state.getValue(RedstoneTorchBlock.LIT)) {
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneWallTorchBlock.java
|
|
index 5cf0ae04059533385a19f7b07909a67b57350c09..ec5c201ebac0e1b44b90319884ce968a9212f3b2 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 33bca696c1ae0a63055eea5d2e05551458da50b4..4a0e4b9c49f04d63ca0b7d0a6ce71680f911ff29 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/SculkSensorBlock.java
|
|
@@ -206,6 +206,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 d609c60c1650a5b7f860154e0a4f4c6d84fa63fc..391d64b698871aa420f736a97b7b4b28c9d11da0 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/TargetBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/TargetBlock.java
|
|
@@ -113,6 +113,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 a4344bf2267112e3c1e31c07c9f6b8eae9666947..5e973eb53b240615e70b7d46ef4dc17b907ecaf9 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java
|
|
@@ -265,6 +265,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 61590f2f04a797235299f1bd6b78a08f5bfe4a33..7f83c9390823b42fc30d04e1d3222e2825eaad50 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
|
|
@@ -70,7 +70,7 @@ import net.minecraft.world.phys.shapes.VoxelShape;
|
|
|
|
public abstract class BlockBehaviour {
|
|
|
|
- protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP};
|
|
+ public static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP}; // Paper - public
|
|
protected final Material material;
|
|
public final boolean hasCollision;
|
|
protected final float explosionResistance;
|
|
@@ -187,6 +187,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) {
|
|
@@ -846,6 +856,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);
|
|
}
|