package block import ( "slices" "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/block/model" "github.com/df-mc/dragonfly/server/event" "github.com/df-mc/dragonfly/server/world" ) // wireNetwork implements a minimally-invasive bolt-on accelerator that performs a breadth-first search through redstone // wires in order to more efficiently and compute new redstone wire power levels and determine the order in which other // blocks should be updated. This implementation is heavily based off of RedstoneWireTurbo and MCHPRS. type wireNetwork struct { nodes []*wireNode nodeCache map[cube.Pos]*wireNode updateQueue [3][]*wireNode currentWalkLayer uint32 } // wireNode is a data structure to keep track of redstone wires and neighbours that will receive updates. type wireNode struct { visited bool pos cube.Pos block world.Block source cube.Pos neighbours []*wireNode oriented bool xBias int32 zBias int32 layer uint32 } const ( wireHeadingNorth = 0 wireHeadingEast = 1 wireHeadingSouth = 2 wireHeadingWest = 3 ) // updateStrongRedstone sets off the breadth-first walk through all redstone wires connected to the initial position // triggered. This is the main entry point for the redstone update algorithm. func updateStrongRedstone(pos cube.Pos, tx *world.Tx) { n := &wireNetwork{ nodeCache: make(map[cube.Pos]*wireNode), } root := &wireNode{ block: tx.Block(pos), pos: pos, visited: true, } n.nodeCache[pos] = root n.nodes = append(n.nodes, root) n.propagateChanges(tx, root, 0) n.breadthFirstWalk(tx) } // updateAroundRedstone updates redstone components around the given center position. It will also ignore any faces // provided within the ignoredFaces parameter. This implementation is based off of RedstoneCircuit and Java 1.19. func updateAroundRedstone(centre cube.Pos, tx *world.Tx, ignoredFaces ...cube.Face) { // Order matches Java 1.19's RedstoneCircuit traversal. for _, face := range []cube.Face{ cube.FaceWest, cube.FaceEast, cube.FaceDown, cube.FaceUp, cube.FaceNorth, cube.FaceSouth, } { if slices.Contains(ignoredFaces, face) { continue } pos := centre.Side(face) updateRedstoneFrom(pos, centre, tx) updateRedstoneFrom(pos.Side(cube.FaceUp), centre, tx) updateRedstoneFrom(pos.Side(cube.FaceDown), centre, tx) updateReceiversAroundPoweredBlock(pos, tx, face.Opposite()) } } // updateReceiversAroundPoweredBlock updates redstone receivers directly adjacent to an indirectly powered solid block. // This keeps mechanisms behind the powered block in sync without walking corner positions around the original update. // For example, this covers the following vertical path: torch -> stone -> note block. func updateReceiversAroundPoweredBlock(pos cube.Pos, tx *world.Tx, ignoredFaces ...cube.Face) { if _, ok := tx.Block(pos).Model().(model.Solid); !ok { return } for _, face := range cube.Faces() { if slices.Contains(ignoredFaces, face) || tx.RedstonePower(pos, face, true) == 0 { continue } updateRedstoneFrom(pos.Side(face), pos, tx) } } // updateRedstone dispatches a cancellable redstone update to the block at pos, if it handles redstone updates. // Prefer updateRedstone over calling RedstoneUpdate directly so HandleRedstoneUpdate gets a chance to observe // and optionally cancel the update. Direct RedstoneUpdate calls are reserved for internal state initialisation // (e.g. seeding a freshly placed block) where handler cancellation isn't appropriate. func updateRedstone(pos cube.Pos, tx *world.Tx) { updateRedstoneFrom(pos, pos, tx) } // updateRedstoneFrom dispatches a cancellable redstone update and records the position that caused it. func updateRedstoneFrom(pos, source cube.Pos, tx *world.Tx) { r, ok := tx.Block(pos).(world.RedstoneUpdater) if !ok { return } if redstoneUpdateCancelled(pos, tx) { return } tx.Redstone().WithUpdateSource(source, func() { r.RedstoneUpdate(pos, tx) }) } // redstoneUpdateCancelled checks if the redstone update has been cancelled by the HandleRedstoneUpdate handler. func redstoneUpdateCancelled(pos cube.Pos, tx *world.Tx) bool { ctx := event.C(tx) tx.World().Handler().HandleRedstoneUpdate(ctx, pos) return ctx.Cancelled() } // updateDirectionalRedstone updates redstone components through the given face. This implementation is based off of // RedstoneCircuit and Java 1.19. func updateDirectionalRedstone(pos cube.Pos, tx *world.Tx, face cube.Face) { updateAroundRedstone(pos, tx) updateAroundRedstone(pos.Side(face), tx, face.Opposite()) } // identifyNeighbours identifies the neighbouring positions of a given node, determines their types, and links them into // the graph. After that, based on what nodes in the graph have been visited, the neighbours are reordered left-to-right // relative to the direction of information flow. func (n *wireNetwork) identifyNeighbours(tx *world.Tx, node *wireNode) { var neighboursVisited [24]bool var neighbourNodes [24]*wireNode for i, offset := range redstoneNeighbourOffsets { neighbourPos := node.pos.Add(offset) neighbour, ok := n.nodeCache[neighbourPos] if !ok { neighbour = &wireNode{ pos: neighbourPos, block: tx.Block(neighbourPos), } n.nodeCache[neighbourPos] = neighbour n.nodes = append(n.nodes, neighbour) } neighbourNodes[i] = neighbour neighboursVisited[i] = neighbour.visited } fromWest := neighboursVisited[0] || neighboursVisited[7] || neighboursVisited[8] fromEast := neighboursVisited[1] || neighboursVisited[12] || neighboursVisited[13] fromNorth := neighboursVisited[4] || neighboursVisited[17] || neighboursVisited[20] fromSouth := neighboursVisited[5] || neighboursVisited[18] || neighboursVisited[21] var cX, cZ int32 if fromWest { cX++ } if fromEast { cX-- } if fromNorth { cZ++ } if fromSouth { cZ-- } var heading uint32 if cX == 0 && cZ == 0 { heading = computeRedstoneHeading(node.xBias, node.zBias) for _, neighbourNode := range neighbourNodes { neighbourNode.xBias = node.xBias neighbourNode.zBias = node.zBias } } else { if cX != 0 && cZ != 0 { if node.xBias != 0 { cZ = 0 } if node.zBias != 0 { cX = 0 } } heading = computeRedstoneHeading(cX, cZ) for _, neighbourNode := range neighbourNodes { neighbourNode.xBias = cX neighbourNode.zBias = cZ } } n.orientNeighbours(neighbourNodes, node, heading) } // redstoneNeighbourOffsets lists the 24 positions visited around a redstone wire node: the 6 immediate neighbours // followed by the unique neighbours-of-neighbours, in the order west, east, down, up, north, south. The fixed // indices here are referenced directly by identifyNeighbours and the redstoneReordering tables below. var redstoneNeighbourOffsets = [...]cube.Pos{ // Immediate neighbours, in the order of west, east, down, up, north, and south. {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}, // Neighbours of neighbours, in the same order, except that duplicates are omitted. {-2, 0, 0}, {-1, -1, 0}, {-1, 1, 0}, {-1, 0, -1}, {-1, 0, 1}, {2, 0, 0}, {1, -1, 0}, {1, 1, 0}, {1, 0, -1}, {1, 0, 1}, {0, -2, 0}, {0, -1, -1}, {0, -1, 1}, {0, 2, 0}, {0, 1, -1}, {0, 1, 1}, {0, 0, -2}, {0, 0, 2}, } // redstoneReordering contains lookup tables that completely remap neighbour positions into a left-to-right ordering, // based on the cardinal direction that is determined to be forward. var redstoneReordering = [...][24]uint32{ {2, 3, 16, 19, 0, 4, 1, 5, 7, 8, 17, 20, 12, 13, 18, 21, 6, 9, 22, 14, 11, 10, 23, 15}, {2, 3, 16, 19, 4, 1, 5, 0, 17, 20, 12, 13, 18, 21, 7, 8, 22, 14, 11, 15, 23, 9, 6, 10}, {2, 3, 16, 19, 1, 5, 0, 4, 12, 13, 18, 21, 7, 8, 17, 20, 11, 15, 23, 10, 6, 14, 22, 9}, {2, 3, 16, 19, 5, 0, 4, 1, 18, 21, 7, 8, 17, 20, 12, 13, 23, 10, 6, 9, 22, 15, 11, 14}, } // orientNeighbours reorders the neighbours of a node based on the direction that is determined to be forward. func (n *wireNetwork) orientNeighbours(src [24]*wireNode, dst *wireNode, heading uint32) { dst.oriented = true dst.neighbours = make([]*wireNode, 0, 24) for _, i := range redstoneReordering[heading] { dst.neighbours = append(dst.neighbours, src[i]) } } // propagateChanges propagates changes for any redstone wire in layer N, informing the neighbours to recompute their // states in layers N + 1 and N + 2. func (n *wireNetwork) propagateChanges(tx *world.Tx, node *wireNode, layer uint32) { if !node.oriented { n.identifyNeighbours(tx, node) } layerOne := layer + 1 for _, neighbour := range node.neighbours { if layerOne > neighbour.layer { neighbour.layer = layerOne neighbour.source = node.pos n.updateQueue[1] = append(n.updateQueue[1], neighbour) } } layerTwo := layer + 2 for _, neighbour := range node.neighbours[:4] { if layerTwo > neighbour.layer { neighbour.layer = layerTwo neighbour.source = node.pos n.updateQueue[2] = append(n.updateQueue[2], neighbour) } } } // breadthFirstWalk performs a breadth-first (layer by layer) traversal through redstone wires, propagating value // changes to neighbours in the order that they are visited. func (n *wireNetwork) breadthFirstWalk(tx *world.Tx) { n.shiftQueue() n.currentWalkLayer = 1 for len(n.updateQueue[0]) > 0 || len(n.updateQueue[1]) > 0 { for _, node := range n.updateQueue[0] { if _, ok := node.block.(RedstoneWire); ok { n.updateNode(tx, node, n.currentWalkLayer) continue } updateRedstoneFrom(node.pos, node.source, tx) } n.shiftQueue() n.currentWalkLayer++ } n.currentWalkLayer = 0 } // shiftQueue shifts the update queue, moving all nodes from the current layer to the next layer. The last queue is then // simply invalidated. func (n *wireNetwork) shiftQueue() { n.updateQueue[0] = n.updateQueue[1] n.updateQueue[1] = n.updateQueue[2] n.updateQueue[2] = nil } // updateNode processes a node which has had neighbouring redstone wires that have experienced value changes. func (n *wireNetwork) updateNode(tx *world.Tx, node *wireNode, layer uint32) { node.visited = true if redstoneUpdateCancelled(node.pos, tx) { return } newWire, changed := n.calculateCurrentChanges(tx, node) if !changed { return } node.block = newWire n.propagateChanges(tx, node, layer) } // calculateCurrentChanges computes redstone wire power levels from neighboring blocks. Modifications cut the number of // power level changes by about 45% from vanilla, and also synergies well with the breadth-first search implementation. // It returns the new redstone wire block and a boolean indicating whether the power level changed. func (n *wireNetwork) calculateCurrentChanges(tx *world.Tx, node *wireNode) (RedstoneWire, bool) { wire := node.block.(RedstoneWire) i := wire.Power if !node.oriented { n.identifyNeighbours(tx, node) } j := calculateRedstoneWirePower(node.pos, tx, func(pos cube.Pos) world.Block { if cached, ok := n.nodeCache[pos]; ok { return cached.block } return tx.Block(pos) }) if i == j { return wire, false } wire.Power = j tx.SetBlock(node.pos, wire, &world.SetOpts{DisableBlockUpdates: true}) return wire, true } // maxRedstoneWirePower returns the greater of strength and the power level of b if it is redstone wire. func maxRedstoneWirePower(b world.Block, strength int) int { if wire, ok := b.(RedstoneWire); ok { return max(wire.Power, strength) } return strength } // computeRedstoneHeading computes the cardinal direction that is "forward" given which redstone wires have been visited // and which have not around the position currently being processed. func computeRedstoneHeading(rX, rZ int32) uint32 { code := (rX + 1) + 3*(rZ+1) switch code { case 0, 1: return wireHeadingNorth case 2, 5: return wireHeadingEast case 3, 4: return wireHeadingWest case 6, 7, 8: return wireHeadingSouth } panic("should never happen") }