use crate::definition_region::DefinitionRegion;
use crate::simulation::typed_executor::{
IoLayoutBuilder, IoType, LayoutFunction, SortStrategy, StateMode, TypedCircuitExecutor,
};
use crate::simulation::{MchprsWorld, SimulationOptions};
use crate::UniversalSchematic;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum ValidationError {
OverlappingRegions {
name1: String,
name2: String,
overlap_count: usize,
},
NoInputs,
NoOutputs,
EmptyInput(String),
EmptyOutput(String),
Custom(String),
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValidationError::OverlappingRegions {
name1,
name2,
overlap_count,
} => write!(
f,
"IO regions '{}' and '{}' overlap at {} positions",
name1, name2, overlap_count
),
ValidationError::NoInputs => write!(f, "No inputs defined"),
ValidationError::NoOutputs => write!(f, "No outputs defined"),
ValidationError::EmptyInput(name) => write!(f, "Input '{}' has no positions", name),
ValidationError::EmptyOutput(name) => write!(f, "Output '{}' has no positions", name),
ValidationError::Custom(msg) => write!(f, "{}", msg),
}
}
}
impl std::error::Error for ValidationError {}
pub struct CircuitBuilder {
schematic: UniversalSchematic,
layout_builder: IoLayoutBuilder,
simulation_options: SimulationOptions,
state_mode: StateMode,
input_regions: HashMap<String, Vec<(i32, i32, i32)>>,
output_regions: HashMap<String, Vec<(i32, i32, i32)>>,
}
impl CircuitBuilder {
pub fn new(schematic: UniversalSchematic) -> Self {
Self {
schematic,
layout_builder: IoLayoutBuilder::new(),
simulation_options: SimulationOptions::default(),
state_mode: StateMode::Stateless,
input_regions: HashMap::new(),
output_regions: HashMap::new(),
}
}
pub fn from_insign(schematic: UniversalSchematic) -> Result<Self, String> {
use crate::insign;
use crate::simulation::typed_executor::insign_io;
let signs = insign::extract_signs(&schematic);
let input: Vec<([i32; 3], String)> = signs.into_iter().map(|s| (s.pos, s.text)).collect();
let layout_builder = insign_io::parse_io_layout_from_insign(&input, &schematic)
.map_err(|e| e.to_string())?;
let mut schematic = schematic;
let air_block = crate::BlockState::new("minecraft:air");
for (pos, _) in &input {
schematic.set_block(pos[0], pos[1], pos[2], &air_block);
}
let layout = layout_builder.build();
let mut input_regions = HashMap::new();
let mut output_regions = HashMap::new();
for (name, mapping) in &layout.inputs {
input_regions.insert(name.clone(), mapping.positions.clone());
}
for (name, mapping) in &layout.outputs {
output_regions.insert(name.clone(), mapping.positions.clone());
}
let mut new_builder = IoLayoutBuilder::new();
for (name, mapping) in layout.inputs {
new_builder =
new_builder.add_input(name, mapping.io_type, mapping.layout, mapping.positions)?;
}
for (name, mapping) in layout.outputs {
new_builder =
new_builder.add_output(name, mapping.io_type, mapping.layout, mapping.positions)?;
}
Ok(Self {
schematic,
layout_builder: new_builder,
simulation_options: SimulationOptions::default(),
state_mode: StateMode::Stateless,
input_regions,
output_regions,
})
}
pub fn with_input(
self,
name: impl Into<String>,
io_type: IoType,
layout: LayoutFunction,
region: DefinitionRegion,
) -> Result<Self, String> {
self.with_input_sorted(name, io_type, layout, region, SortStrategy::default())
}
pub fn with_input_sorted(
mut self,
name: impl Into<String>,
io_type: IoType,
layout: LayoutFunction,
region: DefinitionRegion,
sort: SortStrategy,
) -> Result<Self, String> {
let name = name.into();
let raw_positions: Vec<_> = region.iter_positions().collect();
let positions = sort.sort(&raw_positions);
if positions.is_empty() {
return Err(format!("Input '{}' has no positions", name));
}
self.input_regions.insert(name.clone(), positions.clone());
self.layout_builder = self
.layout_builder
.add_input(name, io_type, layout, positions)?;
Ok(self)
}
pub fn with_input_auto(
self,
name: impl Into<String>,
io_type: IoType,
region: DefinitionRegion,
) -> Result<Self, String> {
self.with_input_auto_sorted(name, io_type, region, SortStrategy::default())
}
pub fn with_input_auto_sorted(
mut self,
name: impl Into<String>,
io_type: IoType,
region: DefinitionRegion,
sort: SortStrategy,
) -> Result<Self, String> {
let name = name.into();
let raw_positions: Vec<_> = region.iter_positions().collect();
let positions = sort.sort(&raw_positions);
if positions.is_empty() {
return Err(format!("Input '{}' has no positions", name));
}
self.input_regions.insert(name.clone(), positions.clone());
self.layout_builder = self
.layout_builder
.add_input_auto(name, io_type, positions)?;
Ok(self)
}
pub fn with_output(
self,
name: impl Into<String>,
io_type: IoType,
layout: LayoutFunction,
region: DefinitionRegion,
) -> Result<Self, String> {
self.with_output_sorted(name, io_type, layout, region, SortStrategy::default())
}
pub fn with_output_sorted(
mut self,
name: impl Into<String>,
io_type: IoType,
layout: LayoutFunction,
region: DefinitionRegion,
sort: SortStrategy,
) -> Result<Self, String> {
let name = name.into();
let raw_positions: Vec<_> = region.iter_positions().collect();
let positions = sort.sort(&raw_positions);
if positions.is_empty() {
return Err(format!("Output '{}' has no positions", name));
}
self.output_regions.insert(name.clone(), positions.clone());
self.layout_builder = self
.layout_builder
.add_output(name, io_type, layout, positions)?;
Ok(self)
}
pub fn with_output_auto(
self,
name: impl Into<String>,
io_type: IoType,
region: DefinitionRegion,
) -> Result<Self, String> {
self.with_output_auto_sorted(name, io_type, region, SortStrategy::default())
}
pub fn with_output_auto_sorted(
mut self,
name: impl Into<String>,
io_type: IoType,
region: DefinitionRegion,
sort: SortStrategy,
) -> Result<Self, String> {
let name = name.into();
let raw_positions: Vec<_> = region.iter_positions().collect();
let positions = sort.sort(&raw_positions);
if positions.is_empty() {
return Err(format!("Output '{}' has no positions", name));
}
self.output_regions.insert(name.clone(), positions.clone());
self.layout_builder = self
.layout_builder
.add_output_auto(name, io_type, positions)?;
Ok(self)
}
pub fn with_options(mut self, options: SimulationOptions) -> Self {
self.simulation_options = options;
self
}
pub fn with_state_mode(mut self, mode: StateMode) -> Self {
self.state_mode = mode;
self
}
pub fn validate(&self) -> Result<&Self, ValidationError> {
if self.input_regions.is_empty() {
return Err(ValidationError::NoInputs);
}
if self.output_regions.is_empty() {
return Err(ValidationError::NoOutputs);
}
for (name, positions) in &self.input_regions {
if positions.is_empty() {
return Err(ValidationError::EmptyInput(name.clone()));
}
}
for (name, positions) in &self.output_regions {
if positions.is_empty() {
return Err(ValidationError::EmptyOutput(name.clone()));
}
}
let all_regions: Vec<(&str, &Vec<(i32, i32, i32)>)> = self
.input_regions
.iter()
.map(|(n, p)| (n.as_str(), p))
.chain(self.output_regions.iter().map(|(n, p)| (n.as_str(), p)))
.collect();
for i in 0..all_regions.len() {
for j in (i + 1)..all_regions.len() {
let (name1, positions1) = all_regions[i];
let (name2, positions2) = all_regions[j];
let set1: std::collections::HashSet<_> = positions1.iter().collect();
let overlap_count = positions2.iter().filter(|p| set1.contains(p)).count();
if overlap_count > 0 {
return Err(ValidationError::OverlappingRegions {
name1: name1.to_string(),
name2: name2.to_string(),
overlap_count,
});
}
}
}
Ok(self)
}
pub fn build(self) -> Result<TypedCircuitExecutor, String> {
let layout = self.layout_builder.build();
let mut options = self.simulation_options;
for mapping in layout.inputs.values().chain(layout.outputs.values()) {
for &(x, y, z) in &mapping.positions {
let pos = crate::simulation::BlockPos::new(x, y, z);
if !options.custom_io.contains(&pos) {
options.custom_io.push(pos);
}
}
}
let world = MchprsWorld::with_options(self.schematic, options.clone())?;
let mut executor = TypedCircuitExecutor::from_layout_with_options(world, layout, options);
executor.set_state_mode(self.state_mode);
Ok(executor)
}
pub fn build_validated(self) -> Result<TypedCircuitExecutor, String> {
self.validate().map_err(|e| e.to_string())?;
self.build()
}
pub fn input_count(&self) -> usize {
self.input_regions.len()
}
pub fn output_count(&self) -> usize {
self.output_regions.len()
}
pub fn input_names(&self) -> Vec<&str> {
self.input_regions.keys().map(|s| s.as_str()).collect()
}
pub fn output_names(&self) -> Vec<&str> {
self.output_regions.keys().map(|s| s.as_str()).collect()
}
}
pub fn create_circuit_from_insign(
schematic: &UniversalSchematic,
) -> Result<TypedCircuitExecutor, String> {
use crate::simulation::typed_executor::insign_io;
insign_io::create_executor_from_insign(schematic).map_err(|e| e.to_string())
}
pub fn create_circuit_from_insign_with_options(
schematic: &UniversalSchematic,
options: SimulationOptions,
) -> Result<TypedCircuitExecutor, String> {
use crate::simulation::typed_executor::insign_io;
insign_io::create_executor_from_insign_with_options(schematic, options)
.map_err(|e| e.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BlockState;
fn create_test_schematic() -> UniversalSchematic {
let mut schematic = UniversalSchematic::new("test".to_string());
for x in 0..10 {
schematic.set_block(x, 0, 0, &BlockState::new("minecraft:stone".to_string()));
}
schematic
}
#[test]
fn test_circuit_builder_creation() {
let schematic = create_test_schematic();
let builder = CircuitBuilder::new(schematic);
assert_eq!(builder.input_count(), 0);
assert_eq!(builder.output_count(), 0);
}
#[test]
fn test_circuit_builder_validation_no_inputs() {
let schematic = create_test_schematic();
let builder = CircuitBuilder::new(schematic);
let result = builder.validate();
assert!(matches!(result, Err(ValidationError::NoInputs)));
}
}