1 Commits

Author SHA1 Message Date
d48910ecb4 A very rude implementation for Tile-Entity Search 2023-03-21 14:21:30 +01:00
14 changed files with 101 additions and 275 deletions

View File

@ -1,40 +0,0 @@
name: Bug Report
description: Create a report to fix a bug
labels: [bug]
title: "[BUG] <title>"
body:
- type: textarea
id: description
attributes:
label: Description
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: Steps to reproduce the behavior.
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: Actual Behavior
description: A clear and concise description of what actually happened.
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Add any other context about the problem here.
validations:
required: false

View File

@ -1,35 +0,0 @@
name: Feature Request
description: Suggest an idea for this project
title: "[FEATURE] <title>"
labels: [enhancement]
body:
- type: textarea
id: description
attributes:
label: Description
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Add any other context or screenshots about the feature request here.
validations:
required: false

View File

@ -27,7 +27,7 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ env.cache-name }}-
- name: Build
run: make debug
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose -p schemsearch-lib
@ -46,7 +46,7 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ env.cache-name }}-
- name: Build
run: make
run: cargo build --verbose --release -p schemsearch-cli
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2
with:

View File

@ -30,7 +30,7 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ env.cache-name }}-
- name: Build
run: make
run: cargo build --verbose --release -p schemsearch-cli
- name: Create Tarball
if: ${{ matrix.os != 'windows-latest' }}
run: tar -czvf schemsearch-cli-${{ matrix.os }}.tar.gz -C target/release schemsearch-cli

View File

@ -1,28 +0,0 @@
default:
@echo "Building (Release)...";
cargo rustc --release --color=always -p schemsearch-cli -- -C target-feature=+avx2
sql:
@echo "Building (Release)...";
cargo rustc --release --color=always -p schemsearch-cli --features sql -- -C target-feature=+avx2
debug:
@echo "Building (Debug)...";
cargo build -p schemsearch-cli
install: default
@echo "Installing...";
install -Dm755 target/release/schemsearch-cli /usr/bin/schemsearch
uninstall:
@echo "Uninstalling...";
rm -f /usr/bin/schemsearch
java:
@echo "Building Java...";
@echo "WARNING: This is WORK IN PROGRESS!";
javac SchemSearch.java
clean:
@echo "Cleaning...";
cargo clean

View File

