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.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 oldState.dropNaturally(worldIn, pos, 0); 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(); 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(); 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; } }