From 91071dd74395c9e6e25e23696421927e18e6b64c Mon Sep 17 00:00:00 2001 From: theosib Date: Thu, 27 Sep 2018 01:43:35 -0600 Subject: [PATCH] Optimize redstone algorithm Author: theosib Co-authored-by: egg82 Original license: MIT This patch implements theosib's redstone algorithms to completely overhaul the way redstone works. The new algorithms should be many times faster than current vanilla ones. From the original author's comments, it looks like it shouldn't interfere with any redstone save for very extreme edge-cases. Surprisingly, not a lot was touched aside from a few obfuscation helpers and BlockRedstoneWire. A lot of this code is self-contained in a helper class. Aside from making the obvious class/function renames and obfhelpers I didn't need to modify much. Just added Bukkit's event system and took a few liberties with dead code and comment misspellings. diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java index dd5e263d71..357c7cf1df 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -430,4 +430,14 @@ public class PaperWorldConfig { private void preventMovingIntoUnloadedChunks() { preventMovingIntoUnloadedChunks = getBoolean("prevent-moving-into-unloaded-chunks", false); } + + public boolean useEigencraftRedstone = false; + private void useEigencraftRedstone() { + useEigencraftRedstone = this.getBoolean("use-faster-eigencraft-redstone", false); + if (useEigencraftRedstone) { + log("Using Eigencraft redstone algorithm by theosib."); + } else { + log("Using vanilla redstone algorithm."); + } + } } diff --git a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java new file mode 100644 index 0000000000..cf5661f1c5 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java @@ -0,0 +1,912 @@ +package com.destroystokyo.paper.util; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +import org.bukkit.event.block.BlockRedstoneEvent; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import net.minecraft.server.Block; +import net.minecraft.server.BlockPosition; +import net.minecraft.server.BlockRedstoneWire; +import net.minecraft.server.IBlockData; +import net.minecraft.server.Items; +import net.minecraft.server.ItemStack; +import net.minecraft.server.World; + +/** + * Used for the faster redstone algorithm. + * Original author: theosib + * Original license: MIT + * + * Ported to Paper and updated to 1.13 by egg82 + */ +public class RedstoneWireTurbo { + /* + * This is Helper class for BlockRedstoneWire. It implements a minimally-invasive + * bolt-on accelerator that performs a breadth-first search through redstone wire blocks + * in order to more efficiently and deterministically compute new redstone wire power levels + * and determine the order in which other blocks should be updated. + * + * Features: + * - Changes to BlockRedstoneWire are very limited, no other classes are affected, and the + * choice between old and new redstone wire update algorithms is switchable on-line. + * - The vanilla implementation relied on World.notifyNeighborsOfStateChange for redstone + * wire blocks to communicate power level changes to each other, generating 36 block + * updates per call. This improved implementation propagates power level changes directly + * between redstone wire blocks. Redstone wire power levels are therefore computed more quickly, + * and block updates are sent only to non-redstone blocks, many of which may perform an + * action when informed of a change in redstone power level. (Note: Block updates are not + * the same as state changes to redstone wire. Wire block states are updated as soon + * as they are computed.) + * - Of the 36 block updates generated by a call to World.notifyNeighborsOfStateChange, + * 12 of them are obviously redundant (e.g. the west neighbor of the east neighbor). + * These are eliminated. + * - Updates to redstone wire and other connected blocks are propagated in a breath-first + * manner, radiating out from the initial trigger (a block update to a redstone wire + * from something other than redstone wire). + * - Updates are scheduled both deterministically and in an intuitive order, addressing bug + * MC-11193. + * - All redstone behavior that used to be locational now works the same in all locations. + * - All behaviors of redstone wire that used to be orientational now work the same in all + * orientations, as long as orientation can be determined; random otherwise. Some other + * redstone components still update directionally (e.g. switches), and this code can't + * compensate for that. + * - Information that is otherwise computed over and over again or which is expensive to + * to compute is cached for faster lookup. This includes coordinates of block position + * neighbors and block states that won't change behind our backs during the execution of + * this search algorithm. + * - Redundant block updates (both to redstone wire and to other blocks) are heavily + * consolidated. For worst-case scenarios (depowering of redstone wire) this results + * in a reduction of block updates by as much as 95% (factor of 1/21). Due to overheads, + * empirical testing shows a speedup better than 10x. This addresses bug MC-81098. + * + * Extensive testing has been performed to ensure that existing redstone contraptions still + * behave as expected. Results of early testing that identified undesirable behavior changes + * were addressed. Additionally, real-time performance testing revealed compute inefficiencies + * With earlier implementations of this accelerator. Some compatibility adjustments and + * performance optimizations resulted in harmless increases in block updates above the + * theoretical minimum. + * + * Only a single redstone machine was found to break: An instant dropper line hack that + * relies on powered rails and quasi-connectivity but doesn't work in all directions. The + * replacement is to lay redstone wire directly on top of the dropper line, which now works + * reliably in any direction. + * + * There are numerous other optimization that can be made, but those will be provided later in + * separate updates. This version is designed to be minimalistic. + * + * Many thanks to the following individuals for their help in testing this functionality: + * - pokechu22, _MethodZz_, WARBEN, NarcolepticFrog, CommandHelper (nessie), ilmango, + * OreoLamp, Xcom6000, tryashtar, RedCMD, Smokey95Dog, EDDxample, Rays Works, + * Nodnam, BlockyPlays, Grumm, NeunEinser, HelVince. + */ + + /* Reference to BlockRedstoneWire object, which uses this accelerator */ + private final BlockRedstoneWire wire; + + /* + * Implementation: + * + * RedstoneWire Blocks are updated in concentric rings or "layers" radiating out from the + * initial block update that came from a call to BlockRedstoneWire.neighborChanged(). + * All nodes put in Layer N are those with Manhattan distance N from the trigger + * position, reachable through connected redstone wire blocks. + * + * Layer 0 represents the trigger block position that was input to neighborChanged. + * Layer 1 contains the immediate neighbors of that position. + * Layer N contains the neighbors of blocks in layer N-1, not including + * those in previous layers. + * + * Layers enforce an update order that is a function of Manhattan distance + * from the initial coordinates input to neighborChanged. The same + * coordinates may appear in multiple layers, but redundant updates are minimized. + * Block updates are sent layer-by-layer. If multiple of a block's neighbors experience + * redstone wire changes before its layer is processed, then those updates will be merged. + * If a block's update has been sent, but its neighboring redstone changes + * after that, then another update will be sent. This preserves compatibility with + * machines that rely on zero-tick behavior, except that the new functionality is non- + * locational. + * + * Within each layer, updates are ordered left-to-right relative to the direction of + * information flow. This makes the implementation non-orientational. Only when + * this direction is ambiguous is randomness applied (intentionally). + */ + private List updateQueue0 = Lists.newArrayList(); + private List updateQueue1 = Lists.newArrayList(); + private List updateQueue2 = Lists.newArrayList(); + + public RedstoneWireTurbo(BlockRedstoneWire wire) { + this.wire = wire; + } + + /* + * Compute neighbors of a block. When a redstone wire value changes, previously it called + * World.notifyNeighborsOfStateChange. That lists immediately neighboring blocks in + * west, east, down, up, north, south order. For each of those neighbors, their own + * neighbors are updated in the same order. This generates 36 updates, but 12 of them are + * redundant; for instance the west neighbor of a block's east neighbor. + * + * Note that this ordering is only used to create the initial list of neighbors. Once + * the direction of signal flow is identified, the ordering of updates is completely + * reorganized. + */ + public static BlockPosition[] computeAllNeighbors(final BlockPosition pos) { + final int x = pos.getX(); + final int y = pos.getY(); + final int z = pos.getZ(); + final BlockPosition[] n = new BlockPosition[24]; + + // Immediate neighbors, in the same order as + // World.notifyNeighborsOfStateChange, etc.: + // west, east, down, up, north, south + n[0] = new BlockPosition(x - 1, y, z); + n[1] = new BlockPosition(x + 1, y, z); + n[2] = new BlockPosition(x, y - 1, z); + n[3] = new BlockPosition(x, y + 1, z); + n[4] = new BlockPosition(x, y, z - 1); + n[5] = new BlockPosition(x, y, z + 1); + + // Neighbors of neighbors, in the same order, + // except that duplicates are not included + n[6] = new BlockPosition(x - 2, y, z); + n[7] = new BlockPosition(x - 1, y - 1, z); + n[8] = new BlockPosition(x - 1, y + 1, z); + n[9] = new BlockPosition(x - 1, y, z - 1); + n[10] = new BlockPosition(x - 1, y, z + 1); + n[11] = new BlockPosition(x + 2, y, z); + n[12] = new BlockPosition(x + 1, y - 1, z); + n[13] = new BlockPosition(x + 1, y + 1, z); + n[14] = new BlockPosition(x + 1, y, z - 1); + n[15] = new BlockPosition(x + 1, y, z + 1); + n[16] = new BlockPosition(x, y - 2, z); + n[17] = new BlockPosition(x, y - 1, z - 1); + n[18] = new BlockPosition(x, y - 1, z + 1); + n[19] = new BlockPosition(x, y + 2, z); + n[20] = new BlockPosition(x, y + 1, z - 1); + n[21] = new BlockPosition(x, y + 1, z + 1); + n[22] = new BlockPosition(x, y, z - 2); + n[23] = new BlockPosition(x, y, z + 2); + return n; + } + + /* + * We only want redstone wires to update redstone wires that are + * immediately adjacent. Some more distant updates can result + * in cross-talk that (a) wastes time and (b) can make the update + * order unintuitive. Therefore (relative to the neighbor order + * computed by computeAllNeighbors), updates are not scheduled + * for redstone wire in those non-connecting positions. On the + * other hand, updates will always be sent to *other* types of blocks + * in any of the 24 neighboring positions. + */ + private static final boolean[] update_redstone = { + true, true, false, false, true, true, // 0 to 5 + false, true, true, false, false, false, // 6 to 11 + true, true, false, false, false, true, // 12 to 17 + true, false, true, true, false, false // 18 to 23 + }; + + // Internal numbering for cardinal directions + private static final int North = 0; + private static final int East = 1; + private static final int South = 2; + private static final int West = 3; + + /* + * These lookup tables completely remap neighbor positions into a left-to-right + * ordering, based on the cardinal direction that is determined to be forward. + * See below for more explanation. + */ + private static final int[] forward_is_north = {2, 3, 16, 19, 0, 4, 1, 5, 7, 8, 17, 20, 12, 13, 18, 21, 6, 9, 22, 14, 11, 10, 23, 15}; + private static final int[] forward_is_east = {2, 3, 16, 19, 4, 1, 5, 0, 17, 20, 12, 13, 18, 21, 7, 8, 22, 14, 11, 15, 23, 9, 6, 10}; + private static final int[] forward_is_south = {2, 3, 16, 19, 1, 5, 0, 4, 12, 13, 18, 21, 7, 8, 17, 20, 11, 15, 23, 10, 6, 14, 22, 9}; + private static final int[] forward_is_west = {2, 3, 16, 19, 5, 0, 4, 1, 18, 21, 7, 8, 17, 20, 12, 13, 23, 10, 6, 9, 22, 15, 11, 14}; + + /* For any orientation, we end up with the update order defined below. This order is relative to any redstone wire block + * that is itself having an update computed, and this center position is marked with C. + * - The update position marked 0 is computed first, and the one marked 23 is last. + * - Forward is determined by the local direction of information flow into position C from prior updates. + * - The first updates are scheduled for the four positions below and above C. + * - Then updates are scheduled for the four horizontal neighbors of C, followed by the positions below and above those neighbors. + * - Finally, updates are scheduled for the remaining positions with Manhattan distance 2 from C (at the same Y coordinate). + * - For a given horizontal distance from C, updates are scheduled starting from directly left and stepping clockwise to directly + * right. The remaining positions behind C are scheduled counterclockwise so as to maintain the left-to-right ordering. + * - If C is in layer N of the update schedule, then all 24 positions may be scheduled for layer N+1. For redstone wire, no + * updates are scheduled for positions that cannot directly connect. Additionally, the four positions above and below C + * are ALSO scheduled for layer N+2. + * - This update order was selected after experimenting with a number of alternative schedules, based on its compatibility + * with existing redstone designs and behaviors that were considered to be intuitive by various testers. WARBEN in particular + * made some of the most challenging test cases, but the 3-tick clocks (made by RedCMD) were also challenging to fix, + * along with the rail-based instant dropper line built by ilmango. Numerous others made test cases as well, including + * NarcolepticFrog, nessie, and Pokechu22. + * + * - The forward direction is determined locally. So when there are branches in the redstone wire, the left one will get updated + * before the right one. Each branch can have its own relative forward direction, resulting in the left side of a left branch + * having priority over the right branch of a left branch, which has priority over the left branch of a right branch, followed + * by the right branch of a right branch. And so forth. Since redstone power reduces to zero after a path distance of 15, + * that imposes a practical limit on the branching. Note that the branching is not tracked explicitly -- relative forward + * directions dictate relative sort order, which maintains the proper global ordering. This also makes it unnecessary to be + * concerned about branches meeting up with each other. + * + * ^ + * | + * Forward + * <-- Left Right --> + * + * 18 + * 10 17 5 19 11 + * 2 8 0 12 16 4 C 6 20 9 1 13 3 + * 14 21 7 23 15 + * Further 22 Further + * Down Down Up Up + * + * Backward + * | + * V + */ + + // This allows the above remapping tables to be looked up by cardial direction index + private static final int[][] reordering = { forward_is_north, forward_is_east, forward_is_south, forward_is_west }; + + /* + * Input: Array of UpdateNode objects in an order corresponding to the positions + * computed by computeAllNeighbors above. + * Output: Array of UpdateNode objects oriented using the above remapping tables + * corresponding to the identified heading (direction of information flow). + */ + private static void orientNeighbors(final UpdateNode[] src, final UpdateNode[] dst, final int heading) { + final int[] re = reordering[heading]; + for (int i = 0; i < 24; i++) { + dst[i] = src[re[i]]; + } + } + + /* + * Structure to keep track of redstone wire blocks and + * neighbors that will receive updates. + */ + private static class UpdateNode { + public static enum Type { + UNKNOWN, REDSTONE, OTHER + } + + IBlockData currentState; // Keep track of redstone wire value + UpdateNode[] neighbor_nodes; // References to neighbors (directed graph edges) + BlockPosition self; // UpdateNode's own position + BlockPosition parent; // Which block pos spawned/updated this node + Type type = Type.UNKNOWN; // unknown, redstone wire, other type of block + int layer; // Highest layer this node is scheduled in + boolean visited; // To keep track of information flow direction, visited restone wire is marked + int xbias, zbias; // Remembers directionality of ancestor nodes; helps eliminate directional ambiguities. + } + + /* + * Keep track of all block positions discovered during search and their current states. + * We want to remember one entry for each position. + */ + private final Map nodeCache = Maps.newHashMap(); + + /* + * For a newly created UpdateNode object, determine what type of block it is. + */ + private void identifyNode(final World worldIn, final UpdateNode upd1) { + final BlockPosition pos = upd1.self; + final IBlockData oldState = worldIn.getType(pos); + upd1.currentState = oldState; + + // Some neighbors of redstone wire are other kinds of blocks. + // These need to receive block updates to inform them that + // redstone wire values have changed. + final Block block = oldState.getBlock(); + if (block != wire) { + // Mark this block as not redstone wire and therefore + // requiring updates + upd1.type = UpdateNode.Type.OTHER; + + // Non-redstone blocks may propagate updates, but those updates + // are not handled by this accelerator. Therefore, we do not + // expand this position's neighbors. + return; + } + + // One job of BlockRedstoneWire.neighborChanged is to convert + // redstone wires to items if the block beneath was removed. + // With this accelerator, BlockRedstoneWire.neighborChanged + // is only typically called for a single wire block, while + // others are processed internally by the breadth first search + // algorithm. To preserve this game behavior, this check must + // be replicated here. + if (!wire.canPlace(null, worldIn, pos)) { + // Pop off the redstone dust + Block.a(worldIn, pos, new ItemStack(Items.REDSTONE)); // TODO + worldIn.setAir(pos); + + // Mark this position as not being redstone wire + upd1.type = UpdateNode.Type.OTHER; + + // Note: Sending updates to air blocks leads to an empty method. + // Testing shows this to be faster than explicitly avoiding updates to + // air blocks. + return; + } + + // If the above conditions fail, then this is a redstone wire block. + upd1.type = UpdateNode.Type.REDSTONE; + } + + /* + * Given which redstone wire blocks have been visited and not visited + * around the position currently being updated, compute the cardinal + * direction that is "forward." + * + * rx is the forward direction along the West/East axis + * rz is the forward direction along the North/South axis + */ + static private int computeHeading(final int rx, final int rz) { + // rx and rz can only take on values -1, 0, and 1, so we can + // compute a code number that allows us to use a single switch + // to determine the heading. + final int code = (rx + 1) + 3 * (rz + 1); + switch (code) { + case 0: { + // Both rx and rz are -1 (northwest) + // Randomly choose one to be forward. + final int j = ThreadLocalRandom.current().nextInt(0, 1); + return (j == 0) ? North : West; + } + case 1: { + // rx=0, rz=-1 + // Definitively North + return North; + } + case 2: { + // rx=1, rz=-1 (northeast) + // Choose randomly between north and east + final int j = ThreadLocalRandom.current().nextInt(0, 1); + return (j == 0) ? North : East; + } + case 3: { + // rx=-1, rz=0 + // Definitively West + return West; + } + case 4: { + // rx=0, rz=0 + // Heading is completely ambiguous. Choose + // randomly among the four cardinal directions. + return ThreadLocalRandom.current().nextInt(0, 4); + } + case 5: { + // rx=1, rz=0 + // Definitively East + return East; + } + case 6: { + // rx=-1, rz=1 (southwest) + // Choose randomly between south and west + final int j = ThreadLocalRandom.current().nextInt(0, 1); + return (j == 0) ? South : West; + } + case 7: { + // rx=0, rz=1 + // Definitively South + return South; + } + case 8: { + // rx=1, rz=1 (southeast) + // Choose randomly between south and east + final int j = ThreadLocalRandom.current().nextInt(0, 1); + return (j == 0) ? South : East; + } + } + + // We should never get here + return ThreadLocalRandom.current().nextInt(0, 4); + } + + // Select whether to use updateSurroundingRedstone from BlockRedstoneWire (old) + // or this helper class (new) + private static final boolean old_current_change = false; + + /* + * Process a node whose neighboring redstone wire has experienced value changes. + */ + private void updateNode(final World worldIn, final UpdateNode upd1, final int layer) { + final BlockPosition pos = upd1.self; + + // Mark this redstone wire as having been visited so that it can be used + // to calculate direction of information flow. + upd1.visited = true; + + // Look up the last known state. + // Due to the way other redstone components are updated, we do not + // have to worry about a state changing behind our backs. The rare + // exception is handled by scheduleReentrantNeighborChanged. + final IBlockData oldState = upd1.currentState; + + // Ask the wire block to compute its power level from its neighbors. + // This will also update the wire's power level and return a new + // state if it has changed. When a wire power level is changed, + // calculateCurrentChanges will immediately update the block state in the world + // and return the same value here to be cached in the corresponding + // UpdateNode object. + IBlockData newState; + if (old_current_change) { + newState = wire.calculateCurrentChanges(worldIn, pos, pos, oldState); + } else { + // Looking up block state is slow. This accelerator includes a version of + // calculateCurrentChanges that uses cahed wire values for a + // significant performance boost. + newState = this.calculateCurrentChanges(worldIn, upd1); + } + + // Only inform neighbors if the state has changed + if (newState != oldState) { + // Store the new state + upd1.currentState = newState; + + // Inform neighbors of the change + propagateChanges(worldIn, upd1, layer); + } + } + + /* + * This identifies the neighboring positions of a new UpdateNode object, + * determines their types, and links those to into the graph. Then based on + * what nodes in the redstone wire graph have been visited, the neighbors + * are reordered left-to-right relative to the direction of information flow. + */ + private void findNeighbors(final World worldIn, final UpdateNode upd1) { + final BlockPosition pos = upd1.self; + + // Get the list of neighbor coordinates + final BlockPosition[] neighbors = computeAllNeighbors(pos); + + // Temporary array of neighbors in cardinal ordering + final UpdateNode[] neighbor_nodes = new UpdateNode[24]; + + // Target array of neighbors sorted left-to-right + upd1.neighbor_nodes = new UpdateNode[24]; + + for (int i=0; i<24; i++) { + // Look up each neighbor in the node cache + final BlockPosition pos2 = neighbors[i]; + UpdateNode upd2 = nodeCache.get(pos2); + if (upd2 == null) { + // If this is a previously unreached position, create + // a new update node, add it to the cache, and identify what it is. + upd2 = new UpdateNode(); + upd2.self = pos2; + upd2.parent = pos; + nodeCache.put(pos2, upd2); + identifyNode(worldIn, upd2); + } + + // For non-redstone blocks, any of the 24 neighboring positions + // should receive a block update. However, some block coordinates + // may contain a redstone wire that does not directly connect to the + // one being expanded. To avoid redundant calculations and confusing + // cross-talk, those neighboring positions are not included. + if (update_redstone[i] || upd2.type != UpdateNode.Type.REDSTONE) { + neighbor_nodes[i] = upd2; + } + } + + // Determine the directions from which the redstone signal may have come from. This + // checks for redstone wire at the same Y level and also Y+1 and Y-1, relative to the + // block being expanded. + final boolean fromWest = (neighbor_nodes[0].visited || neighbor_nodes[7].visited || neighbor_nodes[8].visited); + final boolean fromEast = (neighbor_nodes[1].visited || neighbor_nodes[12].visited || neighbor_nodes[13].visited); + final boolean fromNorth = (neighbor_nodes[4].visited || neighbor_nodes[17].visited || neighbor_nodes[20].visited); + final boolean fromSouth = (neighbor_nodes[5].visited || neighbor_nodes[18].visited || neighbor_nodes[21].visited); + + int cx = 0, cz = 0; + if (fromWest) cx += 1; + if (fromEast) cx -= 1; + if (fromNorth) cz += 1; + if (fromSouth) cz -= 1; + + int heading; + if (cx==0 && cz==0) { + // If there is no clear direction, try to inherit the heading from ancestor nodes. + heading = computeHeading(upd1.xbias, upd1.zbias); + + // Propagate that heading to descendant nodes. + for (int i=0; i<24; i++) { + final UpdateNode nn = neighbor_nodes[i]; + if (nn != null) { + nn.xbias = upd1.xbias; + nn.zbias = upd1.zbias; + } + } + } else { + if (cx != 0 && cz != 0) { + // If the heading is somewhat ambiguous, try to disambiguate based on + // ancestor nodes. + if (upd1.xbias != 0) cz = 0; + if (upd1.zbias != 0) cx = 0; + } + heading = computeHeading(cx, cz); + + // Propagate that heading to descendant nodes. + for (int i=0; i<24; i++) { + final UpdateNode nn = neighbor_nodes[i]; + if (nn != null) { + nn.xbias = cx; + nn.zbias = cz; + } + } + } + + // Reorder neighboring UpdateNode objects according to the forward direction + // determined above. + orientNeighbors(neighbor_nodes, upd1.neighbor_nodes, heading); + } + + /* + * For any redstone wire block in layer N, inform neighbors to recompute their states + * in layers N+1 and N+2; + */ + private void propagateChanges(final World worldIn, final UpdateNode upd1, final int layer) { + if (upd1.neighbor_nodes == null) { + // If this node has not been expanded yet, find its neighbors + findNeighbors(worldIn, upd1); + } + + final BlockPosition pos = upd1.self; + + // All neighbors may be scheduled for layer N+1 + final int layer1 = layer + 1; + + // If the node being updated (upd1) has already been expanded, then merely + // schedule updates to its neighbors. + for (int i = 0; i < 24; i++) { + final UpdateNode upd2 = upd1.neighbor_nodes[i]; + + // This test ensures that an UpdateNode is never scheduled to the same layer + // more than once. Also, skip non-connecting redstone wire blocks + if (upd2 != null && layer1 > upd2.layer) { + upd2.layer = layer1; + updateQueue1.add(upd2); + + // Keep track of which block updated this neighbor + upd2.parent = pos; + } + } + + // Nodes above and below are scheduled ALSO for layer N+2 + final int layer2 = layer + 2; + + // Repeat of the loop above, but only for the first four (above and below) neighbors + // and for layer N+2; + for (int i = 0; i < 4; i++) { + final UpdateNode upd2 = upd1.neighbor_nodes[i]; + if (upd2 != null && layer2 > upd2.layer) { + upd2.layer = layer2; + updateQueue2.add(upd2); + upd2.parent = pos; + } + } + } + + // The breadth-first search below will send block updates to blocks + // that are not redstone wire. If one of those updates results in + // a distant redstone wire getting an update, then this.neighborChanged + // will get called. This would be a reentrant call, and + // it is necessary to properly integrate those updates into the + // on-going search through redstone wire. Thus, we make the layer + // currently being processed visible at the object level. + + // The current layer being processed by the breadth-first search + private int currentWalkLayer = 0; + + private void shiftQueue() { + final List t = updateQueue0; + t.clear(); + updateQueue0 = updateQueue1; + updateQueue1 = updateQueue2; + updateQueue2 = t; + } + + /* + * Perform a breadth-first (layer by layer) traversal through redstone + * wire blocks, propagating value changes to neighbors in an order + * that is a function of distance from the initial call to + * this.neighborChanged. + */ + private void breadthFirstWalk(final World worldIn) { + shiftQueue(); + currentWalkLayer = 1; + + // Loop over all layers + while (updateQueue0.size()>0 || updateQueue1.size()>0) { + // Get the set of blocks in this layer + final List thisLayer = updateQueue0; + + // Loop over all blocks in the layer. Recall that + // this is a List, preserving the insertion order of + // left-to-right based on direction of information flow. + for (UpdateNode upd : thisLayer) { + if (upd.type == UpdateNode.Type.REDSTONE) { + // If the node is is redstone wire, + // schedule updates to neighbors if its value + // has changed. + updateNode(worldIn, upd, currentWalkLayer); + } else { + // If this block is not redstone wire, send a block update. + // Redstone wire blocks get state updates, but they don't + // need block updates. Only non-redstone neighbors need updates. + + // World.neighborChanged is called from + // World.notifyNeighborsOfStateChange, and + // notifyNeighborsOfStateExcept. We don't use + // World.notifyNeighborsOfStateChange here, since we are + // already keeping track of all of the neighbor positions + // that need to be updated. All on its own, handling neighbors + // this way reduces block updates by 1/3 (24 instead of 36). + worldIn.neighborChanged(upd.self, wire, upd.parent); + } + } + + // Move on to the next layer + shiftQueue(); + currentWalkLayer++; + } + + currentWalkLayer = 0; + } + + /* + * Normally, when Minecraft is computing redstone wire power changes, and a wire power level + * change sends a block update to a neighboring functional component (e.g. piston, repeater, etc.), + * those updates are queued. Only once all redstone wire updates are complete will any component + * action generate any further block updates to redstone wire. Instant repeater lines, for instance, + * will process all wire updates for one redstone line, after which the pistons will zero-tick, + * after which the next redstone line performs all of its updates. Thus, each wire is processed in its + * own discrete wave. + * + * However, there are some corner cases where this pattern breaks, with a proof of concept discovered + * by Rays Works, which works the same in vanilla. The scenario is as follows: + * (1) A redstone wire is conducting a signal. + * (2) Part-way through that wave of updates, a neighbor is updated that causes an update to a completely + * separate redstone wire. + * (3) This results in a call to BlockRedstoneWire.neighborChanged for that other wire, in the middle of + * an already on-going propagation through the first wire. + * + * The vanilla code, being depth-first, would end up fully processing the second wire before going back + * to finish processing the first one. (Although technically, vanilla has no special concept of "being + * in the middle" of processing updates to a wire.) For the breadth-first algorithm, we give this + * situation special handling, where the updates for the second wire are incorporated into the schedule + * for the first wire, and then the callstack is allowed to unwind back to the on-going search loop in + * order to continue processing both the first and second wire in the order of distance from the initial + * trigger. + */ + private IBlockData scheduleReentrantNeighborChanged(final World worldIn, final BlockPosition pos, final IBlockData newState, final BlockPosition source) { + if (source != null) { + // If the cause of the redstone wire update is known, we can use that to help determine + // direction of information flow. + UpdateNode src = nodeCache.get(source); + if (src == null) { + src = new UpdateNode(); + src.self = source; + src.parent = source; + src.visited = true; + identifyNode(worldIn, src); + nodeCache.put(source, src); + } + } + + // Find or generate a node for the redstone block position receiving the update + UpdateNode upd = nodeCache.get(pos); + if (upd == null) { + upd = new UpdateNode(); + upd.self = pos; + upd.parent = pos; + upd.visited = true; + identifyNode(worldIn, upd); + nodeCache.put(pos, upd); + } + upd.currentState = newState; + + // Receiving this block update may mean something in the world changed. + // Therefore we clear the cached block info about all neighbors of + // the position receiving the update and then re-identify what they are. + if (upd.neighbor_nodes != null) { + for (int i=0; i<24; i++) { + final UpdateNode upd2 = upd.neighbor_nodes[i]; + if (upd2 == null) continue; + upd2.type = UpdateNode.Type.UNKNOWN; + upd2.currentState = null; + identifyNode(worldIn, upd2); + } + } + + // The block at 'pos' is a redstone wire and has been updated already by calling + // wire.calculateCurrentChanges, so we don't schedule that. However, we do need + // to schedule its neighbors. By passing the current value of 'currentWalkLayer' to + // propagateChanges, the neighbors of 'pos' are scheduled for layers currentWalkLayer+1 + // and currentWalkLayer+2. + propagateChanges(worldIn, upd, currentWalkLayer); + + // Return here. The call stack will unwind back to the first call to + // updateSurroundingRedstone, whereupon the new updates just scheduled will + // be propagated. This also facilitates elimination of superfluous and + // redundant block updates. + return newState; + } + + /* + * New version of pre-existing updateSurroundingRedstone, which is called from + * wire.updateSurroundingRedstone, which is called from wire.neighborChanged and a + * few other methods in BlockRedstoneWire. This sets off the breadth-first + * walk through all redstone dust connected to the initial position triggered. + */ + public IBlockData updateSurroundingRedstone(final World worldIn, final BlockPosition pos, final IBlockData state, final BlockPosition source) { + // Check this block's neighbors and see if its power level needs to change + // Use the calculateCurrentChanges method in BlockRedstoneWire since we have no + // cached block states at this point. + final IBlockData newState = wire.calculateCurrentChanges(worldIn, pos, pos, state); + + // If no change, exit + if (newState == state) { + return state; + } + + // Check to see if this update was received during an on-going breadth first search + if (currentWalkLayer > 0 || nodeCache.size() > 0) { + // As breadthFirstWalk progresses, it sends block updates to neighbors. Some of those + // neighbors may affect the world so as to cause yet another redstone wire block to receive + // an update. If that happens, we need to integrate those redstone wire updates into the + // already on-going graph walk being performed by breadthFirstWalk. + return scheduleReentrantNeighborChanged(worldIn, pos, newState, source); + } + // If there are no on-going walks through redstone wire, then start a new walk. + + // If the source of the block update to the redstone wire at 'pos' is known, we can use + // that to help determine the direction of information flow. + if (source != null) { + final UpdateNode src = new UpdateNode(); + src.self = source; + src.parent = source; + src.visited = true; + nodeCache.put(source, src); + identifyNode(worldIn, src); + } + + // Create a node representing the block at 'pos', and then propagate updates + // to its neighbors. As stated above, the call to wire.calculateCurrentChanges + // already performs the update to the block at 'pos', so it is not added to the schedule. + final UpdateNode upd = new UpdateNode(); + upd.self = pos; + upd.parent = source!=null ? source : pos; + upd.currentState = newState; + upd.type = UpdateNode.Type.REDSTONE; + upd.visited = true; + nodeCache.put(pos, upd); + propagateChanges(worldIn, upd, 0); + + // Perform the walk over all directly reachable redstone wire blocks, propagating wire value + // updates in a breadth first order out from the initial update received for the block at 'pos'. + breadthFirstWalk(worldIn); + + // With the whole search completed, clear the list of all known blocks. + // We do not want to keep around state information that may be changed by other code. + // In theory, we could cache the neighbor block positions, but that is a separate + // optimization. + nodeCache.clear(); + + return newState; + } + + // For any array of neighbors in an UpdateNode object, these are always + // the indices of the four immediate neighbors at the same Y coordinate. + private static final int[] rs_neighbors = {4, 5, 6, 7}; + private static final int[] rs_neighbors_up = {9, 11, 13, 15}; + private static final int[] rs_neighbors_dn = {8, 10, 12, 14}; + + /* + * Updated calculateCurrentChanges that is optimized for speed and uses + * the UpdateNode's neighbor array to find the redstone states of neighbors + * that might power it. + */ + private IBlockData calculateCurrentChanges(final World worldIn, final UpdateNode upd) { + IBlockData state = upd.currentState; + final int i = state.get(BlockRedstoneWire.POWER).intValue(); + int j = 0; + j = getMaxCurrentStrength(upd, j); + int l = 0; + + wire.setCanProvidePower(false); + // Unfortunately, World.isBlockIndirectlyGettingPowered is complicated, + // and I'm not ready to try to replicate even more functionality from + // elsewhere in Minecraft into this accelerator. So sadly, we must + // suffer the performance hit of this very expensive call. If there + // is consistency to what this call returns, we may be able to cache it. + final int k = worldIn.isBlockIndirectlyGettingPowered(upd.self); + wire.setCanProvidePower(true); + + // The variable 'k' holds the maximum redstone power value of any adjacent blocks. + // If 'k' has the highest level of all neighbors, then the power level of this + // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the + // following loop can affect the power level of the wire. Therefore, the loop is + // skipped if k is already 15. + if (k < 15) { + if (upd.neighbor_nodes == null) { + // If this node's neighbors are not known, expand the node + findNeighbors(worldIn, upd); + } + + // These remain constant, so pull them out of the loop. + // Regardless of which direction is forward, the UpdateNode for the + // position directly above the node being calculated is always + // at index 1. + UpdateNode center_up = upd.neighbor_nodes[1]; + boolean center_up_is_cube = center_up.currentState.isOccluding(worldIn, center_up.self); // TODO + + for (int m = 0; m < 4; m++) { + // Get the neighbor array index of each of the four cardinal + // neighbors. + int n = rs_neighbors[m]; + + // Get the max redstone power level of each of the cardinal + // neighbors + UpdateNode neighbor = upd.neighbor_nodes[n]; + l = getMaxCurrentStrength(neighbor, l); + + // Also check the positions above and below the cardinal + // neighbors + boolean neighbor_is_cube = neighbor.currentState.isOccluding(worldIn, neighbor.self); // TODO + if (!neighbor_is_cube) { + UpdateNode neighbor_down = upd.neighbor_nodes[rs_neighbors_dn[m]]; + l = getMaxCurrentStrength(neighbor_down, l); + } else + if (!center_up_is_cube) { + UpdateNode neighbor_up = upd.neighbor_nodes[rs_neighbors_up[m]]; + l = getMaxCurrentStrength(neighbor_up, l); + } + } + } + + // The new code sets this RedstoneWire block's power level to the highest neighbor + // minus 1. This usually results in wire power levels dropping by 2 at a time. + // This optimization alone has no impact on update order, only the number of updates. + j = l - 1; + + // If 'l' turns out to be zero, then j will be set to -1, but then since 'k' will + // always be in the range of 0 to 15, the following if will correct that. + if (k > j) j = k; + + // egg82's amendment + // Adding Bukkit's BlockRedstoneEvent - er.. event. + if (i != j) { + BlockRedstoneEvent event = new BlockRedstoneEvent(worldIn.getWorld().getBlockAt(upd.self.getX(), upd.self.getY(), upd.self.getZ()), i, j); + worldIn.getServer().getPluginManager().callEvent(event); + j = event.getNewCurrent(); + } + + if (i != j) { + // If the power level has changed from its previous value, compute a new state + // and set it in the world. + // Possible optimization: Don't commit state changes to the world until they + // need to be known by some nearby non-redstone-wire block. + state = state.set(BlockRedstoneWire.POWER, Integer.valueOf(j)); + worldIn.setTypeAndData(upd.self, state, 2); + } + + return state; + } + + /* + * Optimized function to compute a redstone wire's power level based on cached + * state. + */ + private static int getMaxCurrentStrength(final UpdateNode upd, final int strength) { + if (upd.type != UpdateNode.Type.REDSTONE) return strength; + final int i = upd.currentState.get(BlockRedstoneWire.POWER).intValue(); + return i > strength ? i : strength; + } +} diff --git a/src/main/java/net/minecraft/server/BlockRedstoneWire.java b/src/main/java/net/minecraft/server/BlockRedstoneWire.java index 5bf2fc0b3f..52a4982ecd 100644 --- a/src/main/java/net/minecraft/server/BlockRedstoneWire.java +++ b/src/main/java/net/minecraft/server/BlockRedstoneWire.java @@ -1,5 +1,7 @@ package net.minecraft.server; +import com.destroystokyo.paper.PaperConfig; +import com.destroystokyo.paper.util.RedstoneWireTurbo; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -22,8 +24,8 @@ public class BlockRedstoneWire extends Block { public static final BlockStateInteger POWER = BlockProperties.at; public static final Map> f = Maps.newEnumMap(ImmutableMap.of(EnumDirection.NORTH, BlockRedstoneWire.NORTH, EnumDirection.EAST, BlockRedstoneWire.EAST, EnumDirection.SOUTH, BlockRedstoneWire.SOUTH, EnumDirection.WEST, BlockRedstoneWire.WEST)); protected static final VoxelShape[] g = new VoxelShape[]{Block.a(3.0D, 0.0D, 3.0D, 13.0D, 1.0D, 13.0D), Block.a(3.0D, 0.0D, 3.0D, 13.0D, 1.0D, 16.0D), Block.a(0.0D, 0.0D, 3.0D, 13.0D, 1.0D, 13.0D), Block.a(0.0D, 0.0D, 3.0D, 13.0D, 1.0D, 16.0D), Block.a(3.0D, 0.0D, 0.0D, 13.0D, 1.0D, 13.0D), Block.a(3.0D, 0.0D, 0.0D, 13.0D, 1.0D, 16.0D), Block.a(0.0D, 0.0D, 0.0D, 13.0D, 1.0D, 13.0D), Block.a(0.0D, 0.0D, 0.0D, 13.0D, 1.0D, 16.0D), Block.a(3.0D, 0.0D, 3.0D, 16.0D, 1.0D, 13.0D), Block.a(3.0D, 0.0D, 3.0D, 16.0D, 1.0D, 16.0D), Block.a(0.0D, 0.0D, 3.0D, 16.0D, 1.0D, 13.0D), Block.a(0.0D, 0.0D, 3.0D, 16.0D, 1.0D, 16.0D), Block.a(3.0D, 0.0D, 0.0D, 16.0D, 1.0D, 13.0D), Block.a(3.0D, 0.0D, 0.0D, 16.0D, 1.0D, 16.0D), Block.a(0.0D, 0.0D, 0.0D, 16.0D, 1.0D, 13.0D), Block.a(0.0D, 0.0D, 0.0D, 16.0D, 1.0D, 16.0D)}; - private boolean h = true; - private final Set i = Sets.newHashSet(); + private boolean h = true; public final boolean canProvidePower() { return this.h; } public final void setCanProvidePower(boolean value) { this.h = value; } // Paper - OBFHELPER + private final Set i = Sets.newHashSet(); private Set getBlocksNeedingUpdate() { return this.i; } // Paper - OBFHELPER public BlockRedstoneWire(Block.Info block_info) { super(block_info); @@ -157,6 +159,117 @@ public class BlockRedstoneWire extends Block { return iblockdata1.d(iworldreader, blockposition1, EnumDirection.UP) || iblockdata1.getBlock() == Blocks.HOPPER; } + // Paper start - Optimize redstone + // The bulk of the new functionality is found in RedstoneWireTurbo.java + RedstoneWireTurbo turbo = new RedstoneWireTurbo(this); + + /* + * Modified version of pre-existing updateSurroundingRedstone, which is called from + * this.neighborChanged and a few other methods in this class. + * Note: Added 'source' argument so as to help determine direction of information flow + */ + private IBlockData updateSurroundingRedstone(World worldIn, BlockPosition pos, IBlockData state, BlockPosition source) { + if (worldIn.paperConfig.useEigencraftRedstone) { + return turbo.updateSurroundingRedstone(worldIn, pos, state, source); + } + return a(worldIn, pos, state); + } + + /* + * Slightly modified method to compute redstone wire power levels from neighboring blocks. + * Modifications cut the number of power level changes by about 45% from vanilla, and this + * optimization synergizes well with the breadth-first search implemented in + * RedstoneWireTurbo. + * Note: RedstoneWireTurbo contains a faster version of this code. + * Note: Made this public so that RedstoneWireTurbo can access it. + */ + public IBlockData calculateCurrentChanges(World worldIn, BlockPosition pos1, BlockPosition pos2, IBlockData state) { + IBlockData iblockstate = state; + int i = state.get(POWER).intValue(); + int j = 0; + j = this.getPower(j, worldIn.getType(pos2)); + this.setCanProvidePower(false); + int k = worldIn.isBlockIndirectlyGettingPowered(pos1); + this.setCanProvidePower(true); + + if (!worldIn.paperConfig.useEigencraftRedstone) { + // This code is totally redundant to if statements just below the loop. + if (k > 0 && k > j - 1) { + j = k; + } + } + + int l = 0; + + // The variable 'k' holds the maximum redstone power value of any adjacent blocks. + // If 'k' has the highest level of all neighbors, then the power level of this + // redstone wire will be set to 'k'. If 'k' is already 15, then nothing inside the + // following loop can affect the power level of the wire. Therefore, the loop is + // skipped if k is already 15. + if (!worldIn.paperConfig.useEigencraftRedstone || k < 15) { + for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) { + BlockPosition blockpos = pos1.shift(enumfacing); + boolean flag = blockpos.getX() != pos2.getX() || blockpos.getZ() != pos2.getZ(); + + if (flag) { + l = this.getPower(l, worldIn.getType(blockpos)); + } + + if (worldIn.getType(blockpos).isOccluding(worldIn, blockpos) && !worldIn.getType(pos1.up()).isOccluding(worldIn, pos1)) { + if (flag && pos1.getY() >= pos2.getY()) { + l = this.getPower(l, worldIn.getType(blockpos.up())); + } + } else if (!worldIn.getType(blockpos).isOccluding(worldIn, blockpos) && flag && pos1.getY() <= pos2.getY()) { + l = this.getPower(l, worldIn.getType(blockpos.down())); + } + } + } + + if (!worldIn.paperConfig.useEigencraftRedstone) { + // The old code would decrement the wire value only by 1 at a time. + if (l > j) { + j = l - 1; + } else if (j > 0) { + --j; + } else { + j = 0; + } + + if (k > j - 1) { + j = k; + } + } else { + // The new code sets this RedstoneWire block's power level to the highest neighbor + // minus 1. This usually results in wire power levels dropping by 2 at a time. + // This optimization alone has no impact on update order, only the number of updates. + j = l - 1; + + // If 'l' turns out to be zero, then j will be set to -1, but then since 'k' will + // always be in the range of 0 to 15, the following if will correct that. + if (k > j) j = k; + } + + if (i != j) { + state = state.set(POWER, Integer.valueOf(j)); + + if (worldIn.getType(pos1) == iblockstate) { + worldIn.setTypeAndData(pos1, state, 2); + } + + if (!worldIn.paperConfig.useEigencraftRedstone) { + // The new search algorithm keeps track of blocks needing updates in its own data structures, + // so only add anything to blocksNeedingUpdate if we're using the vanilla update algorithm. + this.getBlocksNeedingUpdate().add(pos1); + + for (EnumDirection enumfacing1 : EnumDirection.values()) { + this.getBlocksNeedingUpdate().add(pos1.shift(enumfacing1)); + } + } + } + + return state; + } + // Paper end private IBlockData a(World world, BlockPosition blockposition, IBlockData iblockdata) { iblockdata = this.b(world, blockposition, iblockdata); List list = Lists.newArrayList(this.i); @@ -255,7 +368,7 @@ public class BlockRedstoneWire extends Block { @Override public void onPlace(IBlockData iblockdata, World world, BlockPosition blockposition, IBlockData iblockdata1, boolean flag) { if (iblockdata1.getBlock() != iblockdata.getBlock() && !world.isClientSide) { - this.a(world, blockposition, iblockdata); + this.updateSurroundingRedstone(world, blockposition, iblockdata, null); // Paper - Optimize redstone Iterator iterator = EnumDirection.EnumDirectionLimit.VERTICAL.iterator(); EnumDirection enumdirection; @@ -302,7 +415,7 @@ public class BlockRedstoneWire extends Block { world.applyPhysics(blockposition.shift(enumdirection), this); } - this.a(world, blockposition, iblockdata); + this.updateSurroundingRedstone(world, blockposition, iblockdata, null); // Paper - Optimize redstone Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator(); EnumDirection enumdirection1; @@ -343,7 +456,7 @@ public class BlockRedstoneWire extends Block { public void doPhysics(IBlockData iblockdata, World world, BlockPosition blockposition, Block block, BlockPosition blockposition1, boolean flag) { if (!world.isClientSide) { if (iblockdata.canPlace(world, blockposition)) { - this.a(world, blockposition, iblockdata); + this.updateSurroundingRedstone(world, blockposition, iblockdata, blockposition1); // Paper - Optimize redstone } else { c(iblockdata, world, blockposition); world.a(blockposition, false); diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 75c539d72d..380833ee54 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -543,6 +543,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { } + public void neighborChanged(BlockPosition pos, Block blockIn, BlockPosition fromPos) { a(pos, blockIn, fromPos); } // Paper - OBFHELPER public void a(BlockPosition blockposition, Block block, BlockPosition blockposition1) { if (!this.isClientSide) { IBlockData iblockdata = this.getType(blockposition); @@ -1292,6 +1293,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { return this.getBlockFacePower(blockposition.down(), EnumDirection.DOWN) > 0 ? true : (this.getBlockFacePower(blockposition.up(), EnumDirection.UP) > 0 ? true : (this.getBlockFacePower(blockposition.north(), EnumDirection.NORTH) > 0 ? true : (this.getBlockFacePower(blockposition.south(), EnumDirection.SOUTH) > 0 ? true : (this.getBlockFacePower(blockposition.west(), EnumDirection.WEST) > 0 ? true : this.getBlockFacePower(blockposition.east(), EnumDirection.EAST) > 0)))); } + public int isBlockIndirectlyGettingPowered(BlockPosition pos) { return this.q(pos); } // Paper - OBFHELPER public int q(BlockPosition blockposition) { int i = 0; EnumDirection[] aenumdirection = World.a; -- 2.24.1