diff --git a/CHANGELOG.md b/CHANGELOG.md index 2546290..fccf759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## [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 ### Added 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 e52c27b..993a79e 100644 --- a/src/world/chunk.rs +++ b/src/world/chunk.rs @@ -56,7 +56,7 @@ impl<'a> Chunk<'a> { 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 { @@ -75,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 { @@ -85,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) })?, @@ -111,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 @@ -120,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 = @@ -134,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( @@ -166,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 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