diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f45c64..fccf759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,22 @@ ## [Unreleased] - ReleaseDate +### Changed + +- Without `--verbose`, only a single warning is printed at the end of + processing for unknown block/biome types, rather than once for every + section where such a block/biome is encountered. + ## [2.0.2] - 2024-01-07 -### Updated +### Added - Added support for Minecraft 1.20.3+ Minecraft 1.20.3 renamed the `grass` block type to `short_grass`. + +### Changed + - Updated [Leaflet](https://leafletjs.com/) to 1.9.4 - Updated attribution URL to https://github.com/neocturne/MinedMap diff --git a/Cargo.lock b/Cargo.lock index a7b6072..ab7a97a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,9 +161,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.13" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" dependencies = [ "clap_builder", "clap_derive", @@ -171,9 +171,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.12" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" dependencies = [ "anstream", "anstyle", @@ -231,34 +231,28 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.17" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "either" @@ -473,15 +467,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libz-ng-sys" -version = "1.1.12" +version = "1.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dd9f43e75536a46ee0f92b758f6b63846e594e86638c61a9251338a65baea63" +checksum = "81157dde2fd4ad2b45ea3a4bb47b8193b52a6346b678840d91d80d3c2cd166c5" dependencies = [ "cmake", "libc", @@ -713,9 +707,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.75" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -784,9 +778,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] @@ -802,9 +796,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.194" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", diff --git a/src/core/region_processor.rs b/src/core/region_processor.rs index 9e1bb65..0a8ad0e 100644 --- a/src/core/region_processor.rs +++ b/src/core/region_processor.rs @@ -1,6 +1,14 @@ //! The [RegionProcessor] and related functions -use std::{ffi::OsStr, path::PathBuf, sync::mpsc, time::SystemTime}; +use std::{ + ffi::OsStr, + path::PathBuf, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc, + }, + time::SystemTime, +}; use anyhow::{Context, Result}; use rayon::prelude::*; @@ -32,6 +40,8 @@ fn parse_region_filename(file_name: &OsStr) -> Option { enum RegionProcessorStatus { /// Region was processed Ok, + /// Region was processed, unknown blocks or biomes were encountered + OkWithUnknown, /// Region was unchanged and skipped Skipped, /// Reading the region failed, previous processed data is reused @@ -68,6 +78,8 @@ struct SingleRegionProcessor<'a> { processed_region: ProcessedRegion, /// Lightmap intermediate data lightmap: image::GrayAlphaImage, + /// True if any unknown block or biome types were encountered during processing + has_unknown: bool, } impl<'a> SingleRegionProcessor<'a> { @@ -104,6 +116,7 @@ impl<'a> SingleRegionProcessor<'a> { lightmap_needed, processed_region, lightmap, + has_unknown: false, }) } @@ -162,8 +175,10 @@ impl<'a> SingleRegionProcessor<'a> { /// Processes a single chunk fn process_chunk(&mut self, chunk_coords: ChunkCoords, data: world::de::Chunk) -> Result<()> { - let chunk = world::chunk::Chunk::new(&data, self.block_types, self.biome_types) - .with_context(|| format!("Failed to decode chunk {:?}", chunk_coords))?; + let (chunk, has_unknown) = + world::chunk::Chunk::new(&data, self.block_types, self.biome_types) + .with_context(|| format!("Failed to decode chunk {:?}", chunk_coords))?; + self.has_unknown |= has_unknown; let Some(layer::LayerData { blocks, biomes, @@ -231,7 +246,11 @@ impl<'a> SingleRegionProcessor<'a> { self.save_region()?; self.save_lightmap()?; - Ok(RegionProcessorStatus::Ok) + Ok(if self.has_unknown { + RegionProcessorStatus::OkWithUnknown + } else { + RegionProcessorStatus::Ok + }) } } @@ -300,6 +319,8 @@ impl<'a> RegionProcessor<'a> { let (processed_send, processed_recv) = mpsc::channel(); let (error_send, error_recv) = mpsc::channel(); + let has_unknown = AtomicBool::new(false); + self.collect_regions()?.par_iter().try_for_each(|&coords| { let ret = self .process_region(coords) @@ -311,6 +332,10 @@ impl<'a> RegionProcessor<'a> { match ret { RegionProcessorStatus::Ok => processed_send.send(()).unwrap(), + RegionProcessorStatus::OkWithUnknown => { + has_unknown.store(true, Ordering::Relaxed); + processed_send.send(()).unwrap(); + } RegionProcessorStatus::Skipped => {} RegionProcessorStatus::ErrorOk | RegionProcessorStatus::ErrorMissing => { error_send.send(()).unwrap() @@ -335,6 +360,16 @@ impl<'a> RegionProcessor<'a> { errors, ); + if has_unknown.into_inner() { + warn!("Unknown block or biome types found during processing"); + eprint!(concat!( + "\n", + " If you're encountering this issue with an unmodified Minecraft version supported by MinedMap,\n", + " please file a bug report including the output with the --verbose flag.\n", + "\n", + )); + } + // Sort regions in a zig-zag pattern to optimize cache usage regions.sort_unstable_by_key(|&TileCoords { x, z }| (x, if x % 2 == 0 { z } else { -z })); diff --git a/src/world/chunk.rs b/src/world/chunk.rs index 77a543c..993a79e 100644 --- a/src/world/chunk.rs +++ b/src/world/chunk.rs @@ -50,46 +50,13 @@ pub enum Chunk<'a> { Empty, } -/// Inner data structure of [SectionIter] -#[derive(Debug, Clone)] -enum SectionIterInner<'a> { - /// Iterator over sections of [Chunk::V1_18] - V1_18 { - /// Inner iterator into section map - iter: btree_map::Iter<'a, SectionY, (SectionV1_13<'a>, BiomesV1_18<'a>, BlockLight<'a>)>, - }, - /// Iterator over sections of [Chunk::V1_13] - V1_13 { - /// Inner iterator into section map - iter: btree_map::Iter<'a, SectionY, (SectionV1_13<'a>, BlockLight<'a>)>, - /// Chunk biome data - biomes: &'a BiomesV0<'a>, - }, - /// Iterator over sections of [Chunk::V0] - V0 { - /// Inner iterator into section map - iter: btree_map::Iter<'a, SectionY, (SectionV0<'a>, BlockLight<'a>)>, - /// Chunk biome data - biomes: &'a BiomesV0<'a>, - }, - /// Empty iterator over an unpopulated chunk ([Chunk::Empty]) - Empty, -} - -/// Iterator over the sections of a [Chunk] -#[derive(Debug, Clone)] -pub struct SectionIter<'a> { - /// Inner iterator enum - inner: SectionIterInner<'a>, -} - impl<'a> Chunk<'a> { /// Creates a new [Chunk] from a deserialized [de::Chunk] pub fn new( data: &'a de::Chunk, block_types: &'a BlockTypes, biome_types: &'a BiomeTypes, - ) -> Result { + ) -> Result<(Self, bool)> { let data_version = data.data_version.unwrap_or_default(); match &data.chunk { @@ -108,8 +75,9 @@ impl<'a> Chunk<'a> { sections: &'a Vec, block_types: &'a BlockTypes, biome_types: &'a BiomeTypes, - ) -> Result { + ) -> Result<(Self, bool)> { let mut section_map = BTreeMap::new(); + let mut has_unknown = false; for section in sections { match §ion.section { @@ -118,22 +86,27 @@ impl<'a> Chunk<'a> { biomes, block_light, } => { + let (loaded_section, unknown_blocks) = SectionV1_13::new( + data_version, + block_states.data.as_deref(), + &block_states.palette, + block_types, + ) + .with_context(|| format!("Failed to load section at Y={}", section.y))?; + has_unknown |= unknown_blocks; + + let (loaded_biomes, unknown_biomes) = + BiomesV1_18::new(biomes.data.as_deref(), &biomes.palette, biome_types) + .with_context(|| { + format!("Failed to load section biomes at Y={}", section.y) + })?; + has_unknown |= unknown_biomes; + section_map.insert( SectionY(section.y), ( - SectionV1_13::new( - data_version, - block_states.data.as_deref(), - &block_states.palette, - block_types, - ) - .with_context(|| { - format!("Failed to load section at Y={}", section.y) - })?, - BiomesV1_18::new(biomes.data.as_deref(), &biomes.palette, biome_types) - .with_context(|| { - format!("Failed to load section biomes at Y={}", section.y) - })?, + loaded_section, + loaded_biomes, BlockLight::new(block_light.as_deref()).with_context(|| { format!("Failed to load section block light at Y={}", section.y) })?, @@ -144,7 +117,8 @@ impl<'a> Chunk<'a> { }; } - Ok(Chunk::V1_18 { section_map }) + let chunk = Chunk::V1_18 { section_map }; + Ok((chunk, has_unknown)) } /// [Chunk::new] implementation for all pre-1.18 chunk variants @@ -153,9 +127,10 @@ impl<'a> Chunk<'a> { level: &'a de::LevelV0, block_types: &'a BlockTypes, biome_types: &'a BiomeTypes, - ) -> Result { + ) -> Result<(Self, bool)> { let mut section_map_v1_13 = BTreeMap::new(); let mut section_map_v0 = BTreeMap::new(); + let mut has_unknown = false; for section in &level.sections { let block_light = @@ -167,21 +142,13 @@ impl<'a> Chunk<'a> { block_states, palette, } => { - section_map_v1_13.insert( - SectionY(section.y.into()), - ( - SectionV1_13::new( - data_version, - Some(block_states), - palette, - block_types, - ) - .with_context(|| { - format!("Failed to load section at Y={}", section.y) - })?, - block_light, - ), - ); + let (loaded_section, unknown_blocks) = + SectionV1_13::new(data_version, Some(block_states), palette, block_types) + .with_context(|| format!("Failed to load section at Y={}", section.y))?; + has_unknown |= unknown_blocks; + + section_map_v1_13 + .insert(SectionY(section.y.into()), (loaded_section, block_light)); } de::SectionV0Variant::V0 { blocks, data } => { section_map_v0.insert( @@ -199,23 +166,22 @@ impl<'a> Chunk<'a> { } let biomes = BiomesV0::new(level.biomes.as_ref(), biome_types); - - Ok( - match (section_map_v1_13.is_empty(), section_map_v0.is_empty()) { - (true, true) => Chunk::Empty, - (false, true) => Chunk::V1_13 { - section_map: section_map_v1_13, - biomes: biomes?, - }, - (true, false) => Chunk::V0 { - section_map: section_map_v0, - biomes: biomes?, - }, - (false, false) => { - bail!("Mixed section versions"); - } + let chunk = match (section_map_v1_13.is_empty(), section_map_v0.is_empty()) { + (true, true) => Chunk::Empty, + (false, true) => Chunk::V1_13 { + section_map: section_map_v1_13, + biomes: biomes?, }, - ) + (true, false) => Chunk::V0 { + section_map: section_map_v0, + biomes: biomes?, + }, + (false, false) => { + bail!("Mixed section versions"); + } + }; + + Ok((chunk, has_unknown)) } /// Returns true if the chunk does not contain any sections @@ -283,6 +249,39 @@ impl<'a, T> SectionIterTrait<'a> for T where { } +/// Inner data structure of [SectionIter] +#[derive(Debug, Clone)] +enum SectionIterInner<'a> { + /// Iterator over sections of [Chunk::V1_18] + V1_18 { + /// Inner iterator into section map + iter: btree_map::Iter<'a, SectionY, (SectionV1_13<'a>, BiomesV1_18<'a>, BlockLight<'a>)>, + }, + /// Iterator over sections of [Chunk::V1_13] + V1_13 { + /// Inner iterator into section map + iter: btree_map::Iter<'a, SectionY, (SectionV1_13<'a>, BlockLight<'a>)>, + /// Chunk biome data + biomes: &'a BiomesV0<'a>, + }, + /// Iterator over sections of [Chunk::V0] + V0 { + /// Inner iterator into section map + iter: btree_map::Iter<'a, SectionY, (SectionV0<'a>, BlockLight<'a>)>, + /// Chunk biome data + biomes: &'a BiomesV0<'a>, + }, + /// Empty iterator over an unpopulated chunk ([Chunk::Empty]) + Empty, +} + +/// Iterator over the sections of a [Chunk] +#[derive(Debug, Clone)] +pub struct SectionIter<'a> { + /// Inner iterator enum + inner: SectionIterInner<'a>, +} + impl<'a> SectionIter<'a> { /// Helper to run a closure on the inner section iterator fn with_iter(&mut self, f: F) -> T diff --git a/src/world/section.rs b/src/world/section.rs index 998d2f7..97e0061 100644 --- a/src/world/section.rs +++ b/src/world/section.rs @@ -7,7 +7,7 @@ use std::fmt::Debug; use anyhow::{bail, Context, Result}; use num_integer::div_rem; -use tracing::warn; +use tracing::debug; use super::de; use crate::{ @@ -73,7 +73,7 @@ impl<'a> SectionV1_13<'a> { block_states: Option<&'a [i64]>, palette: &'a [de::BlockStatePaletteEntry], block_types: &'a BlockTypes, - ) -> Result { + ) -> Result<(Self, bool)> { let aligned_blocks = data_version >= 2529; let bits = palette_bits(palette.len(), 4, 12).context("Unsupported block palette size")?; @@ -90,23 +90,29 @@ impl<'a> SectionV1_13<'a> { } } + let mut has_unknown = false; + let palette_types = palette .iter() .map(|entry| { let block_type = block_types.get(&entry.name); if block_type.is_none() { - warn!("Unknown block type: {}", entry.name); + debug!("Unknown block type: {}", entry.name); + has_unknown = true; } block_type }) .collect(); - Ok(Self { - block_states, - palette: palette_types, - bits, - aligned_blocks, - }) + Ok(( + Self { + block_states, + palette: palette_types, + bits, + aligned_blocks, + }, + has_unknown, + )) } /// Looks up the block type palette index at the given coordinates @@ -231,7 +237,7 @@ impl<'a> BiomesV1_18<'a> { biomes: Option<&'a [i64]>, palette: &'a [String], biome_types: &'a BiomeTypes, - ) -> Result { + ) -> Result<(Self, bool)> { let bits = palette_bits(palette.len(), 1, 6).context("Unsupported block palette size")?; if let Some(biomes) = biomes { @@ -242,22 +248,28 @@ impl<'a> BiomesV1_18<'a> { } } + let mut has_unknown = false; + let palette_types = palette .iter() .map(|entry| { let biome_type = biome_types.get(entry); if biome_type.is_none() { - warn!("Unknown biome type: {}", entry); + debug!("Unknown biome type: {}", entry); + has_unknown = true; } biome_type }) .collect(); - Ok(BiomesV1_18 { - biomes, - palette: palette_types, - bits, - }) + Ok(( + BiomesV1_18 { + biomes, + palette: palette_types, + bits, + }, + has_unknown, + )) } /// Looks up the block type palette index at the given coordinates