Pumpkin/pumpkin/src/block/blocks/redstone/turbo.rs
Alexander Medvedev 932512b5d6 lets try fix CI
2025-10-02 16:54:48 +02:00

422 lines
14 KiB
Rust

//! The implementation of "Redstone Wire Turbo" was largely based on
//! the accelerator created by theosib. For more information, see:
//! <https://bugs.mojang.com/browse/MC-81098>.
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<Vec<NodeId>>,
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<UpdateNode>,
node_cache: FxHashMap<BlockPos, NodeId>,
update_queue: Vec<Vec<NodeId>>,
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<World>, 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<World>) {
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<World>, 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<World>,
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
}
}
}