@ -4,12 +4,12 @@
---
## WARNING: This is a work in progress and is really simple right now. It will be improved in the future.
| Feature | Status |
|---------------------------|--------|
| Block search | ✅ |
| Block data less search | ✅ |
| Tile entities data search | ❌ |
| Entities search | ❌ |
| Feature | Status |
|------------------------|--------|
| Block search | ✅ |
| Block data less search | ✅ |
| Tile entities search | ❌ |
| Entities search | ❌ |
---
@ -41,13 +41,12 @@ schemsearch-cli --help
---
## Roadmap
A list of features that are planned to be implemented in the future. In order of priority.
- [ ] Full JSON output (Progressbar)
- [ ] Use AVX2 for faster search
- [ ] Tile entities data search
- [ ] Tile entities search
- [ ] Entities search
- [ ] McEdit Schematic support
- [ ] Better error handling
- [ ] Web interface
- [ ] McEdit search
---

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use schemsearch_lib::{Match, SearchBehavior};
use schemsearch_lib::SearchBehavior;
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "event")]
@ -12,8 +12,10 @@ pub enum JsonEvent {
#[derive(Serialize, Deserialize, Debug)]
pub struct FoundEvent {
pub name: String,
#[serde(flatten, rename = "match")]
pub match_: Match,
pub x: u16,
pub y: u16,
pub z: u16,
pub percent: f32,
}
#[derive(Serialize, Deserialize, Debug)]

View File

@ -18,7 +18,6 @@
mod types;
mod json_output;
mod sinks;
mod stderr;
use std::fmt::Debug;
use std::io::Write;
@ -26,7 +25,7 @@ use clap::{command, Arg, ArgAction, ValueHint};
use std::path::PathBuf;
use std::str::FromStr;
use clap::error::ErrorKind;
use schemsearch_lib::{Match, search, SearchBehavior};
use schemsearch_lib::{search, SearchBehavior};
use crate::types::{PathSchematicSupplier, SchematicSupplierType};
#[cfg(feature = "sql")]
use futures::executor::block_on;
@ -38,10 +37,9 @@ use schemsearch_sql::filter::SchematicFilter;
use schemsearch_sql::load_all_schematics;
#[cfg(feature = "sql")]
use crate::types::SqlSchematicSupplier;
use indicatif::*;
use indicatif::{ParallelProgressIterator, ProgressStyle};
use schemsearch_files::Schematic;
use crate::sinks::{OutputFormat, OutputSink};
use crate::stderr::MaschineStdErr;
fn main() {
#[allow(unused_mut)]
@ -67,10 +65,17 @@ fn main() {
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("ignore-block-entities")
.help("Ignores block entities when searching [Not Implemented]")
Arg::new("ignore-tile-entities")
.help("Ignores tile entities when searching [Not Implemented]")
.short('b')
.long("ignore-block-entities")
.long("ignore-tile-entities")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("ignore-tile-entity-data")
.help("Ignores tile entity data when searching [Not Implemented]")
.short('B')
.long("ignore-tile-entity-data")
.action(ArgAction::SetTrue),
)
.arg(
@ -96,7 +101,7 @@ fn main() {
)
.arg(
Arg::new("output")
.help("The output format and path [Format:Path] available formats: text, json, csv; available paths: std, err, (file path)")
.help("The output format and path [Format:Path] available formats: text, json, csv; available paths: std, (file path)")
.short('o')
.long("output")
.action(ArgAction::Append)
@ -141,15 +146,6 @@ fn main() {
.default_value("0")
.value_parser(|s: &str| s.parse::<usize>().map_err(|e| e.to_string())),
)
.arg(
Arg::new("machine")
.help("Output for machines")
.short('m')
.long("machine")
.action(ArgAction::Set)
.default_value("0")
.value_parser(|s: &str| s.parse::<u16>().map_err(|e| e.to_string()))
)
.about("Searches for a pattern in a schematic")
.bin_name("schemsearch");
@ -190,7 +186,8 @@ fn main() {
let search_behavior = SearchBehavior {
ignore_block_data: matches.get_flag("ignore-data"),
ignore_block_entities: matches.get_flag("ignore-block-entities"),
ignore_tile_entities: matches.get_flag("ignore-tile-entities"),
ignore_tile_entity_data: matches.get_flag("ignore-tile-entity-data"),
ignore_air: matches.get_flag("ignore-air"),
air_as_any: matches.get_flag("air-as-any"),
ignore_entities: matches.get_flag("ignore-entities"),
@ -257,21 +254,14 @@ fn main() {
ThreadPoolBuilder::new().num_threads(*matches.get_one::<usize>("threads").expect("Could not get threads")).build_global().unwrap();
let bar = ProgressBar::new(schematics.len() as u64); // "maschine"
bar.set_style(ProgressStyle::with_template("[{elapsed}, ETA: {eta}] {wide_bar} {pos}/{len} {per_sec}").unwrap());
let term_size = *matches.get_one::<u16>("machine").expect("Could not get machine");
if term_size != 0 {
bar.set_draw_target(ProgressDrawTarget::term_like(Box::new(MaschineStdErr { size: term_size })))
}
let matches: Vec<SearchResult> = schematics.par_iter().progress_with(bar).map(|schem| {
let matches: Vec<SearchResult> = schematics.par_iter().progress_with_style(ProgressStyle::with_template("[{elapsed}, ETA: {eta}] {wide_bar} {pos}/{len} {per_sec}").unwrap()).map(|schem| {
match schem {
SchematicSupplierType::PATH(schem) => {
let schematic = match load_schem(&schem.path) {
Some(x) => x,
None => return SearchResult {
name: schem.get_name(),
matches: Vec::default()
matches: vec![]
}
};
SearchResult {
@ -289,10 +279,10 @@ fn main() {
}
}
Err(e) => {
eprintln!("Error while loading schematic ({}): {}", schem.get_name(), e.to_string());
println!("Error while loading schematic ({}): {}", schem.get_name(), e.to_string());
SearchResult {
name: schem.get_name(),
matches: Vec::default()
matches: vec![]
}
}
}
@ -330,6 +320,6 @@ fn load_schem(schem_path: &PathBuf) -> Option<Schematic> {
#[derive(Debug, Clone)]
struct SearchResult {
name: String,
matches: Vec<Match>,
matches: Vec<(u16, u16, u16, f32)>,
}

View File

@ -2,15 +2,12 @@ use std::fs::File;
use std::io::BufWriter;
use std::str::FromStr;
use std::io::Write;
use std::time::Duration;
use indicatif::HumanDuration;
use schemsearch_lib::{Match, SearchBehavior};
use schemsearch_lib::SearchBehavior;
use crate::json_output::{EndEvent, FoundEvent, InitEvent, JsonEvent};
#[derive(Debug, Clone)]
pub enum OutputSink {
Stdout,
Stderr,
File(String),
}
@ -40,7 +37,6 @@ impl FromStr for OutputSink {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"std" => Ok(OutputSink::Stdout),
"err" => Ok(OutputSink::Stderr),
_ => Ok(OutputSink::File(s.to_string()))
}
}
@ -49,21 +45,23 @@ impl FromStr for OutputSink {
impl OutputSink {
pub fn output(&self) -> Box<dyn Write> {
match self {
OutputSink::Stdout => Box::new(std::io::stdout()),
OutputSink::Stderr => Box::new(std::io::stderr()),
OutputSink::Stdout => Box::new(std::io::stdout().lock()),
OutputSink::File(path) => Box::new(BufWriter::new(File::create(path).unwrap()))
}
}
}
impl OutputFormat {
pub fn found_match(&self, name: &String, pos: Match) -> String {
pub fn found_match(&self, name: &String, pos: (u16, u16, u16, f32)) -> String {
match self {
OutputFormat::Text => format!("Found match in '{}' at x: {}, y: {}, z: {}, % = {}\n", name, pos.x, pos.y, pos.z, pos.percent),
OutputFormat::CSV => format!("{},{},{},{},{}\n", name, pos.x, pos.y, pos.z, pos.percent),
OutputFormat::Text => format!("Found match in '{}' at x: {}, y: {}, z: {}, % = {}\n", name, pos.0, pos.1, pos.2, pos.3),
OutputFormat::CSV => format!("{},{},{},{},{}\n", name, pos.0, pos.1, pos.2, pos.3),
OutputFormat::JSON => format!("{}\n", serde_json::to_string(&JsonEvent::Found(FoundEvent {
name: name.clone(),
match_: pos,
x: pos.0,
y: pos.1,
z: pos.2,
percent: pos.3,
})).unwrap())
}
}
@ -82,7 +80,7 @@ impl OutputFormat {
pub fn end(&self, end_time: u128) -> String {
match self {
OutputFormat::Text => format!("Search complete in {}\n", HumanDuration(Duration::from_millis(end_time as u64))),
OutputFormat::Text => format!("Search complete in {}s\n", end_time / 1000),
OutputFormat::CSV => format!("{}\n", end_time),
OutputFormat::JSON => format!("{}\n", serde_json::to_string(&JsonEvent::End(EndEvent{ end_time })).unwrap())
}

View File

@ -1,44 +0,0 @@
use std::fmt::Debug;
use std::io::Write;
use indicatif::TermLike;
#[derive(Debug)]
pub struct MaschineStdErr { pub(crate) size: u16}
impl TermLike for MaschineStdErr {
fn width(&self) -> u16 {
self.size
}
fn move_cursor_up(&self, _: usize) -> std::io::Result<()> {
Ok(())
}
fn move_cursor_down(&self, _: usize) -> std::io::Result<()> {
Ok(())
}
fn move_cursor_right(&self, _: usize) -> std::io::Result<()> {
Ok(())
}
fn move_cursor_left(&self, _: usize) -> std::io::Result<()> {
Ok(())
}
fn write_line(&self, s: &str) -> std::io::Result<()> {
writeln!(std::io::stderr(), "{}", s)
}
fn write_str(&self, s: &str) -> std::io::Result<()> {
write!(std::io::stderr(), "{}", s)
}
fn clear_line(&self) -> std::io::Result<()> {
Ok(())
}
fn flush(&self) -> std::io::Result<()> {
std::io::stderr().flush()
}
}

View File

@ -18,8 +18,6 @@
use std::path::PathBuf;
#[cfg(feature = "sql")]
use futures::executor::block_on;
#[allow(unused_imports)]
use schemsearch_files::Schematic;
#[cfg(feature = "sql")]
use schemsearch_sql::{load_schemdata, SchematicNode};

View File

@ -62,6 +62,9 @@ pub struct BlockEntity {
pub id: String,
#[serde(rename = "Pos")]
pub pos: [i32; 3],
#[serde(flatten)]
pub data: Map<String, Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
@ -70,6 +73,9 @@ pub struct Entity {
pub id: String,
#[serde(rename = "Pos")]
pub pos: [i32; 3],
#[serde(flatten)]
pub data: Map<String, Value>,
}
impl Schematic {

View File

@ -35,18 +35,11 @@ pub extern "system" fn Java_SchemSearch_search<'local>(mut env: JNIEnv<'local>,
let schematic = Schematic::load(&PathBuf::from(&schematic_path)).unwrap();
let pattern = Schematic::load(&PathBuf::from(&pattern_path)).unwrap();
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 matches = search(schematic, &pattern, SearchBehavior::default());
let mut result = String::new();
for m in matches {
result.push_str(&format!("{}, {}, {}, {};", m.x, m.y, m.z, m.percent));
for (x, y, z, p) in matches {
result.push_str(&format!("{}, {}, {}, {};", x, y, z, p));
}
result.remove(result.len() - 1);
let output = env.new_string(result).expect("Couldn't create java string!");

View File

@ -25,18 +25,33 @@ use crate::pattern_mapper::match_palette_adapt;
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
pub struct SearchBehavior {
pub ignore_block_data: bool,
pub ignore_block_entities: bool,
pub ignore_tile_entities: bool,
pub ignore_tile_entity_data: bool,
pub ignore_air: bool,
pub air_as_any: bool,
pub ignore_entities: bool,
pub threshold: f32,
}
impl Default for SearchBehavior {
fn default() -> Self {
SearchBehavior {
ignore_block_data: true,
ignore_tile_entities: true,
ignore_tile_entity_data: false,
ignore_entities: true,
ignore_air: false,
air_as_any: false,
threshold: 90.0,
}
}
}
pub fn search(
schem: Schematic,
pattern_schem: &Schematic,
search_behavior: SearchBehavior,
) -> Vec<Match> {
) -> Vec<(u16, u16, u16, f32)> {
if schem.width < pattern_schem.width || schem.height < pattern_schem.height || schem.length < pattern_schem.length {
return vec![];
}
@ -47,7 +62,7 @@ pub fn search(
let pattern_schem = match_palette(&schem, &pattern_schem, search_behavior.ignore_block_data);
let mut matches: Vec<Match> = Vec::new();
let mut matches: Vec<(u16, u16, u16, f32)> = Vec::new();
let pattern_data = pattern_schem.block_data.as_slice();
@ -61,7 +76,7 @@ pub fn search(
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_blocks = pattern_data.len() as f32;
let pattern_blocks = (pattern_schem.width * pattern_schem.height * pattern_schem.length) as f32 + if search_behavior.ignore_tile_entities { 0.0 } else { pattern_schem.block_entities.len() as f32 };
let pattern_width = pattern_schem.width as usize;
let pattern_height = pattern_schem.height as usize;
@ -88,14 +103,29 @@ pub fn search(
}
}
}
if !search_behavior.ignore_tile_entities {
for tile_entity in &pattern_schem.block_entities {
for entry in &schem.block_entities {
if tile_entity.id.as_str() == entry.id.as_str() {
let pos = tile_entity.pos;
let schem_pos = [pos[0] + x as i32, pos[1] + y as i32, pos[2] + z as i32];
if schem_pos == entry.pos {
if search_behavior.ignore_tile_entity_data {
matching += 1;
} else {
if tile_entity.data == entry.data {
matching += 1;
}
}
}
}
}
}
}
let matching_percent = matching as f32 / pattern_blocks;
if matching_percent >= search_behavior.threshold {
matches.push(Match {
x: x as u16,
y: y as u16,
z: z as u16,
percent: matching_percent,
});
matches.push((x as u16, y as u16, z as u16, matching_percent));
}
}
}
@ -104,25 +134,6 @@ pub fn search(
return matches;
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
pub struct Match {
pub x: u16,
pub y: u16,
pub z: u16,
pub percent: f32,
}
impl Default for Match {
fn default() -> Self {
Self {
x: 0,
y: 0,
z: 0,
percent: 0.0,
}
}
}
#[inline]
pub fn normalize_data(data: &str, ignore_data: bool) -> &str {
if ignore_data {
@ -194,14 +205,7 @@ mod tests {
let schematic = Schematic::load(&PathBuf::from("../tests/simple.schem")).unwrap();
let endstone = Schematic::load(&PathBuf::from("../tests/endstone.schem")).unwrap();
let _ = search(schematic, &endstone, SearchBehavior {
ignore_block_data: true,
ignore_block_entities: true,
ignore_entities: true,
ignore_air: false,
air_as_any: false,
threshold: 0.9
});
let _ = search(schematic, &endstone, SearchBehavior::default());
}
#[test]
@ -209,21 +213,11 @@ mod tests {
let schematic = Schematic::load(&PathBuf::from("../tests/Random.schem")).unwrap();
let pattern = Schematic::load(&PathBuf::from("../tests/Pattern.schem")).unwrap();
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
});
let matches = search(schematic, &pattern, SearchBehavior::default());
println!("{:?}", matches);
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].x, 1);
assert_eq!(matches[0].y, 0);
assert_eq!(matches[0].z, 3);
assert_eq!(matches[0].percent, 1.0);
assert_eq!(matches[0], (1, 0, 3, 1.0));
}
#[test]
@ -231,14 +225,7 @@ mod tests {
let schematic = Schematic::load(&PathBuf::from("../tests/warships/GreyFly-by-Bosslar.schem")).unwrap();
let pattern = Schematic::load(&PathBuf::from("../tests/gray_castle_complex.schem")).unwrap();
let matches = search(schematic, &pattern, SearchBehavior {
ignore_block_data: false,
ignore_block_entities: false,
ignore_entities: false,
ignore_air: false,
air_as_any: false,
threshold: 0.9
});
let matches = search(schematic, &pattern, SearchBehavior::default());
println!("{:?}", matches);
assert_eq!(matches.len(), 1);