From fb712cd2f5a15dedf29bd68bb54add9684352acc Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Thu, 3 Aug 2023 21:27:08 +0200 Subject: [PATCH] Store per-region biome list in IndexSet Index into the biome list instead of duplicating the biome data for each coordinate. This reduces the size of the processed data and speeds up encoding/decoding. --- Cargo.lock | 24 ++++++++++++++++++++++++ Cargo.toml | 1 + src/bin/minedmap/common.rs | 10 ++++++++-- src/bin/minedmap/region_processor.rs | 15 ++++++++++----- src/bin/minedmap/tile_renderer.rs | 5 +++-- src/resource/biomes.rs | 4 ++-- src/resource/mod.rs | 2 +- src/world/layer.rs | 27 +++++++++++++++++++++------ 8 files changed, 70 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b286664..d771fd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,6 +220,12 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.2" @@ -279,6 +285,12 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42218cb640844e3872cc3c153dc975229e080a6c4733b34709ef445610550226" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -305,6 +317,17 @@ dependencies = [ "png", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -375,6 +398,7 @@ dependencies = [ "flate2", "glam", "image", + "indexmap", "itertools", "num-integer", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5e04100..aad8ff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ fastnbt = "2.3.2" flate2 = "1.0.25" glam = "0.24.0" image = { version = "0.24.5", default-features = false, features = ["png"] } +indexmap = { version = "2.0.0", features = ["serde"] } itertools = "0.11.0" num-integer = "0.1.45" serde = "1.0.152" diff --git a/src/bin/minedmap/common.rs b/src/bin/minedmap/common.rs index a108e8d..eb1613e 100644 --- a/src/bin/minedmap/common.rs +++ b/src/bin/minedmap/common.rs @@ -4,9 +4,10 @@ use std::{ path::{Path, PathBuf}, }; +use indexmap::IndexSet; use serde::{Deserialize, Serialize}; -use minedmap::{io::fs::FileMetaVersion, types::*, world::layer}; +use minedmap::{io::fs::FileMetaVersion, resource::Biome, types::*, world::layer}; // Increase to force regeneration of all output files pub const FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0); @@ -43,7 +44,12 @@ pub struct ProcessedChunk { pub biomes: Box, pub depths: Box, } -pub type ProcessedRegion = ChunkArray>; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ProcessedRegion { + pub biome_list: IndexSet, + pub chunks: ChunkArray>, +} pub struct Config { pub region_dir: PathBuf, diff --git a/src/bin/minedmap/region_processor.rs b/src/bin/minedmap/region_processor.rs index f2720c7..8a77a73 100644 --- a/src/bin/minedmap/region_processor.rs +++ b/src/bin/minedmap/region_processor.rs @@ -2,9 +2,10 @@ use std::{path::Path, time::SystemTime}; use anyhow::{Context, Result}; +use indexmap::IndexSet; use minedmap::{ io::{fs, storage}, - resource, + resource::{self, Biome}, types::*, world::{ self, @@ -45,9 +46,13 @@ impl<'a> RegionProcessor<'a> { } /// Processes a single chunk - fn process_chunk(&self, data: world::de::Chunk) -> Result> { + fn process_chunk( + &self, + biome_list: &mut IndexSet, + data: world::de::Chunk, + ) -> Result> { let chunk = world::chunk::Chunk::new(&data, &self.block_types, &self.biome_types)?; - world::layer::top_layer(&chunk) + world::layer::top_layer(biome_list, &chunk) } fn render_chunk_lightmap( @@ -110,12 +115,12 @@ impl<'a> RegionProcessor<'a> { minedmap::io::region::from_file(path)?.foreach_chunk( |chunk_coords, data: world::de::Chunk| { let Some(layer::LayerData{ blocks, biomes, block_light, depths }) = self - .process_chunk(data) + .process_chunk(&mut processed_region.biome_list, data) .with_context(|| format!("Failed to process chunk {:?}", chunk_coords))? else { return Ok(()); }; - processed_region[chunk_coords] = Some(ProcessedChunk { + processed_region.chunks[chunk_coords] = Some(ProcessedChunk { blocks, biomes, depths, diff --git a/src/bin/minedmap/tile_renderer.rs b/src/bin/minedmap/tile_renderer.rs index c66431f..556483b 100644 --- a/src/bin/minedmap/tile_renderer.rs +++ b/src/bin/minedmap/tile_renderer.rs @@ -56,7 +56,8 @@ fn biome_at( z: block_z, }; let region = region_group.get(region_x, region_z)?; - region[chunk].as_ref()?.biomes[block].as_ref() + let index = region.chunks[chunk].as_ref()?.biomes[block]?.get() - 1; + region.biome_list.get_index(index.into()) } pub struct TileRenderer<'a> { @@ -144,7 +145,7 @@ impl<'a> TileRenderer<'a> { } fn render_region(image: &mut image::RgbaImage, region_group: &RegionGroup) { - for (coords, chunk) in region_group.center().iter() { + for (coords, chunk) in region_group.center().chunks.iter() { let Some(chunk) = chunk else { continue; }; diff --git a/src/resource/biomes.rs b/src/resource/biomes.rs index cf0db4b..0515e93 100644 --- a/src/resource/biomes.rs +++ b/src/resource/biomes.rs @@ -2,13 +2,13 @@ use serde::{Deserialize, Serialize}; use super::Color; -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum BiomeGrassColorModifier { DarkForest, Swamp, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Biome { pub temp: i8, pub downfall: i8, diff --git a/src/resource/mod.rs b/src/resource/mod.rs index 921fdc2..5eb2838 100644 --- a/src/resource/mod.rs +++ b/src/resource/mod.rs @@ -35,7 +35,7 @@ where BitFlags::::from_bits(bits).map_err(de::Error::custom) } -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Color(pub [u8; 3]); #[derive(Debug, Clone, Copy, Serialize, Deserialize)] diff --git a/src/world/layer.rs b/src/world/layer.rs index 4b23f26..fa9a4f7 100644 --- a/src/world/layer.rs +++ b/src/world/layer.rs @@ -1,4 +1,7 @@ +use std::num::NonZeroU16; + use anyhow::{Context, Result}; +use indexmap::IndexSet; use serde::{Deserialize, Serialize}; use super::chunk::{Chunk, SectionIterItem}; @@ -26,13 +29,13 @@ impl BlockHeight { } pub type BlockArray = LayerBlockArray>; -pub type BiomeArray = LayerBlockArray>; +pub type BiomeArray = LayerBlockArray>; pub type BlockLightArray = LayerBlockArray; pub type DepthArray = LayerBlockArray>; struct LayerEntry<'a> { block: &'a mut Option, - biome: &'a mut Option, + biome: &'a mut Option, block_light: &'a mut u8, depth: &'a mut Option, } @@ -46,7 +49,12 @@ impl<'a> LayerEntry<'a> { self.depth.is_some() } - fn fill(&mut self, section: SectionIterItem, coords: SectionBlockCoords) -> Result { + fn fill( + &mut self, + biome_list: &mut IndexSet, + section: SectionIterItem, + coords: SectionBlockCoords, + ) -> Result { let Some(block_type) = section.section.block_at(coords)? .filter(|block_type| block_type.is(BlockFlag::Opaque)) else { @@ -59,7 +67,14 @@ impl<'a> LayerEntry<'a> { if self.is_empty() { *self.block = Some(block_type); - *self.biome = section.biomes.biome_at(section.y, coords)?.copied(); + if let Some(biome) = section.biomes.biome_at(section.y, coords)? { + let (biome_index, _) = biome_list.insert_full(*biome); + *self.biome = NonZeroU16::new( + (biome_index + 1) + .try_into() + .expect("biome index not in range"), + ); + } } if block_type.is(BlockFlag::Water) { @@ -99,7 +114,7 @@ impl LayerData { /// determined as the block that should be visible on the rendered /// map. For water blocks, the height of the first non-water block /// is additionally filled in as the water depth. -pub fn top_layer(chunk: &Chunk) -> Result> { +pub fn top_layer(biome_list: &mut IndexSet, chunk: &Chunk) -> Result> { use BLOCKS_PER_CHUNK as N; if chunk.is_empty() { @@ -121,7 +136,7 @@ pub fn top_layer(chunk: &Chunk) -> Result> { } let coords = SectionBlockCoords { xz, y }; - if !entry.fill(section, coords)? { + if !entry.fill(biome_list, section, coords)? { continue; }