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.
This commit is contained in:
Matthias Schiffer 2023-08-03 21:27:08 +02:00
parent c38f00e411
commit fb712cd2f5
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
8 changed files with 70 additions and 18 deletions

24
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -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<layer::BiomeArray>,
pub depths: Box<layer::DepthArray>,
}
pub type ProcessedRegion = ChunkArray<Option<ProcessedChunk>>;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ProcessedRegion {
pub biome_list: IndexSet<Biome>,
pub chunks: ChunkArray<Option<ProcessedChunk>>,
}
pub struct Config {
pub region_dir: PathBuf,

View file

@ -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<Option<LayerData>> {
fn process_chunk(
&self,
biome_list: &mut IndexSet<Biome>,
data: world::de::Chunk,
) -> Result<Option<LayerData>> {
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,

View file

@ -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<ProcessedRegion>) {
for (coords, chunk) in region_group.center().iter() {
for (coords, chunk) in region_group.center().chunks.iter() {
let Some(chunk) = chunk else {
continue;
};

View file

@ -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,

View file

@ -35,7 +35,7 @@ where
BitFlags::<BlockFlag>::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)]

View file

@ -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<Option<BlockType>>;
pub type BiomeArray = LayerBlockArray<Option<Biome>>;
pub type BiomeArray = LayerBlockArray<Option<NonZeroU16>>;
pub type BlockLightArray = LayerBlockArray<u8>;
pub type DepthArray = LayerBlockArray<Option<BlockHeight>>;
struct LayerEntry<'a> {
block: &'a mut Option<BlockType>,
biome: &'a mut Option<Biome>,
biome: &'a mut Option<NonZeroU16>,
block_light: &'a mut u8,
depth: &'a mut Option<BlockHeight>,
}
@ -46,7 +49,12 @@ impl<'a> LayerEntry<'a> {
self.depth.is_some()
}
fn fill(&mut self, section: SectionIterItem, coords: SectionBlockCoords) -> Result<bool> {
fn fill(
&mut self,
biome_list: &mut IndexSet<Biome>,
section: SectionIterItem,
coords: SectionBlockCoords,
) -> Result<bool> {
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<Option<LayerData>> {
pub fn top_layer(biome_list: &mut IndexSet<Biome>, chunk: &Chunk) -> Result<Option<LayerData>> {
use BLOCKS_PER_CHUNK as N;
if chunk.is_empty() {
@ -121,7 +136,7 @@ pub fn top_layer(chunk: &Chunk) -> Result<Option<LayerData>> {
}
let coords = SectionBlockCoords { xz, y };
if !entry.fill(section, coords)? {
if !entry.fill(biome_list, section, coords)? {
continue;
}