From 7297c03567e7990520ce9acffe9cb19b517b6ab0 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sat, 25 Nov 2023 21:46:55 +0100 Subject: [PATCH] core/region_processor: store processed block entities for each region --- src/core/common.rs | 39 ++++++++++++++++- src/core/region_processor.rs | 82 ++++++++++++++++++++++++++++-------- 2 files changed, 101 insertions(+), 20 deletions(-) diff --git a/src/core/common.rs b/src/core/common.rs index 49c8301..cd8fd8f 100644 --- a/src/core/common.rs +++ b/src/core/common.rs @@ -9,7 +9,12 @@ use std::{ use indexmap::IndexSet; use serde::{Deserialize, Serialize}; -use crate::{io::fs::FileMetaVersion, resource::Biome, types::*, world::layer}; +use crate::{ + io::fs::FileMetaVersion, + resource::Biome, + types::*, + world::{block_entity::BlockEntity, layer}, +}; /// Increase to force regeneration of all output files @@ -36,6 +41,11 @@ pub const LIGHTMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0); /// Increase when the mipmap generation changes (this should not happen) pub const MIPMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0); +/// MinedMap processed entity data version number +/// +/// Increase when entity collection changes bacause of code changes. +pub const ENTITIES_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0); + /// Coordinate pair of a generated tile /// /// Each tile corresponds to one Minecraft region file @@ -94,6 +104,13 @@ pub struct ProcessedRegion { pub chunks: ChunkArray>>, } +/// Data structure for storing entity data between processing and collection steps +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct ProcessedEntities { + /// List of block entities + pub block_entities: Vec, +} + /// Derives a filename from region coordinates and a file extension /// /// Can be used for input regions, processed data or rendered tiles @@ -122,6 +139,8 @@ pub struct Config { pub output_dir: PathBuf, /// Path for storage of intermediate processed data files pub processed_dir: PathBuf, + /// Path for storage of processed entity data files + pub entities_dir: PathBuf, /// Path of viewer metadata file pub metadata_path: PathBuf, } @@ -137,7 +156,8 @@ impl Config { let region_dir = [&args.input_dir, Path::new("region")].iter().collect(); let level_dat_path = [&args.input_dir, Path::new("level.dat")].iter().collect(); - let processed_dir = [&args.output_dir, Path::new("processed")].iter().collect(); + let processed_dir: PathBuf = [&args.output_dir, Path::new("processed")].iter().collect(); + let entities_dir = [&processed_dir, Path::new("entities")].iter().collect(); let metadata_path = [&args.output_dir, Path::new("info.json")].iter().collect(); Config { @@ -146,6 +166,7 @@ impl Config { level_dat_path, output_dir: args.output_dir.clone(), processed_dir, + entities_dir, metadata_path, } } @@ -162,6 +183,20 @@ impl Config { [&self.processed_dir, Path::new(&filename)].iter().collect() } + /// Constructs the base output path for processed entity data + pub fn entities_dir(&self, level: usize) -> PathBuf { + [&self.entities_dir, Path::new(&level.to_string())] + .iter() + .collect() + } + + /// Constructs the path of a processed entity data file + pub fn entities_path(&self, level: usize, coords: TileCoords) -> PathBuf { + let filename = coord_filename(coords, "bin"); + let dir = self.entities_dir(level); + [Path::new(&dir), Path::new(&filename)].iter().collect() + } + /// Constructs the base output path for a [TileKind] and mipmap level pub fn tile_dir(&self, kind: TileKind, level: usize) -> PathBuf { let prefix = match kind { diff --git a/src/core/region_processor.rs b/src/core/region_processor.rs index 52cb165..f1f8441 100644 --- a/src/core/region_processor.rs +++ b/src/core/region_processor.rs @@ -64,20 +64,28 @@ struct SingleRegionProcessor<'a> { output_path: PathBuf, /// Lightmap output filename lightmap_path: PathBuf, + /// Processed entity output filename + entities_path: PathBuf, /// Timestamp of last modification of input file input_timestamp: SystemTime, /// Timestamp of last modification of processed region output file (if valid) output_timestamp: Option, /// Timestamp of last modification of lightmap output file (if valid) lightmap_timestamp: Option, + /// Timestamp of last modification of entity list output file (if valid) + entities_timestamp: Option, /// True if processed region output file needs to be updated output_needed: bool, /// True if lightmap output file needs to be updated lightmap_needed: bool, + /// True if entity output file needs to be updated + entities_needed: bool, /// Processed region intermediate data processed_region: ProcessedRegion, /// Lightmap intermediate data lightmap: image::GrayAlphaImage, + /// Processed entity intermediate data + entities: ProcessedEntities, /// True if any unknown block or biome types were encountered during processing has_unknown: bool, } @@ -93,14 +101,20 @@ impl<'a> SingleRegionProcessor<'a> { let output_path = processor.config.processed_path(coords); let output_timestamp = fs::read_timestamp(&output_path, REGION_FILE_META_VERSION); + let lightmap_path = processor.config.tile_path(TileKind::Lightmap, 0, coords); let lightmap_timestamp = fs::read_timestamp(&lightmap_path, LIGHTMAP_FILE_META_VERSION); + let entities_path = processor.config.entities_path(0, coords); + let entities_timestamp = fs::read_timestamp(&entities_path, ENTITIES_FILE_META_VERSION); + let output_needed = Some(input_timestamp) > output_timestamp; let lightmap_needed = Some(input_timestamp) > lightmap_timestamp; + let entities_needed = Some(input_timestamp) > entities_timestamp; let processed_region = ProcessedRegion::default(); let lightmap = image::GrayAlphaImage::new(N, N); + let entities = ProcessedEntities::default(); Ok(SingleRegionProcessor { block_types: &processor.block_types, @@ -109,13 +123,17 @@ impl<'a> SingleRegionProcessor<'a> { input_path, output_path, lightmap_path, + entities_path, input_timestamp, output_timestamp, lightmap_timestamp, + entities_timestamp, output_needed, lightmap_needed, + entities_needed, processed_region, lightmap, + entities, has_unknown: false, }) } @@ -174,34 +192,57 @@ impl<'a> SingleRegionProcessor<'a> { ) } + /// Saves processed entity data + /// + /// The timestamp is the time of the last modification of the input region data. + fn save_entities(&self) -> Result<()> { + if !self.entities_needed { + return Ok(()); + } + + storage::write_file( + &self.entities_path, + &self.entities, + storage::Format::Json, + ENTITIES_FILE_META_VERSION, + self.input_timestamp, + ) + } + /// Processes a single chunk fn process_chunk(&mut self, chunk_coords: ChunkCoords, data: world::de::Chunk) -> Result<()> { 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, - block_light, - depths, - }) = world::layer::top_layer(&mut self.processed_region.biome_list, &chunk) - .with_context(|| format!("Failed to process chunk {:?}", chunk_coords))? - else { - return Ok(()); - }; - if self.output_needed { - self.processed_region.chunks[chunk_coords] = Some(Box::new(ProcessedChunk { + if self.output_needed || self.lightmap_needed { + if let Some(layer::LayerData { blocks, biomes, + block_light, depths, - })); + }) = world::layer::top_layer(&mut self.processed_region.biome_list, &chunk) + .with_context(|| format!("Failed to process chunk {:?}", chunk_coords))? + { + if self.output_needed { + self.processed_region.chunks[chunk_coords] = Some(Box::new(ProcessedChunk { + blocks, + biomes, + depths, + })); + } + + if self.lightmap_needed { + let chunk_lightmap = Self::render_chunk_lightmap(block_light); + overlay_chunk(&mut self.lightmap, &chunk_lightmap, chunk_coords); + } + } } - if self.lightmap_needed { - let chunk_lightmap = Self::render_chunk_lightmap(block_light); - overlay_chunk(&mut self.lightmap, &chunk_lightmap, chunk_coords); + if self.entities_needed { + let mut block_entities = chunk.block_entities(); + self.entities.block_entities.append(&mut block_entities); } Ok(()) @@ -215,7 +256,7 @@ impl<'a> SingleRegionProcessor<'a> { /// Processes the region fn run(mut self) -> Result { - if !self.output_needed && !self.lightmap_needed { + if !self.output_needed && !self.lightmap_needed && !self.entities_needed { debug!( "Skipping unchanged region r.{}.{}.mca", self.coords.x, self.coords.z @@ -229,7 +270,10 @@ impl<'a> SingleRegionProcessor<'a> { ); if let Err(err) = self.process_chunks() { - if self.output_timestamp.is_some() && self.lightmap_timestamp.is_some() { + if self.output_timestamp.is_some() + && self.lightmap_timestamp.is_some() + && self.entities_timestamp.is_some() + { warn!( "Failed to process region {:?}, using old data: {:?}", self.coords, err @@ -246,6 +290,7 @@ impl<'a> SingleRegionProcessor<'a> { self.save_region()?; self.save_lightmap()?; + self.save_entities()?; Ok(if self.has_unknown { RegionProcessorStatus::OkWithUnknown @@ -313,6 +358,7 @@ impl<'a> RegionProcessor<'a> { pub fn run(self) -> Result> { fs::create_dir_all(&self.config.processed_dir)?; fs::create_dir_all(&self.config.tile_dir(TileKind::Lightmap, 0))?; + fs::create_dir_all(&self.config.entities_dir(0))?; info!("Processing region files...");