//! The implementation of "Redstone Wire Turbo" was largely based on //! the accelerator created by theosib. For more information, see: //! . use pumpkin_data::{ Block, BlockDirection, BlockState, block_properties::{BlockProperties, EnumVariants, Integer0To15, RedstoneWireLikeProperties}, }; use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; use pumpkin_world::world::BlockFlags; use rustc_hash::FxHashMap; use std::sync::Arc; use crate::world::World; use super::get_redstone_power_no_dust; type RedstoneWireProps = RedstoneWireLikeProperties; fn unwrap_wire(state: &BlockState) -> RedstoneWireProps { RedstoneWireProps::from_state_id(state.id, &Block::REDSTONE_WIRE) } #[derive(Clone, Copy)] struct NodeId { index: usize, } struct UpdateNode { pos: BlockPos, /// The cached state of the block state: &'static BlockState, /// This will only be `Some` when all the neighbors are identified. neighbors: Option>, visited: bool, xbias: i32, zbias: i32, layer: u32, } impl UpdateNode { async fn new(world: &World, pos: BlockPos) -> Self { Self { pos, state: world.get_block_state(&pos).await, visited: false, neighbors: None, xbias: 0, zbias: 0, layer: 0, } } } pub(super) struct RedstoneWireTurbo { nodes: Vec, node_cache: FxHashMap, update_queue: Vec>, current_walk_layer: u32, } impl RedstoneWireTurbo { // Internal numbering for cardinal directions const NORTH: usize = 0; const EAST: usize = 1; const SOUTH: usize = 2; const WEST: usize = 3; pub fn new() -> Self { Self { nodes: Vec::new(), node_cache: FxHashMap::default(), update_queue: vec![vec![], vec![], vec![]], current_walk_layer: 0, } } fn get_node(&self, node_id: NodeId) -> &UpdateNode { &self.nodes[node_id.index] } fn compute_all_neighbors(pos: BlockPos) -> [BlockPos; 24] { let Vector3 { x, y, z } = pos.0; [ BlockPos::new(x - 1, y, z), BlockPos::new(x + 1, y, z), BlockPos::new(x, y - 1, z), BlockPos::new(x, y + 1, z), BlockPos::new(x, y, z - 1), BlockPos::new(x, y, z + 1), // Neighbors of neighbors, in the same order, // except that duplicates are not included BlockPos::new(x - 2, y, z), BlockPos::new(x - 1, y - 1, z), BlockPos::new(x - 1, y + 1, z), BlockPos::new(x - 1, y, z - 1), BlockPos::new(x - 1, y, z + 1), BlockPos::new(x + 2, y, z), BlockPos::new(x + 1, y - 1, z), BlockPos::new(x + 1, y + 1, z), BlockPos::new(x + 1, y, z - 1), BlockPos::new(x + 1, y, z + 1), BlockPos::new(x, y - 2, z), BlockPos::new(x, y - 1, z - 1), BlockPos::new(x, y - 1, z + 1), BlockPos::new(x, y + 2, z), BlockPos::new(x, y + 1, z - 1), BlockPos::new(x, y + 1, z + 1), BlockPos::new(x, y, z - 2), BlockPos::new(x, y, z + 2), ] } fn compute_heading(rx: i32, rz: i32) -> usize { let code = (rx + 1) + 3 * (rz + 1); match code { 0 | 1 => Self::NORTH, 2 | 5 => Self::EAST, 3 | 4 => Self::WEST, 6..=8 => Self::SOUTH, _ => unreachable!(), } } // const UPDATE_REDSTONE: [bool; 24] = [ // 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 // ]; async fn identify_neighbors(&mut self, world: &World, upd1: NodeId) { let pos = self.nodes[upd1.index].pos; let neighbors = Self::compute_all_neighbors(pos); let mut neighbors_visited = Vec::with_capacity(24); let mut neighbor_nodes = Vec::with_capacity(24); for neighbor_pos in &neighbors[0..24] { let neighbor = if self.node_cache.contains_key(neighbor_pos) { self.node_cache[neighbor_pos] } else { let node_id = NodeId { index: self.nodes.len(), }; self.node_cache.insert(*neighbor_pos, node_id); self.nodes.push(UpdateNode::new(world, *neighbor_pos).await); node_id }; let node = &self.nodes[neighbor.index]; // if let Block::RedstoneWire { .. } = node.state { // if RedstoneWireTurbo::UPDATE_REDSTONE[i] { neighbor_nodes.push(neighbor); neighbors_visited.push(node.visited); // continue; // } // } // neighbor_nodes.push(None); // neighbors_visited.push(false); } let from_west = neighbors_visited[0] || neighbors_visited[7] || neighbors_visited[8]; let from_east = neighbors_visited[1] || neighbors_visited[12] || neighbors_visited[13]; let from_north = neighbors_visited[4] || neighbors_visited[17] || neighbors_visited[20]; let from_south = neighbors_visited[5] || neighbors_visited[18] || neighbors_visited[21]; let mut cx = 0; let mut cz = 0; if from_west { cx += 1; } if from_east { cx -= 1; } if from_north { cz += 1; } if from_south { cz -= 1; } let UpdateNode { xbias, zbias, .. } = &self.nodes[upd1.index]; let xbias = *xbias; let zbias = *zbias; let heading; if cx == 0 && cz == 0 { heading = Self::compute_heading(xbias, zbias); for node_id in &neighbor_nodes { // if let Some(node_id) = node_id { let nn = &mut self.nodes[node_id.index]; nn.xbias = xbias; nn.zbias = zbias; // } } } else { if cx != 0 && cz != 0 { if xbias != 0 { cz = 0; } if zbias != 0 { cx = 0; } } heading = Self::compute_heading(cx, cz); for node_id in &neighbor_nodes { // if let Some(node_id) = node_id { let nn = &mut self.nodes[node_id.index]; nn.xbias = cx; nn.zbias = cz; // } } } self.orient_neighbors(&neighbor_nodes, upd1, heading); } const REORDERING: [[usize; 24]; 4] = [ [ 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, ], ]; fn orient_neighbors(&mut self, src: &[NodeId], dst_id: NodeId, heading: usize) { let dst = &mut self.nodes[dst_id.index]; let mut neighbors = Vec::with_capacity(24); let re = Self::REORDERING[heading]; for i in &re { neighbors.push(src[*i]); } dst.neighbors = Some(neighbors); } /// This is the start of a great adventure pub async fn update_surrounding_neighbors(world: &Arc, pos: BlockPos) { let mut turbo = Self::new(); let mut root_node = UpdateNode::new(world, pos).await; root_node.visited = true; let node_id = NodeId { index: 0 }; turbo.node_cache.insert(pos, node_id); turbo.nodes.push(root_node); turbo.propagate_changes(world, node_id, 0).await; turbo.breadth_first_walk(world).await; } async fn propagate_changes(&mut self, world: &World, upd1: NodeId, layer: u32) { if self.nodes[upd1.index].neighbors.is_none() { self.identify_neighbors(world, upd1).await; } let neighbors: [NodeId; 24] = self.nodes[upd1.index].neighbors.as_ref().unwrap()[0..24] .try_into() .unwrap(); let layer1 = layer + 1; for neighbor_id in neighbors { let neighbor = &mut self.nodes[neighbor_id.index]; if layer1 > neighbor.layer { neighbor.layer = layer1; self.update_queue[1].push(neighbor_id); } } let layer2 = layer + 2; for neighbor_id in &neighbors[0..4] { let neighbor = &mut self.nodes[neighbor_id.index]; if layer2 > neighbor.layer { neighbor.layer = layer2; self.update_queue[2].push(*neighbor_id); } } } async fn breadth_first_walk(&mut self, world: &Arc) { self.shift_queue(); self.current_walk_layer = 1; while !self.update_queue[0].is_empty() || !self.update_queue[1].is_empty() { for node_id in self.update_queue[0].clone() { let block = Block::from_state_id(self.nodes[node_id.index].state.id); if block == &Block::REDSTONE_WIRE { self.update_node(world, node_id, self.current_walk_layer) .await; } else { // This only works because updating any other block than a wire will // never change the state of the block. If that changes in the future, // the cached state will need to be updated world .update_neighbor(&self.nodes[node_id.index].pos, block) .await; } } self.shift_queue(); self.current_walk_layer += 1; } self.current_walk_layer = 0; } fn shift_queue(&mut self) { let mut t = self.update_queue.remove(0); t.clear(); self.update_queue.push(t); } async fn update_node(&mut self, world: &Arc, upd1: NodeId, layer: u32) { let old_wire = { let node = &mut self.nodes[upd1.index]; node.visited = true; unwrap_wire(node.state) }; let new_wire = self.calculate_current_changes(world, upd1).await; if old_wire.power != new_wire.power { let node = &mut self.nodes[upd1.index]; let mut wire = unwrap_wire(node.state); wire.power = new_wire.power; node.state = BlockState::from_id(wire.to_state_id(&Block::REDSTONE_WIRE)); self.propagate_changes(world, upd1, layer).await; } } const RS_NEIGHBORS: [usize; 4] = [4, 5, 6, 7]; const RS_NEIGHBORS_UP: [usize; 4] = [9, 11, 13, 15]; const RS_NEIGHBORS_DN: [usize; 4] = [8, 10, 12, 14]; async fn calculate_current_changes( &mut self, world: &Arc, upd: NodeId, ) -> RedstoneWireProps { let mut wire = unwrap_wire(self.nodes[upd.index].state); let i = wire.power; let mut block_power = 0; if self.nodes[upd.index].neighbors.is_none() { self.identify_neighbors(world, upd).await; } let pos = self.nodes[upd.index].pos; let mut wire_power = 0; for side in BlockDirection::all() { let neighbor_pos = pos.offset(side.to_offset()); let neighbor = &self.nodes[self.node_cache[&neighbor_pos].index].state; wire_power = wire_power.max( get_redstone_power_no_dust( Block::from_state_id(neighbor.id), neighbor, world, neighbor_pos, side, ) .await, ); } if wire_power < 15 { let neighbors = self.nodes[upd.index].neighbors.as_ref().unwrap(); let center_up = self.nodes[neighbors[1].index].state; for m in 0..4 { let n = Self::RS_NEIGHBORS[m]; let neighbor_id = neighbors[n]; let neighbor = self.get_node(neighbor_id).state; block_power = self.get_max_current_strength(neighbor_id, block_power); if !neighbor.is_solid_block() { let neighbor_down = neighbors[Self::RS_NEIGHBORS_DN[m]]; block_power = self.get_max_current_strength(neighbor_down, block_power); } else if !center_up.is_solid_block() && neighbor.is_solid_block() { let neighbor_up = neighbors[Self::RS_NEIGHBORS_UP[m]]; block_power = self.get_max_current_strength(neighbor_up, block_power); } } } let mut j = block_power.saturating_sub(1); if wire_power > j { j = wire_power; } if i.to_index() as u8 != j { wire.power = Integer0To15::from_index(j.into()); world .set_block_state( &pos, wire.to_state_id(&Block::REDSTONE_WIRE), BlockFlags::empty(), ) .await; } wire } fn get_max_current_strength(&self, upd: NodeId, strength: u8) -> u8 { let node = &self.nodes[upd.index]; let block = Block::from_state_id(node.state.id); if block == &Block::REDSTONE_WIRE { (unwrap_wire(node.state).power.to_index() as u8).max(strength) } else { strength } } }