diff --git a/Cargo.toml b/Cargo.toml index 7e4e931..032b1b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,15 @@ members = [ "schemsearch-cli", "schemsearch-lib", + "schemsearch_faster", "schemsearch-files", "schemsearch-sql", "schemsearch-java" ] + +[profile.test] +inherits = "release" +lto = true + +[profile.release] +lto = true \ No newline at end of file diff --git a/schemsearch-cli/Cargo.toml b/schemsearch-cli/Cargo.toml index 6a37a78..5f0c51d 100644 --- a/schemsearch-cli/Cargo.toml +++ b/schemsearch-cli/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" [dependencies] schemsearch-lib = { path = "../schemsearch-lib" } -schemsearch-files = { path = "../schemsearch-files" } \ No newline at end of file +schemsearch-files = { path = "../schemsearch-files" } +clap = { version = "4.1.8", features = ["cargo"] } diff --git a/schemsearch-cli/src/main.rs b/schemsearch-cli/src/main.rs index dae5457..4ee633b 100644 --- a/schemsearch-cli/src/main.rs +++ b/schemsearch-cli/src/main.rs @@ -1,13 +1,203 @@ -use std::path::Path; +use std::fmt::format; +use std::fs::File; +use std::io::{BufWriter, Stdout, StdoutLock, Write}; +use clap::{command, Arg, ArgAction, ColorChoice, value_parser, Command}; use schemsearch_files::Schematic; use schemsearch_lib::pattern_mapper::match_palette; +use std::path::Path; +use clap::ArgAction::Help; +use clap::error::ErrorKind; +use schemsearch_lib::{search, SearchBehavior}; fn main() { - let schematic = Schematic::load(Path::new("tests/simple.schem")); - let endstone = Schematic::load(Path::new("tests/endstone.schem")); + let mut cmd = command!("schemsearch") + .arg( + Arg::new("pattern") + .help("The pattern to search for") + .required(true) + .action(ArgAction::Set), + ) + .arg( + Arg::new("schematic") + .help("The schematics to search in") + .required(true) + .action(ArgAction::Append), + ) + .arg( + Arg::new("ignore-data") + .help("Ignores block data when searching") + .short('d') + .long("ignore-data") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("ignore-block-entities") + .help("Ignores block entities when searching") + .short('b') + .long("ignore-block-entities") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("ignore-entities") + .help("Ignores entities when searching") + .short('e') + .long("ignore-entities") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("ignore-air") + .help("Ignores air when searching") + .short('a') + .long("ignore-air") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("air-as-any") + .help("Treats air as any block when searching") + .short('A') + .long("air-as-any") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("output") + .help("The output format") + .short('o') + .long("output") + .action(ArgAction::Append) + .default_value("std") + .value_parser(["std_csv", "file_csv", "std"]), + ) + .arg( + Arg::new("output-file") + .help("The output file") + .short('O') + .long("output-file") + .action(ArgAction::Append) + ) + .arg( + Arg::new("threshold") + .help("The threshold for the search") + .short('t') + .long("threshold") + .action(ArgAction::Set) + .default_value("0.9") + .value_parser(|s: &str| s.parse::().map_err(|e| e.to_string())), + ) + .about("Searches for a pattern in a schematic") + .bin_name("schemsearch"); - let (matched_schematic, matched_endstone) = match_palette(&schematic, &endstone, true); + let matches = cmd.get_matches_mut(); - println!("{:?}", matched_schematic); - println!("{:?}", matched_endstone); + if matches.contains_id("help") { + return; + } + + let search_behavior = SearchBehavior { + ignore_block_data: matches.get_flag("ignore-data"), + ignore_block_entities: matches.get_flag("ignore-block-entities"), + ignore_air: matches.get_flag("ignore-air"), + air_as_any: matches.get_flag("air-as-any"), + ignore_entities: matches.get_flag("ignore-entities"), + threshold: *matches.get_one::("threshold").expect("Couldn't get threshold"), + }; + + let pattern = match Schematic::load(Path::new(matches.get_one::("pattern").unwrap())) { + Ok(x) => x, + Err(e) => { + cmd.error(ErrorKind::Io, format!("Error while loading Pattern: {}", e.to_string())).exit(); + } + }; + + let schematics = matches.get_many::("schematic").expect("Couldn't get schematics"); + + let mut output_std = false; + let mut output_std_csv = false; + let mut output_file_csv = false; + let mut output_file = false; + + for x in matches.get_many::("output").expect("Couldn't get output") { + match x.as_str() { + "std" => output_std = true, + "std_csv" => output_std_csv = true, + "file_csv" => output_file_csv = true, + "file" => output_file = true, + _ => {} + } + }; + + let mut stdout = std::io::stdout(); + let mut lock = stdout.lock(); + + let mut file: Option = None; + let mut file_out: Option> = None; + + if output_file || output_file_csv { + let output_file_path = match matches.get_one::("output-file") { + None => { + cmd.error(ErrorKind::MissingRequiredArgument, "No output file specified").exit(); + } + Some(x) => x + }; + + file = match std::fs::File::create(output_file_path) { + Ok(x) => Some(x), + Err(e) => { + cmd.error(ErrorKind::Io, format!("Error while creating output file: {}", e.to_string())).exit(); + } + }; + file_out = Some(BufWriter::new(file.unwrap())); + } + + for schem_path in schematics { + let path = Path::new(schem_path); + if path.is_dir() { + match path.read_dir() { + Ok(x) => { + for path in x { + match path { + Ok(x) => { + if x.path().extension().unwrap_or_default() == "schem" { + search_schempath(&mut cmd, search_behavior, &pattern, &mut output_std, &mut output_std_csv, &mut output_file_csv, &mut output_file, &mut lock, &mut file_out, &x.path()); + } + } + Err(e) => cmd.error(ErrorKind::Io, format!("Error while reading dir: {}", e.to_string())).exit() + } + } + } + Err(e) => cmd.error(ErrorKind::Io, "Expected to be a dir").exit() + } + } else { + search_schempath(&mut cmd, search_behavior, &pattern, &mut output_std, &mut output_std_csv, &mut output_file_csv, &mut output_file, &mut lock, &mut file_out, path) + } + } +} + +fn search_schempath(cmd: &mut Command, search_behavior: SearchBehavior, pattern: &Schematic, output_std: &mut bool, output_std_csv: &mut bool, output_file_csv: &mut bool, output_file: &mut bool, stdout: &mut StdoutLock, file_out: &mut Option>, schem_path: &Path) { + let schematic = match Schematic::load(schem_path) { + Ok(x) => x, + Err(e) => { + cmd.error(ErrorKind::Io, format!("Error while loading Schematic ({}): {}", schem_path.file_name().unwrap().to_str().unwrap(), e.to_string())).exit(); + } + }; + + if *output_std { + writeln!(stdout, "Searching in schematic: {}", schem_path.file_name().unwrap().to_str().unwrap()).unwrap(); + } + + let matches = search(&schematic, &pattern, search_behavior); + + for x in matches { + if *output_std { + writeln!(stdout, "Found match at x: {}, y: {}, z: {}", x.0, x.1, x.2).unwrap(); + } + if *output_std_csv { + writeln!(stdout, "{},{},{},{}", schem_path.file_name().unwrap().to_str().unwrap(), x.0, x.1, x.2).unwrap(); + } + if *output_file { + writeln!(file_out.as_mut().unwrap(), "Found match at x: {}, y: {}, z: {}", x.0, x.1, x.2).unwrap(); + } + if *output_file_csv { + writeln!(file_out.as_mut().unwrap(), "{},{},{},{}", schem_path.file_name().unwrap().to_str().unwrap(), x.0, x.1, x.2).unwrap(); + } + } } diff --git a/schemsearch-files/src/lib.rs b/schemsearch-files/src/lib.rs index 9201edb..fe005c0 100644 --- a/schemsearch-files/src/lib.rs +++ b/schemsearch-files/src/lib.rs @@ -1,6 +1,7 @@ use std::path::Path; use nbt::{Map, Value}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde::de::Error; #[derive(Serialize, Deserialize, Debug)] pub struct Schematic { @@ -22,14 +23,22 @@ pub struct Schematic { pub palette_max: i32, #[serde(rename = "Palette")] pub palette: Map, - #[serde(rename = "BlockData")] - pub block_data: Vec, + #[serde(rename = "BlockData", deserialize_with = "read_blockdata")] + pub block_data: Vec, #[serde(rename = "BlockEntities")] pub block_entities: Vec, #[serde(rename = "Entities")] pub entities: Option>, } +fn read_blockdata<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, +{ + let s: Vec = Deserialize::deserialize(deserializer)?; + Ok(read_varint_array(&s)) +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BlockEntity { #[serde(rename = "Id")] @@ -47,17 +56,16 @@ pub struct Entity { } impl Schematic { - pub fn load(path: &Path) -> Schematic { - let file = std::fs::File::open(path).expect("Failed to open file"); + pub fn load(path: &Path) -> Result { + let file = match std::fs::File::open(path) { + Ok(x) => x, + Err(_) => return Err(format!("Failed to open file: {}", path.display())) + }; let schematic: Schematic = match nbt::from_gzip_reader(file) { Ok(schem) => schem, - Err(e) => panic!("Failed to parse schematic: {}", e), + Err(e) => return Err(format!("Failed to parse schematic: {}", e)) }; - schematic - } - - pub fn read_blockdata(&self) -> Vec { - read_varint_array(&self.block_data) + Ok(schematic) } } @@ -89,20 +97,3 @@ pub fn read_varint_array(read: &Vec) -> Vec { } data } - -pub fn to_varint_array(data: &Vec) -> Vec { - let mut bytes: Vec = Vec::new(); - for value in data { - let mut value = *value as u32; - 'inner: loop { - if (value & 0x80) == 0 { - bytes.push(value as u8); - break 'inner; - } - - bytes.push((value & 0x7F) as u8 | 0x80); - value >>= 7; - } - } - bytes -} \ No newline at end of file diff --git a/schemsearch-java/src/lib.rs b/schemsearch-java/src/lib.rs index 038cfd8..8c1f44f 100644 --- a/schemsearch-java/src/lib.rs +++ b/schemsearch-java/src/lib.rs @@ -1,27 +1,29 @@ -#![no_mangle] - +use std::path::Path; use jni::JNIEnv; use jni::objects::{JClass, JString}; use jni::sys::jstring; +use schemsearch_files::Schematic; use schemsearch_lib::{search, SearchBehavior}; +#[no_mangle] pub extern "system" fn Java_SchemSearch_search<'local>(mut env: JNIEnv<'local>, class: JClass<'local>, schematic_path: JString<'local>, pattern_path: JString<'local>) -> jstring { let schematic_path: String = env.get_string(&schematic_path).expect("Couldn't get java string!").into(); let pattern_path: String = env.get_string(&pattern_path).expect("Couldn't get java string!").into(); - let file = std::fs::File::open(schematic_path).expect("Failed to open file"); - let schematic = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect(); - let file = std::fs::File::open(pattern_path).expect("Failed to open file"); - let pattern = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect(); + let schematic = Schematic::load(Path::new(&schematic_path)); + let pattern = Schematic::load(Path::new(&pattern_path)); - let matches = search(schematic, pattern, SearchBehavior { + let matches = search(&schematic, &pattern, SearchBehavior { ignore_block_data: true, ignore_block_entities: true, ignore_entities: true, + ignore_air: false, + air_as_any: false, + threshold: 0.0, }); let mut result = String::new(); diff --git a/schemsearch-lib/src/lib.rs b/schemsearch-lib/src/lib.rs index 76de0bf..1811fe5 100644 --- a/schemsearch-lib/src/lib.rs +++ b/schemsearch-lib/src/lib.rs @@ -1,23 +1,23 @@ +pub mod pattern_mapper; + use pattern_mapper::match_palette; use schemsearch_files::Schematic; -pub mod pattern_mapper; - #[derive(Debug, Clone, Copy)] pub struct SearchBehavior { pub ignore_block_data: bool, pub ignore_block_entities: bool, + pub ignore_air: bool, + pub air_as_any: bool, pub ignore_entities: bool, + pub threshold: f64, } pub fn search( - data: &Vec, - pattern: &Vec, + schem: &Schematic, + pattern_schem: &Schematic, search_behavior: SearchBehavior, ) -> Vec<(u16, u16, u16)> { - let schem: Schematic = parse_schematic(data); - let pattern_schem: Schematic = parse_schematic(pattern); - if schem.width < pattern_schem.width || schem.height < pattern_schem.height || schem.length < pattern_schem.length { return vec![]; } @@ -26,52 +26,44 @@ pub fn search( return vec![]; } - let (schem, pattern_schem) = match_palette(&schem, &pattern_schem, search_behavior.ignore_block_data); + let pattern_schem = match match_palette(&schem, &pattern_schem, search_behavior.ignore_block_data) { + Some(x) => x, + None => return vec![], + }; let mut matches: Vec<(u16, u16, u16)> = Vec::new(); - println!("{:?}", schem); - println!("{:?}", pattern_schem); + let pattern_data = pattern_schem.block_data; + let schem_data = &schem.block_data; + let air_id = if search_behavior.ignore_air || search_behavior.air_as_any { pattern_schem.palette.get("minecraft:air").unwrap_or(&-1) } else { &-1}; - let pattern_data = pattern_schem.read_blockdata(); - let schem_data = schem.read_blockdata(); + let pattern_blocks = (pattern_schem.width * pattern_schem.height * pattern_schem.length) as f64; - for x in 0..=schem.width - pattern_schem.width { - for y in 0..=schem.height - pattern_schem.height { - for z in 0..=schem.length - pattern_schem.length { - let mut match_found = true; - 'outer: for i in 0..pattern_schem.width { - for j in 0..pattern_schem.height { - for k in 0..pattern_schem.length { - let index = (x + i) + (y + j) * schem.width + (z + k) * schem.width * schem.height; - let pattern_index = i + j * pattern_schem.width + k * pattern_schem.width * pattern_schem.height; - if schem_data.get(index as usize) != pattern_data.get(pattern_index as usize) { - match_found = false; - break 'outer; + for x in 0..=schem.width as usize - pattern_schem.width as usize { + for y in 0..=schem.height as usize - pattern_schem.height as usize { + for z in 0..=schem.length as usize - pattern_schem.length as usize { + let mut matching = 0; + for i in 0..pattern_schem.width as usize { + for j in 0..pattern_schem.height as usize { + for k in 0..pattern_schem.length as usize { + let index = (x + i) + (y + j) * (schem.width as usize) + (z + k) * (schem.width as usize) * (schem.height as usize); + let pattern_index = i + j * pattern_schem.width as usize + k * pattern_schem.width as usize * pattern_schem.height as usize; + let data = schem_data.get(index as usize).expect("Index out of bounds"); + let pattern_data = pattern_data.get(pattern_index as usize).expect("Index out of bounds"); + if data == pattern_data || (search_behavior.ignore_air && *data == *air_id) || (search_behavior.air_as_any && *pattern_data == *air_id) { + matching += 1; } } } } - if match_found { - matches.push((x, y, z)); + if matching as f64 / pattern_blocks > search_behavior.threshold { + matches.push((x as u16, y as u16, z as u16)); } } } } - /* - [ - 0, -1, 1, 1, 2, - 0, -1, 2, 1, 0, - 2, -1, -1, 2, -1, - 2, 0, 0, 2, -1, - 2, 1, 2, 2, 1 - ] - - */ - return matches; - } pub fn normalize_data(data: &String, ignore_data: bool) -> String { @@ -104,7 +96,6 @@ mod tests { let schematic = Schematic::load(Path::new("../tests/simple.schem")); assert_eq!(schematic.width as usize * schematic.height as usize * schematic.length as usize, schematic.block_data.len()); assert_eq!(schematic.palette_max, schematic.palette.len() as i32); - println!("{:?}", schematic); } #[test] @@ -113,7 +104,6 @@ mod tests { let schematic: Schematic = parse_schematic(&std::io::Read::bytes(file).map(|b| b.unwrap()).collect()); assert_eq!(schematic.width as usize * schematic.height as usize * schematic.length as usize, schematic.block_data.len()); assert_eq!(schematic.palette_max, schematic.palette.len() as i32); - println!("{:?}", schematic); } #[test] @@ -122,7 +112,6 @@ mod tests { let stripped = strip_data(&schematic); assert_eq!(stripped.palette.keys().any(|k| k.contains('[')), false); - println!("{:?}", stripped); } #[test] @@ -130,17 +119,38 @@ mod tests { let schematic = Schematic::load(Path::new("../tests/simple.schem")); let endstone = Schematic::load(Path::new("../tests/endstone.schem")); - let (matched_schematic, matched_endstone) = match_palette(&schematic, &endstone, true); + let matched_schematic = match_palette(&schematic, &endstone, true); + } - println!("{:?}", matched_schematic); - println!("{:?}", matched_endstone); + #[test] + fn test_match_palette_ignore_data() { + let schematic = Schematic::load(Path::new("../tests/simple.schem")); + let endstone = Schematic::load(Path::new("../tests/endstone.schem")); + + let matched_schematic = match_palette(&schematic, &endstone, false); + } + + #[test] + pub fn test_big_search() { + let file = std::fs::File::open("../tests/simple.schem").expect("Failed to open file"); + let schematic = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect(); + let file = std::fs::File::open("../tests/endstone.schem").expect("Failed to open file"); + let pattern = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect(); + + let matches = search(schematic, pattern, SearchBehavior { + ignore_block_data: true, + ignore_block_entities: true, + ignore_entities: true, + ignore_air: false, + air_as_any: false, + threshold: 0.9 + }); } #[test] pub fn test_search() { let file = std::fs::File::open("../tests/Random.schem").expect("Failed to open file"); let schematic = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect(); - println!("{:?}", schematic); let file = std::fs::File::open("../tests/Pattern.schem").expect("Failed to open file"); let pattern = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect(); @@ -148,6 +158,9 @@ mod tests { ignore_block_data: true, ignore_block_entities: true, ignore_entities: true, + ignore_air: false, + air_as_any: false, + threshold: 0.9 }); println!("{:?}", matches); diff --git a/schemsearch-lib/src/pattern_mapper.rs b/schemsearch-lib/src/pattern_mapper.rs index 4a06f3c..38a7b97 100644 --- a/schemsearch-lib/src/pattern_mapper.rs +++ b/schemsearch-lib/src/pattern_mapper.rs @@ -1,5 +1,5 @@ use nbt::Map; -use schemsearch_files::{Schematic, to_varint_array}; +use schemsearch_files::Schematic; use crate::normalize_data; fn create_reverse_palette(schem: &Schematic) -> Vec { @@ -17,7 +17,7 @@ pub fn strip_data(schem: &Schematic) -> Schematic { let mut palette: Map = Map::new(); let mut palette_max: i32 = 0; let reverse_palette = create_reverse_palette(schem); - let dat = schem.read_blockdata(); + let dat = &schem.block_data; for block in dat.iter() { let block_name = reverse_palette[*block as usize].clone(); @@ -36,7 +36,7 @@ pub fn strip_data(schem: &Schematic) -> Schematic { data_version: schem.data_version, palette, palette_max, - block_data: to_varint_array(&data), + block_data: data, block_entities: schem.block_entities.clone(), height: schem.height, length: schem.length, @@ -47,24 +47,27 @@ pub fn strip_data(schem: &Schematic) -> Schematic { } } -fn match_palette_adapt(schem: &Schematic, matching_palette: Map, ignore_data: bool) -> Vec { +fn match_palette_adapt(schem: &Schematic, matching_palette: &Map, ignore_data: bool) -> Option> { let mut data: Vec = Vec::new(); - for x in schem.read_blockdata().iter() { + for x in schem.block_data.iter() { let blockname = schem.palette.iter().find(|(_, &v)| v == *x).expect("Invalid Schematic").0; let blockname = if ignore_data { normalize_data(&blockname, ignore_data) } else { blockname.clone() }; - let block_id = matching_palette.get(&blockname).unwrap_or(&-1); + let block_id = match matching_palette.get(&blockname) { + None => return None, + Some(x) => x + }; data.push(*block_id); } - data + Some(data) } pub fn match_palette( schem: &Schematic, pattern: &Schematic, ignore_data: bool, -) -> (Schematic, Schematic) { +) -> Option { if ignore_data { match_palette_internal(&strip_data(schem), &strip_data(pattern), ignore_data) } else { @@ -76,46 +79,21 @@ fn match_palette_internal( schem: &Schematic, pattern: &Schematic, ignore_data: bool, -) -> (Schematic, Schematic) { - - if schem.palette.len() < pattern.palette.len() { - panic!("Schematic palette is larger than pattern palette"); +) -> Option { + if pattern.palette.iter().any(|(k, _)| schem.palette.get(k).is_none()) { + return None; } - let mut matching_palette: Map = Map::new(); - let mut matching_palette_max: i32 = 0; - - for (block_name, _) in pattern.palette.iter() { - let block_name = normalize_data(block_name, true); - let schem_block_id = pattern.palette.get(&block_name).expect("Pattern block not found in schematic palette"); - matching_palette.insert(block_name, *schem_block_id); - matching_palette_max += 1; - } - - let data_schem: Vec = match_palette_adapt(&schem, matching_palette.clone(), true); - - let data_pattern: Vec = match_palette_adapt(&pattern, matching_palette.clone(), true); - - let schem = Schematic { - version: schem.version.clone(), - data_version: schem.data_version.clone(), - palette: matching_palette.clone(), - palette_max: matching_palette_max.clone(), - block_data: to_varint_array(&data_schem), - block_entities: schem.block_entities.clone(), - height: schem.height.clone(), - length: schem.length.clone(), - width: schem.width.clone(), - metadata: schem.metadata.clone(), - offset: schem.offset.clone(), - entities: None, + let data_pattern: Vec = match match_palette_adapt(&pattern, &schem.palette, ignore_data) { + None => return None, + Some(x) => x }; - let pattern = Schematic { + Some(Schematic { version: pattern.version.clone(), data_version: pattern.data_version.clone(), - palette: matching_palette.clone(), - palette_max: matching_palette_max.clone(), - block_data: to_varint_array(&data_pattern), + palette: schem.palette.clone(), + palette_max: schem.palette_max, + block_data: data_pattern, block_entities: pattern.block_entities.clone(), height: pattern.height.clone(), length: pattern.length.clone(), @@ -123,7 +101,5 @@ fn match_palette_internal( metadata: pattern.metadata.clone(), offset: pattern.offset.clone(), entities: None, - }; - - (schem, pattern) + }) } \ No newline at end of file diff --git a/schemsearch_faster/Cargo.toml b/schemsearch_faster/Cargo.toml new file mode 100644 index 0000000..094ccb1 --- /dev/null +++ b/schemsearch_faster/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "schemsearch_faster" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +schemsearch-lib = { path = "../schemsearch-lib" } +schemsearch-files = { path = "../schemsearch-files" } +hematite-nbt = "0.5.2" diff --git a/schemsearch_faster/src/lib.rs b/schemsearch_faster/src/lib.rs new file mode 100644 index 0000000..8ff6e62 --- /dev/null +++ b/schemsearch_faster/src/lib.rs @@ -0,0 +1,55 @@ +use nbt::Map; +use schemsearch_files::Schematic; + +pub fn convert_to_search_space(schem: &Schematic, palette: &Vec) -> Vec> { + let mut data: Vec> = Vec::with_capacity(palette.len()); + let block_data = &schem.block_data; + for name in palette { + let mut output: Vec = Vec::with_capacity(block_data.len()); + for block in block_data.iter() { + if schem.palette.get(name).unwrap_or(&-1) == block { + output.push(1); + } else { + output.push(0); + } + } + data.push(output); + } + data +} + +pub fn unwrap_palette(palette: &Map) -> Vec { + let mut output: Vec = Vec::with_capacity(palette.len()); + (0..palette.len()).for_each(|_| output.push(String::new())); + for (key, id) in palette.iter() { + output[*id as usize] = key.clone(); + } + output +} + +#[allow(unused_imports)] +mod tests { + use std::path::Path; + use schemsearch_files::Schematic; + use crate::{convert_to_search_space, unwrap_palette}; + + #[test] + pub fn test() { + let schematic = Schematic::load(Path::new("../tests/Pattern.schem")); + dbg!(convert_to_search_space(&schematic, &unwrap_palette(&schematic.palette))); + } + + #[test] + pub fn test_2() { + let schematic = Schematic::load(Path::new("../tests/Pattern.schem")); + let schematic2 = Schematic::load(Path::new("../tests/Random.schem")); + println!("{:?}", convert_to_search_space(&schematic2, &unwrap_palette(&schematic.palette))); + } + + #[test] + pub fn test_big() { + let schematic = Schematic::load(Path::new("../tests/endstone.schem")); + let schematic2 = Schematic::load(Path::new("../tests/simple.schem")); + let _ = convert_to_search_space(&schematic2, &unwrap_palette(&schematic.palette)); + } +} \ No newline at end of file