diff --git a/patches/server/0005-Paper-config-files.patch b/patches/server/0005-Paper-config-files.patch index 58b32ceedc..71cd9f2a39 100644 --- a/patches/server/0005-Paper-config-files.patch +++ b/patches/server/0005-Paper-config-files.patch @@ -1416,10 +1416,10 @@ index 0000000000000000000000000000000000000000..990d1bb46e0f9719f4e9af928d80ac6f +} diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java new file mode 100644 -index 0000000000000000000000000000000000000000..aa3624fb8aaaf2720aaef7800f537dd4b906797f +index 0000000000000000000000000000000000000000..f1b74f7b12fc7b35815886501937725b65f8a8e3 --- /dev/null +++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java -@@ -0,0 +1,573 @@ +@@ -0,0 +1,578 @@ +package io.papermc.paper.configuration; + +import com.google.common.collect.HashBasedTable; @@ -1982,6 +1982,7 @@ index 0000000000000000000000000000000000000000..aa3624fb8aaaf2720aaef7800f537dd4 + public boolean updatePathfindingOnBlockUpdate = true; + public boolean showSignClickCommandFailureMsgsToPlayer = false; + public RedstoneImplementation redstoneImplementation = RedstoneImplementation.VANILLA; ++ public AlternateCurrentUpdateOrder alternateCurrentUpdateOrder = AlternateCurrentUpdateOrder.HORIZONTAL_FIRST_OUTWARD; + public boolean disableEndCredits = false; + public DoubleOr.Default maxLeashDistance = DoubleOr.Default.USE_DEFAULT; + public boolean disableSprintInterruptionOnAttack = false; @@ -1991,6 +1992,10 @@ index 0000000000000000000000000000000000000000..aa3624fb8aaaf2720aaef7800f537dd4 + public enum RedstoneImplementation { + VANILLA, EIGENCRAFT, ALTERNATE_CURRENT + } ++ ++ public enum AlternateCurrentUpdateOrder { ++ HORIZONTAL_FIRST_OUTWARD, HORIZONTAL_FIRST_INWARD, VERTICAL_FIRST_OUTWARD, VERTICAL_FIRST_INWARD ++ } + } +} diff --git a/src/main/java/io/papermc/paper/configuration/constraint/Constraint.java b/src/main/java/io/papermc/paper/configuration/constraint/Constraint.java diff --git a/patches/server/0999-Add-Alternate-Current-redstone-implementation.patch b/patches/server/0999-Add-Alternate-Current-redstone-implementation.patch index f7fadd89b6..8e8dac3c75 100644 --- a/patches/server/0999-Add-Alternate-Current-redstone-implementation.patch +++ b/patches/server/0999-Add-Alternate-Current-redstone-implementation.patch @@ -22,7 +22,7 @@ Alternate Current's wire handler. diff --git a/src/main/java/alternate/current/wire/LevelHelper.java b/src/main/java/alternate/current/wire/LevelHelper.java new file mode 100644 -index 0000000000000000000000000000000000000000..8b4697421d57f81ff1794c6f845258e10df91622 +index 0000000000000000000000000000000000000000..8196460fe91bc4d1b03ca214d4323276d1d19464 --- /dev/null +++ b/src/main/java/alternate/current/wire/LevelHelper.java @@ -0,0 +1,66 @@ @@ -39,7 +39,7 @@ index 0000000000000000000000000000000000000000..8b4697421d57f81ff1794c6f845258e1 +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunkSection; + -+public class LevelHelper { ++class LevelHelper { + + static int doRedstoneEvent(ServerLevel level, BlockPos pos, int prevPower, int newPower) { + BlockRedstoneEvent event = new BlockRedstoneEvent(CraftBlock.at(level, pos), prevPower, newPower); @@ -546,6 +546,402 @@ index 0000000000000000000000000000000000000000..2b30074252551e1dc55d5be17d26fb4a + } + } +} +diff --git a/src/main/java/alternate/current/wire/UpdateOrder.java b/src/main/java/alternate/current/wire/UpdateOrder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..29338efd16cf62bb49e81cce09fbafd9b4319e7c +--- /dev/null ++++ b/src/main/java/alternate/current/wire/UpdateOrder.java +@@ -0,0 +1,390 @@ ++package alternate.current.wire; ++ ++import java.util.Locale; ++import java.util.function.Consumer; ++ ++import alternate.current.wire.WireHandler.Directions; ++import alternate.current.wire.WireHandler.NodeProvider; ++ ++public enum UpdateOrder { ++ ++ HORIZONTAL_FIRST_OUTWARD( ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP } ++ ++ }, ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ } ++ ) { ++ ++ @Override ++ public void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action) { ++ /* ++ * This iteration order is designed to be an extension of the Vanilla shape ++ * update order, and is determined as follows: ++ *
++ * 1. Each neighbor is identified by the step(s) you must take, starting at the ++ * source, 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. ++ *
++ * 2. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * source. ++ *
++ * 3. Neighbors are iterated over in order of their distance from the source, ++ * moving outward. 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. ++ *
++ * 4. 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. ++ */ ++ ++ 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 = nodes.getNeighbor(source, forward); ++ Node right = nodes.getNeighbor(source, rightward); ++ Node back = nodes.getNeighbor(source, backward); ++ Node left = nodes.getNeighbor(source, leftward); ++ Node below = nodes.getNeighbor(source, downward); ++ Node above = nodes.getNeighbor(source, upward); ++ ++ // direct neighbors (6) ++ action.accept(front); ++ action.accept(back); ++ action.accept(right); ++ action.accept(left); ++ action.accept(below); ++ action.accept(above); ++ ++ // diagonal neighbors (12) ++ action.accept(nodes.getNeighbor(front, rightward)); ++ action.accept(nodes.getNeighbor(back, leftward)); ++ action.accept(nodes.getNeighbor(front, leftward)); ++ action.accept(nodes.getNeighbor(back, rightward)); ++ action.accept(nodes.getNeighbor(front, downward)); ++ action.accept(nodes.getNeighbor(back, upward)); ++ action.accept(nodes.getNeighbor(front, upward)); ++ action.accept(nodes.getNeighbor(back, downward)); ++ action.accept(nodes.getNeighbor(right, downward)); ++ action.accept(nodes.getNeighbor(left, upward)); ++ action.accept(nodes.getNeighbor(right, upward)); ++ action.accept(nodes.getNeighbor(left, downward)); ++ ++ // far neighbors (6) ++ action.accept(nodes.getNeighbor(front, forward)); ++ action.accept(nodes.getNeighbor(back, backward)); ++ action.accept(nodes.getNeighbor(right, rightward)); ++ action.accept(nodes.getNeighbor(left, leftward)); ++ action.accept(nodes.getNeighbor(below, downward)); ++ action.accept(nodes.getNeighbor(above, upward)); ++ } ++ }, ++ HORIZONTAL_FIRST_INWARD( ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP } ++ }, ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ } ++ ) { ++ ++ @Override ++ public void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action) { ++ /* ++ * This iteration order is designed to be an inversion of the above update ++ * order, and is determined as follows: ++ *
++ * 1. Each neighbor is identified by the step(s) you must take, starting at the ++ * source, 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. ++ *
++ * 2. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * source. ++ *
++ * 3. Neighbors are iterated over in order of their distance from the source, ++ * moving inward. This means they are iterated over in 3 groups: neighbors that ++ * are 2 blocks directly out first, then diagonal neighbors, and last are direct ++ * neighbors. ++ *
++ * 4. 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. ++ */ ++ ++ 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 = nodes.getNeighbor(source, forward); ++ Node right = nodes.getNeighbor(source, rightward); ++ Node back = nodes.getNeighbor(source, backward); ++ Node left = nodes.getNeighbor(source, leftward); ++ Node below = nodes.getNeighbor(source, downward); ++ Node above = nodes.getNeighbor(source, upward); ++ ++ // far neighbors (6) ++ action.accept(nodes.getNeighbor(front, forward)); ++ action.accept(nodes.getNeighbor(back, backward)); ++ action.accept(nodes.getNeighbor(right, rightward)); ++ action.accept(nodes.getNeighbor(left, leftward)); ++ action.accept(nodes.getNeighbor(below, downward)); ++ action.accept(nodes.getNeighbor(above, upward)); ++ ++ // diagonal neighbors (12) ++ action.accept(nodes.getNeighbor(front, rightward)); ++ action.accept(nodes.getNeighbor(back, leftward)); ++ action.accept(nodes.getNeighbor(front, leftward)); ++ action.accept(nodes.getNeighbor(back, rightward)); ++ action.accept(nodes.getNeighbor(front, downward)); ++ action.accept(nodes.getNeighbor(back, upward)); ++ action.accept(nodes.getNeighbor(front, upward)); ++ action.accept(nodes.getNeighbor(back, downward)); ++ action.accept(nodes.getNeighbor(right, downward)); ++ action.accept(nodes.getNeighbor(left, upward)); ++ action.accept(nodes.getNeighbor(right, upward)); ++ action.accept(nodes.getNeighbor(left, downward)); ++ ++ ++ // direct neighbors (6) ++ action.accept(front); ++ action.accept(back); ++ action.accept(right); ++ action.accept(left); ++ action.accept(below); ++ action.accept(above); ++ } ++ }, ++ VERTICAL_FIRST_OUTWARD( ++ new int[][] { ++ new int[] { Directions.DOWN, Directions.UP, Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ }, ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ } ++ ) { ++ ++ @Override ++ public void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action) { ++ /* ++ * This iteration order is designed to be the opposite of the Vanilla shape ++ * update order, and is determined as follows: ++ *
++ * 1. Each neighbor is identified by the step(s) you must take, starting at the ++ * source, 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. ++ *
++ * 2. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * source. ++ *
++ * 3. Neighbors are iterated over in order of their distance from the source, ++ * moving outward. 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. ++ *
++ * 4. The order within each group is determined using the following basic order: ++ * { down, up, front, back, right, left }. This order was chosen because it ++ * converts to the following order of absolute directions when west is said to ++ * be 'forward': { down, up west, east, north, south } - this is the order of ++ * shape updates, with the vertical directions moved to the front. ++ */ ++ ++ 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 = nodes.getNeighbor(source, forward); ++ Node right = nodes.getNeighbor(source, rightward); ++ Node back = nodes.getNeighbor(source, backward); ++ Node left = nodes.getNeighbor(source, leftward); ++ Node below = nodes.getNeighbor(source, downward); ++ Node above = nodes.getNeighbor(source, upward); ++ ++ // direct neighbors (6) ++ action.accept(below); ++ action.accept(above); ++ action.accept(front); ++ action.accept(back); ++ action.accept(right); ++ action.accept(left); ++ ++ // diagonal neighbors (12) ++ action.accept(nodes.getNeighbor(below, forward)); ++ action.accept(nodes.getNeighbor(above, backward)); ++ action.accept(nodes.getNeighbor(below, backward)); ++ action.accept(nodes.getNeighbor(above, forward)); ++ action.accept(nodes.getNeighbor(below, rightward)); ++ action.accept(nodes.getNeighbor(above, leftward)); ++ action.accept(nodes.getNeighbor(below, leftward)); ++ action.accept(nodes.getNeighbor(above, rightward)); ++ action.accept(nodes.getNeighbor(front, rightward)); ++ action.accept(nodes.getNeighbor(back, leftward)); ++ action.accept(nodes.getNeighbor(front, leftward)); ++ action.accept(nodes.getNeighbor(back, rightward)); ++ ++ // far neighbors (6) ++ action.accept(nodes.getNeighbor(below, downward)); ++ action.accept(nodes.getNeighbor(above, upward)); ++ action.accept(nodes.getNeighbor(front, forward)); ++ action.accept(nodes.getNeighbor(back, backward)); ++ action.accept(nodes.getNeighbor(right, rightward)); ++ action.accept(nodes.getNeighbor(left, leftward)); ++ } ++ }, ++ VERTICAL_FIRST_INWARD( ++ new int[][] { ++ new int[] { Directions.DOWN, Directions.UP, Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.DOWN, Directions.UP, Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ }, ++ new int[][] { ++ new int[] { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ new int[] { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ new int[] { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ new int[] { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ } ++ ) { ++ ++ @Override ++ public void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action) { ++ /* ++ * This iteration order is designed to be an inversion of the above update ++ * order, and is determined as follows: ++ *
++ * 1. Each neighbor is identified by the step(s) you must take, starting at the ++ * source, 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. ++ *
++ * 2. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * source. ++ *
++ * 3. Neighbors are iterated over in order of their distance from the source, ++ * moving inward. This means they are iterated over in 3 groups: neighbors that ++ * are 2 blocks directly out first, then diagonal neighbors, and last are direct ++ * neighbors. ++ *
++ * 4. The order within each group is determined using the following basic order: ++ * { down, up, front, back, right, left }. This order was chosen because it ++ * converts to the following order of absolute directions when west is said to ++ * be 'forward': { down, up west, east, north, south } - this is the order of ++ * shape updates, with the vertical directions moved to the front. ++ */ ++ ++ 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 = nodes.getNeighbor(source, forward); ++ Node right = nodes.getNeighbor(source, rightward); ++ Node back = nodes.getNeighbor(source, backward); ++ Node left = nodes.getNeighbor(source, leftward); ++ Node below = nodes.getNeighbor(source, downward); ++ Node above = nodes.getNeighbor(source, upward); ++ ++ // far neighbors (6) ++ action.accept(nodes.getNeighbor(below, downward)); ++ action.accept(nodes.getNeighbor(above, upward)); ++ action.accept(nodes.getNeighbor(front, forward)); ++ action.accept(nodes.getNeighbor(back, backward)); ++ action.accept(nodes.getNeighbor(right, rightward)); ++ action.accept(nodes.getNeighbor(left, leftward)); ++ ++ // diagonal neighbors (12) ++ action.accept(nodes.getNeighbor(below, forward)); ++ action.accept(nodes.getNeighbor(above, backward)); ++ action.accept(nodes.getNeighbor(below, backward)); ++ action.accept(nodes.getNeighbor(above, forward)); ++ action.accept(nodes.getNeighbor(below, rightward)); ++ action.accept(nodes.getNeighbor(above, leftward)); ++ action.accept(nodes.getNeighbor(below, leftward)); ++ action.accept(nodes.getNeighbor(above, rightward)); ++ action.accept(nodes.getNeighbor(front, rightward)); ++ action.accept(nodes.getNeighbor(back, leftward)); ++ action.accept(nodes.getNeighbor(front, leftward)); ++ action.accept(nodes.getNeighbor(back, rightward)); ++ ++ // direct neighbors (6) ++ action.accept(below); ++ action.accept(above); ++ action.accept(front); ++ action.accept(back); ++ action.accept(right); ++ action.accept(left); ++ } ++ }; ++ ++ private final int[][] directNeighbors; ++ private final int[][] cardinalNeighbors; ++ ++ private UpdateOrder(int[][] directNeighbors, int[][] cardinalNeighbors) { ++ this.directNeighbors = directNeighbors; ++ this.cardinalNeighbors = cardinalNeighbors; ++ } ++ ++ public String id() { ++ return name().toLowerCase(Locale.ENGLISH); ++ } ++ ++ public static UpdateOrder byId(String id) { ++ return valueOf(id.toUpperCase(Locale.ENGLISH)); ++ } ++ ++ public int[] directNeighbors(int forward) { ++ return directNeighbors[forward]; ++ } ++ ++ public int[] cardinalNeighbors(int forward) { ++ return cardinalNeighbors[forward]; ++ } ++ ++ /** ++ * Iterate over all neighboring nodes of the given source node. The iteration ++ * order is built from relative directions around the source, depending on the ++ * given 'forward' direction. This is an effort to eliminate any directional ++ * biases that would be emerge in rotationally symmetric circuits if the update ++ * order was built from absolute directions around the source. ++ *
++ * Each update order must include the source's direct neighbors, but further ++ * neighbors may not be included. ++ */ ++ public abstract void forEachNeighbor(NodeProvider nodes, Node source, int forward, Consumer action); ++ ++} 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 @@ -584,10 +980,10 @@ index 0000000000000000000000000000000000000000..4fd8cb29024330397cfe4cbc1f237d28 +} 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 +index 0000000000000000000000000000000000000000..c69dcf2b418a0a2f373425ea0dd7144fd2f33c87 --- /dev/null +++ b/src/main/java/alternate/current/wire/WireConnectionManager.java -@@ -0,0 +1,136 @@ +@@ -0,0 +1,134 @@ +package alternate.current.wire; + +import java.util.Arrays; @@ -642,24 +1038,22 @@ index 0000000000000000000000000000000000000000..5a7209f05b549c222f6c9bc2af2a3579 + + if (neighbor.isWire()) { + add(neighbor.asWire(), iDir, true, true); ++ } else { ++ boolean sideIsConductor = neighbor.isConductor(); + -+ continue; -+ } ++ if (!sideIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, Directions.DOWN); + -+ boolean sideIsConductor = neighbor.isConductor(); -+ -+ if (!sideIsConductor) { -+ Node node = nodes.getNeighbor(neighbor, Directions.DOWN); -+ -+ if (node.isWire()) { -+ add(node.asWire(), iDir, belowIsConductor, true); ++ if (node.isWire()) { ++ add(node.asWire(), iDir, belowIsConductor, true); ++ } + } -+ } -+ if (!aboveIsConductor) { -+ Node node = nodes.getNeighbor(neighbor, Directions.UP); ++ if (!aboveIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, Directions.UP); + -+ if (node.isWire()) { -+ add(node.asWire(), iDir, true, sideIsConductor); ++ if (node.isWire()) { ++ add(node.asWire(), iDir, true, sideIsConductor); ++ } + } + } + } @@ -716,8 +1110,8 @@ index 0000000000000000000000000000000000000000..5a7209f05b549c222f6c9bc2af2a3579 + * Iterate over all connections. Use this method if the iteration order is + * important. + */ -+ void forEach(Consumer consumer, int iFlowDir) { -+ for (int iDir : WireHandler.CARDINAL_UPDATE_ORDERS[iFlowDir]) { ++ void forEach(Consumer consumer, UpdateOrder updateOrder, int iFlowDir) { ++ for (int iDir : updateOrder.cardinalNeighbors(iFlowDir)) { + for (WireConnection c = heads[iDir]; c != null && c.iDir == iDir; c = c.next) { + consumer.accept(c); + } @@ -726,10 +1120,10 @@ index 0000000000000000000000000000000000000000..5a7209f05b549c222f6c9bc2af2a3579 +} diff --git a/src/main/java/alternate/current/wire/WireHandler.java b/src/main/java/alternate/current/wire/WireHandler.java new file mode 100644 -index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be73ae06c0b +index 0000000000000000000000000000000000000000..8b7e33ce050ba75139df1c56c007b7922fccd573 --- /dev/null +++ b/src/main/java/alternate/current/wire/WireHandler.java -@@ -0,0 +1,1150 @@ +@@ -0,0 +1,1053 @@ +package alternate.current.wire; + +import java.util.Iterator; @@ -747,6 +1141,8 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 +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.InstantNeighborUpdater; ++import net.minecraft.world.level.redstone.NeighborUpdater; +import net.minecraft.world.level.redstone.Redstone; + +/** @@ -960,36 +1356,9 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + -1, // 0b1111: west/north/east/south -> x + }; + /** -+ * Update orders of all directions. Given that the index encodes the direction -+ * that is to be considered 'forward', the resulting update order is -+ * { front, back, right, left, down, up }. ++ * Update order of shape updates, matching that of Vanilla. + */ -+ static final int[][] FULL_UPDATE_ORDERS = { -+ { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }, -+ { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP }, -+ { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP }, -+ { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP } -+ }; -+ /** -+ * The default update order of all directions. It is equivalent to the order of -+ * shape updates in vanilla Minecraft. -+ */ -+ static final int[] DEFAULT_FULL_UPDATE_ORDER = FULL_UPDATE_ORDERS[0]; -+ /** -+ * Update orders of cardinal directions. Given that the index encodes the -+ * direction that is to be considered 'forward', the resulting update order is -+ * { front, back, right, left }. -+ */ -+ static final int[][] CARDINAL_UPDATE_ORDERS = { -+ { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, -+ { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, -+ { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, -+ { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } -+ }; -+ /** -+ * The default update order of all cardinal directions. -+ */ -+ static final int[] DEFAULT_CARDINAL_UPDATE_ORDER = CARDINAL_UPDATE_ORDERS[0]; ++ static final int[] SHAPE_UPDATE_ORDER = { Directions.WEST, Directions.EAST, Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }; + + private static final int POWER_MIN = Redstone.SIGNAL_MIN; + private static final int POWER_MAX = Redstone.SIGNAL_MAX; @@ -1008,6 +1377,8 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + /** Queue of updates to wires and neighboring blocks. */ + private final Queue updates; + ++ private final NeighborUpdater neighborUpdater; ++ + // 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; @@ -1015,6 +1386,8 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + + /** Is this WireHandler currently working through the update queue? */ + private boolean updating; ++ /** The update order currently in use. */ ++ private UpdateOrder updateOrder; + + public WireHandler(ServerLevel level) { + this.level = level; @@ -1023,6 +1396,8 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + this.search = new SimpleQueue(); + this.updates = new PriorityQueue(); + ++ this.neighborUpdater = new InstantNeighborUpdater(this.level); ++ + this.nodeCache = new Node[16]; + this.fillNodeCache(0, 16); + } @@ -1162,80 +1537,6 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + } + + /** -+ * 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: -+ *
-+ * 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. -+ *
-+ * 2. Each neighbor is identified by the step(s) you must take, starting at the -+ * wire, to reach it. Each step is 1 block, thus the position of a neighbor is -+ * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left), -+ * etc. -+ *
-+ * 3. Neighbors are iterated over in pairs that lie on opposite sides of the -+ * wire. -+ *
-+ * 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. -+ *
-+ * 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 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) { @@ -1309,6 +1610,8 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + node.invalid = true; + } + } ++ ++ updateOrder = UpdateOrder.values()[level.paperConfig().misc.alternateCurrentUpdateOrder.ordinal()]; + } + + /** @@ -1354,7 +1657,7 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + return; + } + -+ for (int iDir : FULL_UPDATE_ORDERS[wire.iFlowDir]) { ++ for (int iDir : updateOrder.directNeighbors(wire.iFlowDir)) { + Node neighbor = getNeighbor(wire, iDir); + + if (neighbor.isConductor() || neighbor.isSignalSource()) { @@ -1670,7 +1973,7 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + if (needsUpdate(neighbor)) { + search(neighbor, false, connection.iDir); + } -+ }, wire.iFlowDir); ++ }, updateOrder, wire.iFlowDir); + } + } + @@ -1784,7 +2087,7 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + if (neighbor.offerPower(power, iDir)) { + queueWire(neighbor); + } -+ }, wire.iFlowDir); ++ }, updateOrder, wire.iFlowDir); + } + + /** @@ -1794,10 +2097,15 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + BlockPos wirePos = wire.pos; + BlockState wireState = wire.state; + -+ for (int iDir : DEFAULT_FULL_UPDATE_ORDER) { ++ for (int iDir : SHAPE_UPDATE_ORDER) { + Node neighbor = getNeighbor(wire, iDir); + -+ if (!neighbor.isWire()) { ++ // Shape updates to redstone wire are very expensive, and should never happen ++ // as a result of power changes anyway, while shape updates to air do nothing. ++ // The current block state at this position *could* be wrong, but if you somehow ++ // manage to place a block where air used to be during the execution of a shape ++ // update I am very impressed and you deserve to have some broken behavior. ++ if (!neighbor.isWire() && !neighbor.state.isAir()) { + int iOpp = Directions.iOpposite(iDir); + Direction opp = Directions.ALL[iOpp]; + @@ -1807,24 +2115,14 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + } + + private void updateShape(Node node, Direction dir, BlockPos neighborPos, BlockState neighborState) { -+ BlockPos pos = node.pos; -+ BlockState state = level.getBlockState(pos); -+ -+ // Shape updates to redstone wire are very expensive, and should never happen -+ // as a result of power changes anyway. -+ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { -+ BlockState newState = state.updateShape(dir, neighborState, level, pos, neighborPos); -+ Block.updateOrDestroy(state, newState, level, pos, Block.UPDATE_CLIENTS); -+ } ++ neighborUpdater.shapeUpdate(dir, neighborState, node.pos, neighborPos, Block.UPDATE_CLIENTS, 512); + } + + /** + * Queue block updates to nodes around the given wire. + */ + private void queueNeighbors(WireNode wire) { -+ forEachNeighbor(wire, neighbor -> { -+ queueNeighbor(neighbor, wire); -+ }); ++ updateOrder.forEachNeighbor(this::getNeighbor, wire, wire.iFlowDir, neighbor -> queueNeighbor(neighbor, wire)); + } + + /** @@ -1832,7 +2130,20 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + */ + private void queueNeighbor(Node node, WireNode neighborWire) { + // Updates to wires are queued when power is transmitted. -+ if (!node.isWire()) { ++ // 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. ++ // Block updates to air do nothing, so those are skipped as well. ++ // The current block state at this position *could* be wrong, but if you somehow ++ // manage to place a block where air used to be during the execution of a block ++ // update I am very impressed and you deserve to have some broken behavior. ++ if (!node.isWire() && !node.state.isAir()) { + node.neighborWire = neighborWire; + updates.offer(node); + } @@ -1856,21 +2167,7 @@ index 0000000000000000000000000000000000000000..e943fdcbc15d5c17450659c2cd9e0be7 + * Emit a block update to the given node. + */ + private void updateBlock(Node node, BlockPos neighborPos, Block neighborBlock) { -+ BlockPos pos = node.pos; -+ BlockState state = level.getBlockState(pos); -+ -+ // While this check makes sure wires in the network are not given block -+ // updates, it also prevents block updates to wires in neighboring networks. -+ // While this should not make a difference in theory, in practice, it is -+ // possible to force a network into an invalid state without updating it, even -+ // if it is relatively obscure. -+ // While I was willing to make this compromise in return for some significant -+ // performance gains in certain setups, if you are not, you can add all the -+ // positions of the network to a set and filter out block updates to wires in -+ // the network that way. -+ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { -+ state.handleNeighborChanged(level, pos, neighborBlock, neighborPos, false); -+ } ++ neighborUpdater.neighborChanged(node.pos, neighborBlock, neighborPos); + } + + @FunctionalInterface