diff --git a/Cargo.lock b/Cargo.lock index a5617df8..bc90c5cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -728,6 +728,12 @@ dependencies = [ "log", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.24" @@ -1351,6 +1357,7 @@ dependencies = [ "mchprs_world", "md5", "mysql", + "petgraph", "rand", "rayon", "redpiler_graph", @@ -1720,6 +1727,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.9" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 586fde1a..2dc901dd 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -47,6 +47,7 @@ itertools = "0.10" impls = "1" bincode = "1.3" smallvec = "1.9.0" +petgraph = "0.6" redpiler_graph = { path = "../redpiler_graph" } mchprs_save_data = { path = "../save_data" } mchprs_blocks = { path = "../blocks" } diff --git a/crates/core/src/blocks/mod.rs b/crates/core/src/blocks/mod.rs index 805c8d8c..c182f4b3 100644 --- a/crates/core/src/blocks/mod.rs +++ b/crates/core/src/blocks/mod.rs @@ -376,12 +376,26 @@ impl Block { Item::StainedGlass { color } => Block::StainedGlass { color }, Item::SmoothStoneSlab {} => Block::SmoothStoneSlab {}, Item::QuartzSlab {} => Block::QuartzSlab {}, - Item::IronTrapdoor {} => { - match context.block_face { - BlockFace::Bottom => Block::IronTrapdoor { facing: context.player.get_direction().opposite(), half: TrapdoorHalf::Top, powered: false }, - BlockFace::Top => Block::IronTrapdoor { facing: context.player.get_direction().opposite(), half: TrapdoorHalf::Bottom, powered: false }, - _ => Block::IronTrapdoor { facing: context.block_face.to_direction(), half: if context.cursor_y > 0.5 {TrapdoorHalf::Top} else {TrapdoorHalf::Bottom} , powered: false } - } + Item::IronTrapdoor {} => match context.block_face { + BlockFace::Bottom => Block::IronTrapdoor { + facing: context.player.get_direction().opposite(), + half: TrapdoorHalf::Top, + powered: false, + }, + BlockFace::Top => Block::IronTrapdoor { + facing: context.player.get_direction().opposite(), + half: TrapdoorHalf::Bottom, + powered: false, + }, + _ => Block::IronTrapdoor { + facing: context.block_face.to_direction(), + half: if context.cursor_y > 0.5 { + TrapdoorHalf::Top + } else { + TrapdoorHalf::Bottom + }, + powered: false, + }, }, _ => Block::Air {}, }; diff --git a/crates/core/src/plot/commands.rs b/crates/core/src/plot/commands.rs index bcf7ae6a..8b16c9ae 100644 --- a/crates/core/src/plot/commands.rs +++ b/crates/core/src/plot/commands.rs @@ -186,6 +186,21 @@ impl Plot { debug!("Compile took {:?}", start_time.elapsed()); } + "inspect" | "i" => { + let player = &self.players[player]; + let pos = worldedit::ray_trace_block( + &self.world, + player.pos, + player.pitch as f64, + player.yaw as f64, + 10.0, + ); + let Some(pos) = pos else { + player.send_error_message("Trace failed"); + return; + }; + self.redpiler.inspect(pos); + } "reset" | "r" => { self.reset_redpiler(); } diff --git a/crates/core/src/plot/mod.rs b/crates/core/src/plot/mod.rs index a6166b3d..c029c697 100644 --- a/crates/core/src/plot/mod.rs +++ b/crates/core/src/plot/mod.rs @@ -828,7 +828,8 @@ impl Plot { let batch_size = batch_size.min(match self.last_nspt { Some(Duration::ZERO) | None => 5, Some(last_nspt) => { - let ticks_fit = WORLD_SEND_RATE.as_nanos() / last_nspt.as_nanos(); + let ticks_fit = + WORLD_SEND_RATE.as_nanos() / last_nspt.as_nanos(); if ticks_fit == 0 { // A tick previously took longer than the world send rate. // Run at least one just so we're not stuck doing nothing @@ -846,7 +847,7 @@ impl Plot { } self.lag_time -= dur_per_tick * batch_size as u32; self.last_nspt = - Some(self.last_update_time.elapsed() / (batch_size as u32)); + Some(self.last_update_time.elapsed() / (batch_size as u32)); } } } @@ -867,7 +868,7 @@ impl Plot { } else { ticks_fit } - }, + } } as u64; if batch_size != 0 { let batch_size = batch_size.min(50000) as u32; diff --git a/crates/core/src/plot/worldedit/mod.rs b/crates/core/src/plot/worldedit/mod.rs index 3d2d3b50..841a23e9 100644 --- a/crates/core/src/plot/worldedit/mod.rs +++ b/crates/core/src/plot/worldedit/mod.rs @@ -921,7 +921,7 @@ impl WorldEditOperation { } } -fn ray_trace_block( +pub fn ray_trace_block( world: &impl World, mut pos: PlayerPos, start_pitch: f64, diff --git a/crates/core/src/redpiler/backend/direct.rs b/crates/core/src/redpiler/backend/direct.rs index 1b11faa2..3b2fbfb7 100644 --- a/crates/core/src/redpiler/backend/direct.rs +++ b/crates/core/src/redpiler/backend/direct.rs @@ -1,19 +1,30 @@ //! The direct backend does not do code generation and operates on the `CompileNode` graph directly use super::JITBackend; -use crate::blocks::{Block, ComparatorMode, RedstoneRepeater}; +use crate::blocks::{Block, ComparatorMode}; use crate::plot::PlotWorld; -use crate::redpiler::{block_powered_mut, bool_to_ss, CompileNode, LinkType}; +use crate::redpiler::compile_graph::{CompileGraph, LinkType, NodeIdx}; +use crate::redpiler::{block_powered_mut, bool_to_ss}; use crate::world::World; -use log::warn; +use log::{debug, trace, warn}; use mchprs_blocks::block_entities::BlockEntity; use mchprs_blocks::BlockPos; use mchprs_world::{TickEntry, TickPriority}; use nodes::{NodeId, Nodes}; +use petgraph::visit::EdgeRef; +use petgraph::Direction; use smallvec::SmallVec; use std::collections::{HashMap, VecDeque}; use std::{fmt, mem}; +#[derive(Debug, Default)] +struct FinalGraphStats { + update_link_count: usize, + side_link_count: usize, + default_link_count: usize, + nodes_bytes: usize, +} + mod nodes { use super::Node; use std::ops::{Index, IndexMut}; @@ -104,23 +115,6 @@ enum NodeType { } impl NodeType { - fn new(block: Block) -> NodeType { - match block { - Block::RedstoneRepeater { repeater } => NodeType::Repeater(repeater.delay), - Block::RedstoneComparator { comparator } => NodeType::Comparator(comparator.mode), - Block::RedstoneTorch { .. } | Block::RedstoneWallTorch { .. } => NodeType::Torch, - Block::RedstoneWire { .. } => NodeType::Wire, - Block::StoneButton { .. } => NodeType::Button, - Block::RedstoneLamp { .. } => NodeType::Lamp, - Block::Lever { .. } => NodeType::Lever, - Block::StonePressurePlate { .. } => NodeType::PressurePlate, - Block::IronTrapdoor { .. } => NodeType::Trapdoor, - Block::RedstoneBlock { .. } => NodeType::Constant, - block if block.has_comparator_override() => NodeType::Constant, - _ => panic!("Cannot determine node type for {:?}", block), - } - } - fn is_io_block(self) -> bool { matches!( self, @@ -152,56 +146,79 @@ pub struct Node { } impl Node { - fn from_compile_node(node: CompileNode, nodes_len: usize) -> Self { - let output_power = node.output_power(); - let powered = node.powered(); + fn from_compile_node( + graph: &CompileGraph, + node_idx: NodeIdx, + nodes_len: usize, + nodes_map: &HashMap, + stats: &mut FinalGraphStats, + ) -> Self { + let node = &graph[node_idx]; + let mut default_inputs = SmallVec::new(); let mut side_inputs = SmallVec::new(); - node.inputs - .into_iter() - .map(|link| { - assert!(link.end < nodes_len); - ( - link.ty, - DirectLink { - weight: link.weight, - to: unsafe { - // Safety: bounds checked - NodeId::from_index(link.end) - }, - }, - ) - }) - .for_each(|(link_type, link)| match link_type { + for edge in graph.edges_directed(node_idx, Direction::Incoming) { + let idx = nodes_map[&edge.source()]; + assert!(idx < nodes_len); + let idx = unsafe { + // Safety: bounds checked + NodeId::from_index(idx) + }; + let link = DirectLink { + to: idx, + weight: edge.weight().ss, + }; + match edge.weight().ty { LinkType::Default => default_inputs.push(link), LinkType::Side => side_inputs.push(link), - }); - let updates = node - .updates - .into_iter() - .map(|idx| { - assert!(idx < nodes_len); - // Safety: bounds checked - unsafe { NodeId::from_index(idx) } - }) - .collect(); - let ty = match node.state { - Block::RedstoneRepeater { - repeater: RedstoneRepeater { delay, .. }, - } if side_inputs.is_empty() => NodeType::SimpleRepeater(delay), - state => NodeType::new(state), + } + } + stats.default_link_count += default_inputs.len(); + stats.side_link_count += side_inputs.len(); + + use crate::redpiler::compile_graph::NodeType as CNodeType; + let updates: SmallVec<[NodeId; 2]> = if node.ty != CNodeType::Constant { + graph + .neighbors_directed(node_idx, Direction::Outgoing) + .map(|idx| unsafe { + let idx = nodes_map[&idx]; + assert!(idx < nodes_len); + // Safety: bounds checked + NodeId::from_index(idx) + }) + .collect() + } else { + SmallVec::new() + }; + stats.update_link_count += updates.len(); + + let ty = match node.ty { + CNodeType::Repeater(delay) => { + if side_inputs.is_empty() { + NodeType::SimpleRepeater(delay) + } else { + NodeType::Repeater(delay) + } + } + CNodeType::Torch => NodeType::Torch, + CNodeType::Comparator(mode) => NodeType::Comparator(mode), + CNodeType::Lamp => NodeType::Lamp, + CNodeType::Button => NodeType::Button, + CNodeType::Lever => NodeType::Lever, + CNodeType::PressurePlate => NodeType::PressurePlate, + CNodeType::Trapdoor => NodeType::Trapdoor, + CNodeType::Wire => NodeType::Wire, + CNodeType::Constant => NodeType::Constant, }; + Node { ty, default_inputs, side_inputs, updates, - powered, - output_power, - locked: match node.state { - Block::RedstoneRepeater { repeater } => repeater.locked, - _ => false, - }, + powered: node.state.powered, + output_power: node.state.output_strength, + locked: node.state.repeater_locked, facing_diode: node.facing_diode, comparator_far_input: node.comparator_far_input, pending_tick: false, @@ -229,11 +246,14 @@ struct TickScheduler { impl TickScheduler { const NUM_PRIORITIES: usize = 4; - fn reset(&mut self, plot: &mut PlotWorld, blocks: &[(BlockPos, Block)]) { + fn reset(&mut self, plot: &mut PlotWorld, blocks: &[Option<(BlockPos, Block)>]) { for (delay, queues) in self.queues_deque.iter().enumerate() { for (entries, priority) in queues.0.iter().zip(Self::priorities()) { for node in entries { - let pos = blocks[node.index()].0; + let Some((pos, _)) = blocks[node.index()] else { + warn!("Cannot schedule tick for node {:?} because block information is missing", node); + continue; + }; plot.schedule_tick(pos, delay as u32 + 1, priority); } } @@ -287,7 +307,7 @@ impl TickScheduler { #[derive(Default)] pub struct DirectBackend { nodes: Nodes, - blocks: Vec<(BlockPos, Block)>, + blocks: Vec>, pos_map: HashMap, scheduler: TickScheduler, } @@ -311,13 +331,24 @@ impl DirectBackend { } impl JITBackend for DirectBackend { + fn inspect(&mut self, pos: BlockPos) { + let Some(node_id) = self.pos_map.get(&pos) else { + debug!("could not find node at pos {}", pos); + return; + }; + + debug!("Node {:?}: {:#?}", node_id, self.nodes[*node_id]); + } + fn reset(&mut self, plot: &mut PlotWorld, io_only: bool) { self.scheduler.reset(plot, &self.blocks); let nodes = std::mem::take(&mut self.nodes); for (i, node) in nodes.into_inner().iter().enumerate() { - let (pos, block) = self.blocks[i]; + let Some((pos, block)) = self.blocks[i] else { + continue; + }; if matches!(node.ty, NodeType::Comparator(_)) { let block_entity = BlockEntity::Comparator { output_strength: node.output_power, @@ -429,17 +460,33 @@ impl JITBackend for DirectBackend { self.scheduler.end_tick(queues); } - fn compile(&mut self, nodes: Vec, ticks: Vec) { - let nodes_len = nodes.len(); - self.blocks = nodes.iter().map(|node| (node.pos, node.state)).collect(); - let nodes = nodes - .into_iter() - .map(|cn| Node::from_compile_node(cn, nodes_len)) + fn compile(&mut self, graph: CompileGraph, ticks: Vec) { + let mut nodes_map = HashMap::with_capacity(graph.node_count()); + for node in graph.node_indices() { + nodes_map.insert(node, nodes_map.len()); + } + let nodes_len = nodes_map.len(); + + let mut stats = FinalGraphStats::default(); + let nodes = graph + .node_indices() + .map(|idx| Node::from_compile_node(&graph, idx, nodes_len, &nodes_map, &mut stats)) + .collect(); + stats.nodes_bytes = nodes_len * std::mem::size_of::(); + trace!("{:#?}", stats); + + self.blocks = graph + .node_weights() + .map(|node| node.block.map(|(pos, id)| (pos, Block::from_id(id)))) .collect(); self.nodes = Nodes::new(nodes); + for i in 0..self.blocks.len() { - self.pos_map.insert(self.blocks[i].0, self.nodes.get(i)); + if let Some((pos, _)) = self.blocks[i] { + self.pos_map.insert(pos, self.nodes.get(i)); + } } + let queues = self.scheduler.queues_this_tick(); for entry in ticks { if let Some(node) = self.pos_map.get(&entry.pos) { @@ -454,7 +501,9 @@ impl JITBackend for DirectBackend { fn flush(&mut self, plot: &mut PlotWorld, io_only: bool) { for (i, node) in self.nodes.inner_mut().iter_mut().enumerate() { - let (pos, block) = &mut self.blocks[i]; + let Some((pos, block)) = &mut self.blocks[i] else { + continue; + }; if node.changed && (!io_only || node.ty.is_io_block()) { if let Some(powered) = block_powered_mut(block) { *powered = node.powered @@ -637,15 +686,17 @@ impl fmt::Display for DirectBackend { if matches!(node.ty, NodeType::Wire) { continue; } - let pos = self.blocks[id].0; + let pos = if let Some((pos, _)) = self.blocks[id] { + format!("{}, {}, {}", pos.x, pos.y, pos.z) + } else { + "No Pos".to_string() + }; write!( f, - "n{}[label=\"{}\\n({}, {}, {})\"];", + "n{}[label=\"{}\\n({})\"];", id, format!("{:?}", node.ty).split_whitespace().next().unwrap(), - pos.x, - pos.y, - pos.z + pos, )?; let all_inputs = node .default_inputs diff --git a/crates/core/src/redpiler/backend/mod.rs b/crates/core/src/redpiler/backend/mod.rs index 303f8d98..77f27fcc 100644 --- a/crates/core/src/redpiler/backend/mod.rs +++ b/crates/core/src/redpiler/backend/mod.rs @@ -3,17 +3,18 @@ pub mod cranelift; pub mod direct; // pub mod par_direct; +use super::compile_graph::CompileGraph; use crate::plot::PlotWorld; use mchprs_blocks::BlockPos; use mchprs_world::TickEntry; -use super::CompileNode; - pub trait JITBackend { - fn compile(&mut self, nodes: Vec, ticks: Vec); + fn compile(&mut self, graph: CompileGraph, ticks: Vec); fn tick(&mut self, plot: &mut PlotWorld); fn on_use_block(&mut self, plot: &mut PlotWorld, pos: BlockPos); fn set_pressure_plate(&mut self, plot: &mut PlotWorld, pos: BlockPos, powered: bool); fn flush(&mut self, plot: &mut PlotWorld, io_only: bool); fn reset(&mut self, plot: &mut PlotWorld, io_only: bool); + /// Inspect block for debugging + fn inspect(&mut self, pos: BlockPos); } diff --git a/crates/core/src/redpiler/compile_graph.rs b/crates/core/src/redpiler/compile_graph.rs new file mode 100644 index 00000000..54d6a7cc --- /dev/null +++ b/crates/core/src/redpiler/compile_graph.rs @@ -0,0 +1,109 @@ +use crate::blocks::ComparatorMode; +use mchprs_blocks::BlockPos; +use petgraph::stable_graph::{NodeIndex, StableGraph}; + +pub type NodeIdx = NodeIndex; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NodeType { + Repeater(u8), + Torch, + Comparator(ComparatorMode), + Lamp, + Button, + Lever, + PressurePlate, + Trapdoor, + Wire, + Constant, +} + +impl NodeType { + pub fn is_output(self) -> bool { + matches!(self, NodeType::Lamp | NodeType::Trapdoor) + } +} + +#[derive(Debug, Clone, Default)] +pub struct NodeState { + pub powered: bool, + pub repeater_locked: bool, + pub output_strength: u8, +} + +impl NodeState { + pub fn simple(powered: bool) -> NodeState { + NodeState { + powered, + output_strength: if powered { 15 } else { 0 }, + ..Default::default() + } + } + + pub fn repeater(powered: bool, locked: bool) -> NodeState { + NodeState { + powered, + repeater_locked: locked, + output_strength: if powered { 15 } else { 0 }, + } + } + + pub fn ss(ss: u8) -> NodeState { + NodeState { + output_strength: ss, + ..Default::default() + } + } + + pub fn comparator(powered: bool, ss: u8) -> NodeState { + NodeState { + powered, + output_strength: ss, + ..Default::default() + } + } +} + +#[derive(Debug)] +pub struct CompileNode { + pub ty: NodeType, + pub block: Option<(BlockPos, u32)>, + pub state: NodeState, + + pub facing_diode: bool, + pub comparator_far_input: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LinkType { + Default, + Side, +} + +#[derive(Debug)] +pub struct CompileLink { + pub ty: LinkType, + pub ss: u8, +} + +impl CompileLink { + pub fn new(ty: LinkType, ss: u8) -> CompileLink { + CompileLink { ty, ss } + } + + pub fn default(ss: u8) -> CompileLink { + CompileLink { + ty: LinkType::Default, + ss, + } + } + + pub fn side(ss: u8) -> CompileLink { + CompileLink { + ty: LinkType::Side, + ss, + } + } +} + +pub type CompileGraph = StableGraph; diff --git a/crates/core/src/redpiler/mod.rs b/crates/core/src/redpiler/mod.rs index 3d05ccc0..1f447683 100644 --- a/crates/core/src/redpiler/mod.rs +++ b/crates/core/src/redpiler/mod.rs @@ -1,19 +1,17 @@ mod backend; -mod debug_graph; +mod compile_graph; +// mod debug_graph; +mod passes; -use crate::blocks::{Block, ButtonFace, LeverFace}; +use crate::blocks::Block; use crate::plot::PlotWorld; use crate::world::World; use backend::JITBackend; -use log::{error, warn}; -use mchprs_blocks::block_entities::BlockEntity; -use mchprs_blocks::{BlockDirection, BlockFace, BlockPos}; +use log::{debug, error, trace, warn}; +use mchprs_blocks::BlockPos; use mchprs_world::TickEntry; -use std::collections::{HashMap, VecDeque}; - -fn is_wire(world: &dyn World, pos: BlockPos) -> bool { - matches!(world.get_block(pos), Block::RedstoneWire { .. }) -} +use passes::DEFAULT_PASS_MANAGER; +use std::time::Instant; fn bool_to_ss(b: bool) -> u8 { match b { @@ -37,513 +35,6 @@ fn block_powered_mut(block: &mut Block) -> Option<&mut bool> { }) } -type NodeId = usize; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum LinkType { - Default, - Side, -} - -#[derive(Debug, Clone)] -struct Link { - ty: LinkType, - start: NodeId, - weight: u8, - end: NodeId, -} - -impl Link { - fn new(ty: LinkType, start: NodeId, weight: u8, end: NodeId) -> Link { - Link { - ty, - start, - weight, - end, - } - } -} - -#[derive(Debug, Clone)] -pub struct CompileNode { - pos: BlockPos, - state: Block, - inputs: Vec, - updates: Vec, - comparator_output: u8, - container_overriding: bool, - facing_diode: bool, - comparator_far_input: Option, -} - -impl CompileNode { - fn new(pos: BlockPos, state: Block, facing_diode: bool) -> CompileNode { - CompileNode { - pos, - state, - inputs: vec![], - updates: vec![], - comparator_output: 0, - container_overriding: false, - facing_diode, - comparator_far_input: None, - } - } - - fn from_block(pos: BlockPos, block: Block, facing_diode: bool) -> Option { - let is_node = matches!( - block, - Block::RedstoneComparator { .. } - | Block::RedstoneTorch { .. } - | Block::RedstoneWallTorch { .. } - | Block::RedstoneRepeater { .. } - | Block::RedstoneWire { .. } - | Block::Lever { .. } - | Block::StoneButton { .. } - | Block::RedstoneBlock { .. } - | Block::RedstoneLamp { .. } - | Block::StonePressurePlate { .. } - | Block::IronTrapdoor { .. } - ); - - if is_node || block.has_comparator_override() { - Some(CompileNode::new(pos, block, facing_diode)) - } else { - None - } - } - - fn output_power(&self) -> u8 { - match self.state { - Block::RedstoneComparator { .. } => self.comparator_output, - Block::RedstoneTorch { lit } => bool_to_ss(lit), - Block::RedstoneWallTorch { lit, .. } => bool_to_ss(lit), - Block::RedstoneRepeater { repeater } => bool_to_ss(repeater.powered), - Block::Lever { lever } => bool_to_ss(lever.powered), - Block::StoneButton { button } => bool_to_ss(button.powered), - Block::RedstoneBlock {} => 15, - Block::StonePressurePlate { powered } => bool_to_ss(powered), - s if s.has_comparator_override() => self.comparator_output, - _ => 0, - } - } - - fn powered(&self) -> bool { - match block_powered_mut(&mut self.state.clone()) { - Some(powered) => *powered, - None => false, - } - } -} - -struct InputSearch<'a> { - plot: &'a mut PlotWorld, - nodes: &'a mut Vec, - pos_map: HashMap, -} - -impl<'a> InputSearch<'a> { - fn new(plot: &'a mut PlotWorld, nodes: &'a mut Vec) -> InputSearch<'a> { - let mut pos_map = HashMap::new(); - for (i, node) in nodes.iter().enumerate() { - pos_map.insert(node.pos, i); - } - - InputSearch { - plot, - nodes, - pos_map, - } - } - - fn provides_weak_power(&self, block: Block, side: BlockFace) -> bool { - match block { - Block::RedstoneTorch { .. } => true, - Block::RedstoneWallTorch { facing, .. } if facing.block_face() != side => true, - Block::RedstoneBlock {} => true, - Block::Lever { .. } => true, - Block::StoneButton { .. } => true, - Block::StonePressurePlate { .. } => true, - Block::RedstoneRepeater { repeater } if repeater.facing.block_face() == side => true, - Block::RedstoneComparator { comparator } if comparator.facing.block_face() == side => { - true - } - _ => false, - } - } - - fn provides_strong_power(&self, block: Block, side: BlockFace) -> bool { - match block { - Block::RedstoneTorch { .. } if side == BlockFace::Bottom => true, - Block::RedstoneWallTorch { .. } if side == BlockFace::Bottom => true, - Block::StonePressurePlate { .. } if side == BlockFace::Top => true, - Block::Lever { lever } => match side { - BlockFace::Top if lever.face == LeverFace::Floor => true, - BlockFace::Bottom if lever.face == LeverFace::Ceiling => true, - _ if lever.facing == side.to_direction() => true, - _ => false, - }, - Block::StoneButton { button } => match side { - BlockFace::Top if button.face == ButtonFace::Floor => true, - BlockFace::Bottom if button.face == ButtonFace::Ceiling => true, - _ if button.facing == side.to_direction() => true, - _ => false, - }, - Block::RedstoneRepeater { .. } => self.provides_weak_power(block, side), - Block::RedstoneComparator { .. } => self.provides_weak_power(block, side), - _ => false, - } - } - - // unfortunate - #[allow(clippy::too_many_arguments)] - fn get_redstone_links( - &self, - block: Block, - side: BlockFace, - pos: BlockPos, - link_ty: LinkType, - distance: u8, - start_node: NodeId, - search_wire: bool, - ) -> Vec { - let mut res = Vec::new(); - if block.is_solid() { - for side in &BlockFace::values() { - let pos = pos.offset(*side); - let block = self.plot.get_block(pos); - if self.provides_strong_power(block, *side) { - res.push(Link::new(link_ty, start_node, distance, self.pos_map[&pos])); - } - - if let Block::RedstoneWire { wire } = block { - if !search_wire { - continue; - } - match side { - BlockFace::Top => { - res.append(&mut self.search_wire(start_node, pos, link_ty, distance)); - } - BlockFace::Bottom => {} - _ => { - let direction = side.to_direction(); - if search_wire - && !wire - .get_regulated_sides(self.plot, pos) - .get_current_side(direction.opposite()) - .is_none() - { - res.append( - &mut self.search_wire(start_node, pos, link_ty, distance), - ); - } - } - } - } - } - } else if self.provides_weak_power(block, side) { - res.push(Link::new(link_ty, start_node, distance, self.pos_map[&pos])); - } else if let Block::RedstoneWire { wire } = block { - match side { - BlockFace::Top => { - res.append(&mut self.search_wire(start_node, pos, link_ty, distance)) - } - BlockFace::Bottom => {} - _ => { - let direction = side.to_direction(); - if search_wire - && !wire - .get_regulated_sides(self.plot, pos) - .get_current_side(direction.opposite()) - .is_none() - { - res.append(&mut self.search_wire(start_node, pos, link_ty, distance)); - } - } - } - } - res - } - - fn search_wire( - &self, - start_node: NodeId, - root_pos: BlockPos, - link_ty: LinkType, - mut distance: u8, - ) -> Vec { - let mut res = Vec::new(); - - let mut queue: VecDeque = VecDeque::new(); - let mut discovered = HashMap::new(); - - discovered.insert(root_pos, distance); - queue.push_back(root_pos); - - while !queue.is_empty() { - let pos = queue.pop_front().unwrap(); - distance = discovered[&pos]; - - let up_pos = pos.offset(BlockFace::Top); - let up_block = self.plot.get_block(up_pos); - - for side in &BlockFace::values() { - let neighbor_pos = pos.offset(*side); - let neighbor = self.plot.get_block(neighbor_pos); - - res.append(&mut self.get_redstone_links( - neighbor, - *side, - neighbor_pos, - link_ty, - distance, - start_node, - false, - )); - - if is_wire(self.plot, neighbor_pos) && !discovered.contains_key(&neighbor_pos) { - queue.push_back(neighbor_pos); - discovered.insert(neighbor_pos, discovered[&pos] + 1); - } - - if side.is_horizontal() { - if !up_block.is_solid() && !neighbor.is_transparent() { - let neighbor_up_pos = neighbor_pos.offset(BlockFace::Top); - if is_wire(self.plot, neighbor_up_pos) - && !discovered.contains_key(&neighbor_up_pos) - { - queue.push_back(neighbor_up_pos); - discovered.insert(neighbor_up_pos, discovered[&pos] + 1); - } - } - - if !neighbor.is_solid() { - let neighbor_down_pos = neighbor_pos.offset(BlockFace::Bottom); - if is_wire(self.plot, neighbor_down_pos) - && !discovered.contains_key(&neighbor_down_pos) - { - queue.push_back(neighbor_down_pos); - discovered.insert(neighbor_down_pos, discovered[&pos] + 1); - } - } - } - } - } - - res - } - - fn search_diode_inputs( - &mut self, - id: NodeId, - pos: BlockPos, - facing: BlockDirection, - ) -> Vec { - let input_pos = pos.offset(facing.block_face()); - let input_block = self.plot.get_block(input_pos); - self.get_redstone_links( - input_block, - facing.block_face(), - input_pos, - LinkType::Default, - 0, - id, - true, - ) - } - - fn search_repeater_side( - &mut self, - id: NodeId, - pos: BlockPos, - side: BlockDirection, - ) -> Option { - let side_pos = pos.offset(side.block_face()); - let side_block = self.plot.get_block(side_pos); - if side_block.is_diode() && self.provides_weak_power(side_block, side.block_face()) { - Some(Link::new(LinkType::Side, id, 0, self.pos_map[&side_pos])) - } else { - None - } - } - - fn search_comparator_side( - &mut self, - id: NodeId, - pos: BlockPos, - side: BlockDirection, - ) -> Vec { - let side_pos = pos.offset(side.block_face()); - let side_block = self.plot.get_block(side_pos); - if side_block.is_diode() && self.provides_weak_power(side_block, side.block_face()) { - vec![Link::new(LinkType::Side, id, 0, self.pos_map[&side_pos])] - } else if matches!(side_block, Block::RedstoneWire { .. }) { - self.search_wire(id, side_pos, LinkType::Side, 0) - } else { - vec![] - } - } - - fn search_node(&mut self, id: NodeId, node: CompileNode) { - match node.state { - Block::RedstoneTorch { .. } => { - let bottom_pos = node.pos.offset(BlockFace::Bottom); - let bottom_block = self.plot.get_block(bottom_pos); - let inputs = self.get_redstone_links( - bottom_block, - BlockFace::Top, - bottom_pos, - LinkType::Default, - 0, - id, - true, - ); - self.nodes[id].inputs = inputs; - } - Block::RedstoneWallTorch { facing, .. } => { - let wall_pos = node.pos.offset(facing.opposite().block_face()); - let wall_block = self.plot.get_block(wall_pos); - let inputs = self.get_redstone_links( - wall_block, - facing.opposite().block_face(), - wall_pos, - LinkType::Default, - 0, - id, - true, - ); - self.nodes[id].inputs = inputs; - } - Block::RedstoneComparator { comparator } => { - let facing = comparator.facing; - - let mut inputs = self.search_diode_inputs(id, node.pos, facing); - inputs.append(&mut self.search_comparator_side(id, node.pos, facing.rotate())); - inputs.append(&mut self.search_comparator_side(id, node.pos, facing.rotate_ccw())); - - let input_pos = node.pos.offset(facing.block_face()); - let input_block = self.plot.get_block(input_pos); - if input_block.has_comparator_override() { - self.nodes[id].container_overriding = true; - inputs.push(Link::new( - LinkType::Default, - id, - 0, - self.pos_map[&input_pos], - )); - } else { - let far_input_pos = input_pos.offset(facing.block_face()); - let far_input_block = self.plot.get_block(far_input_pos); - if input_block.is_solid() && far_input_block.has_comparator_override() { - let far_override = - far_input_block.get_comparator_override(self.plot, far_input_pos); - self.nodes[id].comparator_far_input = Some(far_override); - } - } - - let output_strength = if let Some(BlockEntity::Comparator { output_strength }) = - self.plot.get_block_entity(node.pos) - { - *output_strength - } else { - 0 - }; - - self.nodes[id].comparator_output = output_strength; - self.nodes[id].inputs = inputs; - } - Block::RedstoneRepeater { repeater } => { - let facing = repeater.facing; - - let mut inputs = self.search_diode_inputs(id, node.pos, facing); - if let Some(l) = self.search_repeater_side(id, node.pos, facing.rotate()) { - inputs.push(l); - } - if let Some(l) = self.search_repeater_side(id, node.pos, facing.rotate_ccw()) { - inputs.push(l); - } - self.nodes[id].inputs = inputs; - } - Block::RedstoneWire { .. } => { - let inputs = self.search_wire(id, node.pos, LinkType::Default, 0); - self.nodes[id].inputs = inputs; - } - Block::RedstoneLamp { .. } | Block::IronTrapdoor { .. } => { - let mut inputs = Vec::new(); - for face in &BlockFace::values() { - let neighbor_pos = node.pos.offset(*face); - let neighbor_block = self.plot.get_block(neighbor_pos); - let mut links = self.get_redstone_links( - neighbor_block, - *face, - neighbor_pos, - LinkType::Default, - 0, - id, - true, - ); - inputs.append(&mut links); - } - self.nodes[id].inputs = inputs; - } - block if block.has_comparator_override() => { - self.nodes[id].comparator_output = - block.get_comparator_override(self.plot, node.pos); - } - _ => {} - } - } - - fn search(&mut self) { - let nodes = self.nodes.clone(); - for (i, node) in nodes.into_iter().enumerate() { - self.search_node(i, node); - } - - // Optimizations against the search graph like wire stripping and dedup go here - - // Dedup links - let nodes = self.nodes.clone(); - for (i, node) in nodes.into_iter().enumerate() { - let mut links: Vec = Vec::new(); - for link in node.inputs.clone() { - let mut exists = false; - for l in &mut links { - if l.end == link.end && l.ty == link.ty { - exists = true; - if link.weight < l.weight { - l.weight = link.weight; - } - } - } - - if !exists && link.weight < 15 { - links.push(link); - } - } - self.nodes[i].inputs = links; - } - - // Remove other inputs to comparators with a comparator overriding container input. - for (i, mut node) in self.nodes.clone().into_iter().enumerate() { - if node.container_overriding { - node.inputs.retain(|link| { - link.ty != LinkType::Default - || self.nodes[link.end].state.has_comparator_override() - }); - self.nodes[i] = node; - } - } - - // Create update links - for (id, node) in self.nodes.clone().into_iter().enumerate() { - for input_node in node.inputs { - self.nodes[input_node.end].updates.push(id); - } - } - } -} - #[derive(Default)] pub struct CompilerOptions { pub optimize: bool, @@ -599,15 +90,14 @@ impl Compiler { options: CompilerOptions, ticks: Vec, ) { - let (first_pos, second_pos) = plot.get_corners(); + debug!("Starting compile"); + let start = Instant::now(); - let mut nodes = Compiler::identify_nodes(plot, first_pos, second_pos, options.optimize); - InputSearch::new(plot, &mut nodes).search(); - if options.export { - debug_graph::debug(&nodes); - } self.is_active = true; + let input = CompilerInput { plot }; + let graph = DEFAULT_PASS_MANAGER.run_passes(&options, input); + // TODO: Remove this once there is proper backend switching if self.jit.is_none() { let jit: Box = Default::default(); @@ -616,12 +106,16 @@ impl Compiler { } if let Some(jit) = &mut self.jit { - jit.compile(nodes, ticks); + trace!("Compiling backend"); + let start = Instant::now(); + jit.compile(graph, ticks); + trace!("Backend compiled in {:?}", start.elapsed()); } else { error!("Cannot compile without JIT variant selected"); } self.options = options; + debug!("Compile completed in {:?}", start.elapsed()); } pub fn reset(&mut self, plot: &mut PlotWorld) { @@ -680,40 +174,15 @@ impl Compiler { self.backend().flush(plot, io_only); } - fn identify_nodes( - plot: &mut PlotWorld, - first_pos: BlockPos, - second_pos: BlockPos, - no_wires: bool, - ) -> Vec { - let mut nodes = Vec::new(); - let start_pos = first_pos.min(second_pos); - let end_pos = first_pos.max(second_pos); - for y in start_pos.y..=end_pos.y { - for z in start_pos.z..=end_pos.z { - for x in start_pos.x..=end_pos.x { - let pos = BlockPos::new(x, y, z); - let block = plot.get_block(pos); - let facing_diode = if let Block::RedstoneRepeater { repeater } = block { - plot.get_block(pos.offset(repeater.facing.opposite().block_face())) - .is_diode() - } else if let Block::RedstoneComparator { comparator } = block { - plot.get_block(pos.offset(comparator.facing.opposite().block_face())) - .is_diode() - } else { - false - }; - - if no_wires && matches!(block, Block::RedstoneWire { .. }) { - continue; - } - - if let Some(node) = CompileNode::from_block(pos, block, facing_diode) { - nodes.push(node); - } - } - } + pub fn inspect(&mut self, pos: BlockPos) { + if let Some(backend) = &mut self.jit { + backend.inspect(pos); + } else { + debug!("cannot inspect when backend is not running"); } - nodes } } + +pub struct CompilerInput<'w> { + pub plot: &'w PlotWorld, +} diff --git a/crates/core/src/redpiler/passes/clamp_weights.rs b/crates/core/src/redpiler/passes/clamp_weights.rs new file mode 100644 index 00000000..b7897b5a --- /dev/null +++ b/crates/core/src/redpiler/passes/clamp_weights.rs @@ -0,0 +1,11 @@ +use super::Pass; +use crate::redpiler::compile_graph::CompileGraph; +use crate::redpiler::{CompilerInput, CompilerOptions}; + +pub struct ClampWeights; + +impl Pass for ClampWeights { + fn run_pass(&self, graph: &mut CompileGraph, _: &CompilerOptions, _: &CompilerInput<'_>) { + graph.retain_edges(|g, edge| g[edge].ss < 15); + } +} diff --git a/crates/core/src/redpiler/passes/coalesce.rs b/crates/core/src/redpiler/passes/coalesce.rs new file mode 100644 index 00000000..ca2aa58f --- /dev/null +++ b/crates/core/src/redpiler/passes/coalesce.rs @@ -0,0 +1,80 @@ +use super::Pass; +use crate::redpiler::compile_graph::{CompileGraph, LinkType, NodeIdx, NodeType}; +use crate::redpiler::{CompilerInput, CompilerOptions}; +use petgraph::visit::{EdgeRef, NodeIndexable}; +use petgraph::Direction; + +pub struct Coalesce; + +impl Pass for Coalesce { + fn run_pass(&self, graph: &mut CompileGraph, _: &CompilerOptions, _: &CompilerInput<'_>) { + for i in 0..graph.node_bound() { + let idx = NodeIdx::new(i); + if !graph.contains_node(idx) { + continue; + } + + let node = &graph[idx]; + // Comparators depend on the link weight as well as the type, + // we could implement that later if it's beneficial enough. + if matches!(node.ty, NodeType::Comparator(_)) || node.ty.is_output() { + continue; + } + + let mut edges = graph.edges_directed(idx, Direction::Incoming); + let Some(edge) = edges.next() else { + continue; + }; + + if edge.weight().ty == LinkType::Side || edges.next().is_some() { + continue; + } + + let source = edge.source(); + // Comparators might output less than 15 ss + if matches!(graph[source].ty, NodeType::Comparator(_)) { + continue; + } + coalesce_outgoing(graph, source, idx); + } + } + + fn should_run(&self, options: &CompilerOptions) -> bool { + options.io_only + } +} + +fn coalesce_outgoing(graph: &mut CompileGraph, source_idx: NodeIdx, into_idx: NodeIdx) { + let mut walk_outgoing = graph + .neighbors_directed(source_idx, Direction::Outgoing) + .detach(); + while let Some(edge_idx) = walk_outgoing.next_edge(graph) { + let dest_idx = graph.edge_endpoints(edge_idx).unwrap().1; + if dest_idx == into_idx { + continue; + } + + let dest = &graph[dest_idx]; + let into = &graph[into_idx]; + + if dest.ty == into.ty + && dest.facing_diode == into.facing_diode + && graph + .neighbors_directed(dest_idx, Direction::Incoming) + .count() + == 1 + { + coalesce(graph, dest_idx, into_idx); + } + } +} + +fn coalesce(graph: &mut CompileGraph, node: NodeIdx, into: NodeIdx) { + let mut walk_outgoing = graph.neighbors_directed(node, Direction::Outgoing).detach(); + while let Some(edge_idx) = walk_outgoing.next_edge(graph) { + let dest = graph.edge_endpoints(edge_idx).unwrap().1; + let weight = graph.remove_edge(edge_idx).unwrap(); + graph.add_edge(into, dest, weight); + } + graph.remove_node(node); +} diff --git a/crates/core/src/redpiler/passes/constant_coalesce.rs b/crates/core/src/redpiler/passes/constant_coalesce.rs new file mode 100644 index 00000000..f1b87458 --- /dev/null +++ b/crates/core/src/redpiler/passes/constant_coalesce.rs @@ -0,0 +1,44 @@ +use super::Pass; +use crate::redpiler::compile_graph::{CompileGraph, NodeIdx, NodeType}; +use crate::redpiler::{CompilerInput, CompilerOptions}; +use petgraph::visit::NodeIndexable; +use petgraph::Direction; +use std::collections::HashMap; + +pub struct ConstantCoalesce; + +impl Pass for ConstantCoalesce { + fn run_pass(&self, graph: &mut CompileGraph, _: &CompilerOptions, _: &CompilerInput<'_>) { + let mut constant_nodes = HashMap::new(); + + for i in 0..graph.node_bound() { + let idx = NodeIdx::new(i); + if !graph.contains_node(idx) { + continue; + } + + if graph[idx].ty != NodeType::Constant { + continue; + } + + let ss = graph[idx].state.output_strength; + + match constant_nodes.get(&graph[idx].state.output_strength) { + Some(&constant_idx) => { + let mut neighbors = graph.neighbors_directed(idx, Direction::Outgoing).detach(); + while let Some(edge) = neighbors.next_edge(graph) { + let dest = graph.edge_endpoints(edge).unwrap().1; + let weight = graph.remove_edge(edge).unwrap(); + graph.add_edge(constant_idx, dest, weight); + } + graph.remove_node(idx); + } + None => { + // Turn this node into a generic constant + graph[idx].block = None; + constant_nodes.insert(ss, idx); + } + } + } + } +} diff --git a/crates/core/src/redpiler/passes/constant_fold.rs b/crates/core/src/redpiler/passes/constant_fold.rs new file mode 100644 index 00000000..5b9f3ec8 --- /dev/null +++ b/crates/core/src/redpiler/passes/constant_fold.rs @@ -0,0 +1,63 @@ +use super::Pass; +use crate::redpiler::compile_graph::{CompileGraph, LinkType, NodeIdx, NodeType}; +use crate::redpiler::{CompilerInput, CompilerOptions}; +use log::trace; +use petgraph::visit::{EdgeRef, NodeIndexable}; +use petgraph::Direction; + +pub struct ConstantFold; + +impl Pass for ConstantFold { + fn run_pass(&self, graph: &mut CompileGraph, _: &CompilerOptions, _: &CompilerInput<'_>) { + loop { + let num_folded = fold(graph); + if num_folded == 0 { + break; + } + trace!("Fold iteration: {} nodes", num_folded); + } + } +} + +fn fold(graph: &mut CompileGraph) -> usize { + let mut num_folded = 0; + + for i in 0..graph.node_bound() { + let idx = NodeIdx::new(i); + if !graph.contains_node(idx) { + continue; + } + + // TODO: Other node types + if !matches!(graph[idx].ty, NodeType::Comparator(_)) { + continue; + } + + let mut edges = graph.edges_directed(idx, Direction::Incoming); + let Some(edge) = edges.next() else { + continue; + }; + + // TODO: Handle multiple inputs + if edges.next().is_some() { + continue; + } + + let constant_idx = edge.source(); + if graph[constant_idx].ty != NodeType::Constant || edge.weight().ty == LinkType::Side { + continue; + } + + let mut outgoing = graph.neighbors_directed(idx, Direction::Outgoing).detach(); + while let Some(outgoing_edge) = outgoing.next_edge(graph) { + let outgoing_node = graph.edge_endpoints(outgoing_edge).unwrap().1; + let weight = graph.remove_edge(outgoing_edge).unwrap(); + graph.add_edge(constant_idx, outgoing_node, weight); + } + + graph.remove_node(idx); + num_folded += 1; + } + + num_folded +} diff --git a/crates/core/src/redpiler/passes/dedup_links.rs b/crates/core/src/redpiler/passes/dedup_links.rs new file mode 100644 index 00000000..d08cb50c --- /dev/null +++ b/crates/core/src/redpiler/passes/dedup_links.rs @@ -0,0 +1,39 @@ +use super::Pass; +use crate::redpiler::compile_graph::{CompileGraph, NodeIdx}; +use crate::redpiler::{CompilerInput, CompilerOptions}; +use petgraph::visit::{EdgeRef, NodeIndexable}; +use petgraph::Direction; + +pub struct DedupLinks; + +impl Pass for DedupLinks { + fn run_pass(&self, graph: &mut CompileGraph, _: &CompilerOptions, _: &CompilerInput<'_>) { + for i in 0..graph.node_bound() { + let idx = NodeIdx::new(i); + if !graph.contains_node(idx) { + continue; + } + + let mut edges = graph.neighbors_directed(idx, Direction::Incoming).detach(); + while let Some(edge_idx) = edges.next_edge(graph) { + let edge = &graph[edge_idx]; + let source_idx = graph.edge_endpoints(edge_idx).unwrap().0; + + let mut should_remove = false; + for other_edge in graph.edges_directed(idx, Direction::Incoming) { + if other_edge.id() != edge_idx + && other_edge.source() == source_idx + && other_edge.weight().ty == edge.ty + && other_edge.weight().ss <= edge.ss + { + should_remove = true; + } + } + + if should_remove { + graph.remove_edge(edge_idx); + } + } + } + } +} diff --git a/crates/core/src/redpiler/passes/identify_nodes.rs b/crates/core/src/redpiler/passes/identify_nodes.rs new file mode 100644 index 00000000..2d4bc8aa --- /dev/null +++ b/crates/core/src/redpiler/passes/identify_nodes.rs @@ -0,0 +1,112 @@ +use super::Pass; +use crate::blocks::Block; +use crate::plot::PlotWorld; +use crate::redpiler::compile_graph::{CompileGraph, CompileNode, NodeState, NodeType}; +use crate::redpiler::{CompilerInput, CompilerOptions}; +use crate::world::World; +use mchprs_blocks::block_entities::BlockEntity; +use mchprs_blocks::BlockPos; + +pub struct IdentifyNodes; + +impl Pass for IdentifyNodes { + fn run_pass( + &self, + graph: &mut CompileGraph, + options: &CompilerOptions, + input: &CompilerInput<'_>, + ) { + let ignore_wires = options.optimize; + let plot = input.plot; + + let (first_pos, second_pos) = plot.get_corners(); + + let start_pos = first_pos.min(second_pos); + let end_pos = first_pos.max(second_pos); + for y in start_pos.y..=end_pos.y { + for z in start_pos.z..=end_pos.z { + for x in start_pos.x..=end_pos.x { + let pos = BlockPos::new(x, y, z); + for_pos(ignore_wires, plot, graph, pos); + } + } + } + } + + fn should_run(&self, _: &CompilerOptions) -> bool { + // Mandatory + true + } +} + +fn for_pos(ignore_wires: bool, plot: &PlotWorld, graph: &mut CompileGraph, pos: BlockPos) { + let id = plot.get_block_raw(pos); + let block = Block::from_id(id); + + let Some((ty, state)) = identify_block(block, pos, plot) else { + return; + }; + + let facing_diode = if let Block::RedstoneRepeater { repeater } = block { + plot.get_block(pos.offset(repeater.facing.opposite().block_face())) + .is_diode() + } else if let Block::RedstoneComparator { comparator } = block { + plot.get_block(pos.offset(comparator.facing.opposite().block_face())) + .is_diode() + } else { + false + }; + + if ignore_wires && ty == NodeType::Wire { + return; + } + + graph.add_node(CompileNode { + ty, + block: Some((pos, id)), + state, + + facing_diode, + comparator_far_input: None, + }); +} + +fn identify_block(block: Block, pos: BlockPos, world: &PlotWorld) -> Option<(NodeType, NodeState)> { + let (ty, state) = match block { + Block::RedstoneRepeater { repeater } => ( + NodeType::Repeater(repeater.delay), + NodeState::repeater(repeater.powered, repeater.locked), + ), + Block::RedstoneComparator { comparator } => ( + NodeType::Comparator(comparator.mode), + NodeState::comparator( + comparator.powered, + if let Some(BlockEntity::Comparator { output_strength }) = + world.get_block_entity(pos) + { + *output_strength + } else { + 0 + }, + ), + ), + Block::RedstoneTorch { lit, .. } | Block::RedstoneWallTorch { lit, .. } => { + (NodeType::Torch, NodeState::simple(lit)) + } + Block::RedstoneWire { wire } => (NodeType::Wire, NodeState::ss(wire.power)), + Block::StoneButton { button } => (NodeType::Button, NodeState::simple(button.powered)), + Block::RedstoneLamp { lit } => (NodeType::Lamp, NodeState::simple(lit)), + Block::Lever { lever } => (NodeType::Lever, NodeState::simple(lever.powered)), + Block::StonePressurePlate { powered } => { + (NodeType::PressurePlate, NodeState::simple(powered)) + } + Block::IronTrapdoor { powered, .. } => (NodeType::Trapdoor, NodeState::simple(powered)), + Block::RedstoneBlock {} => (NodeType::Constant, NodeState::ss(15)), + block if block.has_comparator_override() => ( + NodeType::Constant, + NodeState::ss(block.get_comparator_override(world, pos)), + ), + _ => return None, + }; + Some((ty, state)) +} diff --git a/crates/core/src/redpiler/passes/input_search.rs b/crates/core/src/redpiler/passes/input_search.rs new file mode 100644 index 00000000..9233de1d --- /dev/null +++ b/crates/core/src/redpiler/passes/input_search.rs @@ -0,0 +1,350 @@ +use mchprs_blocks::{BlockDirection, BlockFace, BlockPos}; +use petgraph::visit::NodeIndexable; + +use super::Pass; +use crate::blocks::{Block, ButtonFace, LeverFace}; +use crate::plot::PlotWorld; +use crate::redpiler::compile_graph::{CompileGraph, CompileLink, LinkType, NodeIdx}; +use crate::redpiler::{CompilerInput, CompilerOptions}; +use crate::world::World; +use std::collections::{HashMap, VecDeque}; + +pub struct InputSearch; + +impl Pass for InputSearch { + fn run_pass(&self, graph: &mut CompileGraph, _: &CompilerOptions, input: &CompilerInput<'_>) { + let mut state = InputSearchState::new(input.plot, graph); + state.search(); + } + + fn should_run(&self, _: &CompilerOptions) -> bool { + // Mandatory + true + } +} + +struct InputSearchState<'a> { + plot: &'a PlotWorld, + graph: &'a mut CompileGraph, + pos_map: HashMap, +} + +impl<'a> InputSearchState<'a> { + fn new(plot: &'a PlotWorld, graph: &'a mut CompileGraph) -> InputSearchState<'a> { + let mut pos_map = HashMap::new(); + for id in graph.node_indices() { + let (pos, _) = graph[id].block.unwrap(); + pos_map.insert(pos, id); + } + + InputSearchState { + plot, + graph, + pos_map, + } + } + + fn provides_weak_power(&self, block: Block, side: BlockFace) -> bool { + match block { + Block::RedstoneTorch { .. } => true, + Block::RedstoneWallTorch { facing, .. } if facing.block_face() != side => true, + Block::RedstoneBlock {} => true, + Block::Lever { .. } => true, + Block::StoneButton { .. } => true, + Block::StonePressurePlate { .. } => true, + Block::RedstoneRepeater { repeater } if repeater.facing.block_face() == side => true, + Block::RedstoneComparator { comparator } if comparator.facing.block_face() == side => { + true + } + _ => false, + } + } + + fn provides_strong_power(&self, block: Block, side: BlockFace) -> bool { + match block { + Block::RedstoneTorch { .. } if side == BlockFace::Bottom => true, + Block::RedstoneWallTorch { .. } if side == BlockFace::Bottom => true, + Block::StonePressurePlate { .. } if side == BlockFace::Top => true, + Block::Lever { lever } => match side { + BlockFace::Top if lever.face == LeverFace::Floor => true, + BlockFace::Bottom if lever.face == LeverFace::Ceiling => true, + _ if lever.facing == side.to_direction() => true, + _ => false, + }, + Block::StoneButton { button } => match side { + BlockFace::Top if button.face == ButtonFace::Floor => true, + BlockFace::Bottom if button.face == ButtonFace::Ceiling => true, + _ if button.facing == side.to_direction() => true, + _ => false, + }, + Block::RedstoneRepeater { .. } => self.provides_weak_power(block, side), + Block::RedstoneComparator { .. } => self.provides_weak_power(block, side), + _ => false, + } + } + + // unfortunate + #[allow(clippy::too_many_arguments)] + fn get_redstone_links( + &mut self, + block: Block, + side: BlockFace, + pos: BlockPos, + link_ty: LinkType, + distance: u8, + start_node: NodeIdx, + search_wire: bool, + ) { + if block.is_solid() { + for side in &BlockFace::values() { + let pos = pos.offset(*side); + let block = self.plot.get_block(pos); + if self.provides_strong_power(block, *side) { + self.graph.add_edge( + self.pos_map[&pos], + start_node, + CompileLink::new(link_ty, distance), + ); + } + + if let Block::RedstoneWire { wire } = block { + if !search_wire { + continue; + } + match side { + BlockFace::Top => { + self.search_wire(start_node, pos, link_ty, distance); + } + BlockFace::Bottom => {} + _ => { + let direction = side.to_direction(); + if search_wire + && !wire + .get_regulated_sides(self.plot, pos) + .get_current_side(direction.opposite()) + .is_none() + { + self.search_wire(start_node, pos, link_ty, distance); + } + } + } + } + } + } else if self.provides_weak_power(block, side) { + self.graph.add_edge( + self.pos_map[&pos], + start_node, + CompileLink::new(link_ty, distance), + ); + } else if let Block::RedstoneWire { wire } = block { + match side { + BlockFace::Top => self.search_wire(start_node, pos, link_ty, distance), + BlockFace::Bottom => {} + _ => { + let direction = side.to_direction(); + if search_wire + && !wire + .get_regulated_sides(self.plot, pos) + .get_current_side(direction.opposite()) + .is_none() + { + self.search_wire(start_node, pos, link_ty, distance); + } + } + } + } + } + + fn search_wire( + &mut self, + start_node: NodeIdx, + root_pos: BlockPos, + link_ty: LinkType, + mut distance: u8, + ) { + let mut queue: VecDeque = VecDeque::new(); + let mut discovered = HashMap::new(); + + discovered.insert(root_pos, distance); + queue.push_back(root_pos); + + while !queue.is_empty() { + let pos = queue.pop_front().unwrap(); + distance = discovered[&pos]; + + let up_pos = pos.offset(BlockFace::Top); + let up_block = self.plot.get_block(up_pos); + + for side in &BlockFace::values() { + let neighbor_pos = pos.offset(*side); + let neighbor = self.plot.get_block(neighbor_pos); + + self.get_redstone_links( + neighbor, + *side, + neighbor_pos, + link_ty, + distance, + start_node, + false, + ); + + if is_wire(self.plot, neighbor_pos) && !discovered.contains_key(&neighbor_pos) { + queue.push_back(neighbor_pos); + discovered.insert(neighbor_pos, discovered[&pos] + 1); + } + + if side.is_horizontal() { + if !up_block.is_solid() && !neighbor.is_transparent() { + let neighbor_up_pos = neighbor_pos.offset(BlockFace::Top); + if is_wire(self.plot, neighbor_up_pos) + && !discovered.contains_key(&neighbor_up_pos) + { + queue.push_back(neighbor_up_pos); + discovered.insert(neighbor_up_pos, discovered[&pos] + 1); + } + } + + if !neighbor.is_solid() { + let neighbor_down_pos = neighbor_pos.offset(BlockFace::Bottom); + if is_wire(self.plot, neighbor_down_pos) + && !discovered.contains_key(&neighbor_down_pos) + { + queue.push_back(neighbor_down_pos); + discovered.insert(neighbor_down_pos, discovered[&pos] + 1); + } + } + } + } + } + } + + fn search_diode_inputs(&mut self, id: NodeIdx, pos: BlockPos, facing: BlockDirection) { + let input_pos = pos.offset(facing.block_face()); + let input_block = self.plot.get_block(input_pos); + self.get_redstone_links( + input_block, + facing.block_face(), + input_pos, + LinkType::Default, + 0, + id, + true, + ) + } + + fn search_repeater_side(&mut self, id: NodeIdx, pos: BlockPos, side: BlockDirection) { + let side_pos = pos.offset(side.block_face()); + let side_block = self.plot.get_block(side_pos); + if side_block.is_diode() && self.provides_weak_power(side_block, side.block_face()) { + self.graph + .add_edge(self.pos_map[&side_pos], id, CompileLink::side(0)); + } + } + + fn search_comparator_side(&mut self, id: NodeIdx, pos: BlockPos, side: BlockDirection) { + let side_pos = pos.offset(side.block_face()); + let side_block = self.plot.get_block(side_pos); + if side_block.is_diode() && self.provides_weak_power(side_block, side.block_face()) { + self.graph + .add_edge(self.pos_map[&side_pos], id, CompileLink::side(0)); + } else if matches!(side_block, Block::RedstoneWire { .. }) { + self.search_wire(id, side_pos, LinkType::Side, 0) + } + } + + fn search_node(&mut self, id: NodeIdx, (pos, block_id): (BlockPos, u32)) { + match Block::from_id(block_id) { + Block::RedstoneTorch { .. } => { + let bottom_pos = pos.offset(BlockFace::Bottom); + let bottom_block = self.plot.get_block(bottom_pos); + self.get_redstone_links( + bottom_block, + BlockFace::Top, + bottom_pos, + LinkType::Default, + 0, + id, + true, + ); + } + Block::RedstoneWallTorch { facing, .. } => { + let wall_pos = pos.offset(facing.opposite().block_face()); + let wall_block = self.plot.get_block(wall_pos); + self.get_redstone_links( + wall_block, + facing.opposite().block_face(), + wall_pos, + LinkType::Default, + 0, + id, + true, + ); + } + Block::RedstoneComparator { comparator } => { + let facing = comparator.facing; + + self.search_comparator_side(id, pos, facing.rotate()); + self.search_comparator_side(id, pos, facing.rotate_ccw()); + + let input_pos = pos.offset(facing.block_face()); + let input_block = self.plot.get_block(input_pos); + if input_block.has_comparator_override() { + self.graph + .add_edge(self.pos_map[&input_pos], id, CompileLink::default(0)); + } else { + self.search_diode_inputs(id, pos, facing); + + let far_input_pos = input_pos.offset(facing.block_face()); + let far_input_block = self.plot.get_block(far_input_pos); + if input_block.is_solid() && far_input_block.has_comparator_override() { + let far_override = + far_input_block.get_comparator_override(self.plot, far_input_pos); + self.graph[id].comparator_far_input = Some(far_override); + } + } + } + Block::RedstoneRepeater { repeater } => { + let facing = repeater.facing; + + self.search_diode_inputs(id, pos, facing); + self.search_repeater_side(id, pos, facing.rotate()); + self.search_repeater_side(id, pos, facing.rotate_ccw()); + } + Block::RedstoneWire { .. } => { + self.search_wire(id, pos, LinkType::Default, 0); + } + Block::RedstoneLamp { .. } | Block::IronTrapdoor { .. } => { + for face in &BlockFace::values() { + let neighbor_pos = pos.offset(*face); + let neighbor_block = self.plot.get_block(neighbor_pos); + self.get_redstone_links( + neighbor_block, + *face, + neighbor_pos, + LinkType::Default, + 0, + id, + true, + ); + } + } + _ => {} + } + } + + fn search(&mut self) { + for i in 0..self.graph.node_bound() { + let idx = NodeIdx::new(i); + if !self.graph.contains_node(idx) { + continue; + } + let node = &self.graph[idx]; + self.search_node(idx, node.block.unwrap()); + } + } +} + +fn is_wire(world: &dyn World, pos: BlockPos) -> bool { + matches!(world.get_block(pos), Block::RedstoneWire { .. }) +} diff --git a/crates/core/src/redpiler/passes/mod.rs b/crates/core/src/redpiler/passes/mod.rs new file mode 100644 index 00000000..1bfd1175 --- /dev/null +++ b/crates/core/src/redpiler/passes/mod.rs @@ -0,0 +1,74 @@ +mod clamp_weights; +mod coalesce; +mod constant_coalesce; +mod constant_fold; +mod dedup_links; +mod identify_nodes; +mod input_search; + +use super::compile_graph::CompileGraph; +use super::{CompilerInput, CompilerOptions}; +use log::trace; +use std::time::Instant; + +pub const DEFAULT_PASS_MANAGER: PassManager<'_> = PassManager::new(&[ + &identify_nodes::IdentifyNodes, + &input_search::InputSearch, + &clamp_weights::ClampWeights, + &dedup_links::DedupLinks, + &constant_coalesce::ConstantCoalesce, + &constant_fold::ConstantFold, + &coalesce::Coalesce, +]); + +pub struct PassManager<'p> { + passes: &'p [&'p dyn Pass], +} + +impl<'p> PassManager<'p> { + pub const fn new(passes: &'p [&dyn Pass]) -> Self { + Self { passes } + } + + pub fn run_passes(&self, options: &CompilerOptions, input: CompilerInput<'_>) -> CompileGraph { + let mut graph = CompileGraph::new(); + + for &pass in self.passes { + if !pass.should_run(options) { + trace!("Skipping pass: {}", pass.name()); + continue; + } + + trace!("Running pass: {}", pass.name()); + let start = Instant::now(); + + pass.run_pass(&mut graph, options, &input); + + trace!("Completed pass in {:?}", start.elapsed()); + trace!("node_count: {}", graph.node_count()); + trace!("edge_count: {}", graph.edge_count()); + } + + graph + } +} + +pub trait Pass { + fn run_pass( + &self, + graph: &mut CompileGraph, + options: &CompilerOptions, + input: &CompilerInput<'_>, + ); + + /// This name should only be use for debugging purposes, + /// it is not a valid identifier of the pass. + fn name(&self) -> &'static str { + std::any::type_name::() + } + + fn should_run(&self, options: &CompilerOptions) -> bool { + // Run passes for optimized builds by default + options.optimize + } +} diff --git a/crates/core/src/server.rs b/crates/core/src/server.rs index 48dbdabb..409f4087 100644 --- a/crates/core/src/server.rs +++ b/crates/core/src/server.rs @@ -147,7 +147,7 @@ impl MinecraftServer { message = message, )); }) - .level(log::LevelFilter::Debug) + .level(log::LevelFilter::Trace) .level_for("regalloc", log::LevelFilter::Warn) .level_for("cranelift_jit", log::LevelFilter::Warn) // .level_for("cranelift_codegen::machinst::compile", log::LevelFilter::Debug)