diff --git a/src/core/common.rs b/src/core/common.rs index cd8fd8f..7280be9 100644 --- a/src/core/common.rs +++ b/src/core/common.rs @@ -141,6 +141,8 @@ pub struct Config { pub processed_dir: PathBuf, /// Path for storage of processed entity data files pub entities_dir: PathBuf, + /// Path for storage of the final merged processed entity data file + pub entities_path_final: PathBuf, /// Path of viewer metadata file pub metadata_path: PathBuf, } @@ -157,7 +159,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: PathBuf = [&args.output_dir, Path::new("processed")].iter().collect(); - let entities_dir = [&processed_dir, Path::new("entities")].iter().collect(); + let entities_dir: PathBuf = [&processed_dir, Path::new("entities")].iter().collect(); + let entities_path_final = [&entities_dir, Path::new("entities.bin")].iter().collect(); let metadata_path = [&args.output_dir, Path::new("info.json")].iter().collect(); Config { @@ -167,6 +170,7 @@ impl Config { output_dir: args.output_dir.clone(), processed_dir, entities_dir, + entities_path_final, metadata_path, } } diff --git a/src/core/entity_collector.rs b/src/core/entity_collector.rs new file mode 100644 index 0000000..3d9b6ab --- /dev/null +++ b/src/core/entity_collector.rs @@ -0,0 +1,123 @@ +//! The [EntityCollector] + +use std::path::Path; + +use anyhow::{Context, Result}; +use tracing::{info, warn}; + +use super::{common::*, tile_collector::TileCollector, tile_merger::TileMerger}; +use crate::io::{fs, storage}; + +/// Generates mipmap tiles from full-resolution tile images +pub struct EntityCollector<'a> { + /// Common MinedMap configuration from command line + config: &'a Config, + /// List of populated tiles for base mipmap level (level 0) + regions: &'a [TileCoords], +} + +impl<'a> TileMerger for EntityCollector<'a> { + fn file_meta_version(&self) -> fs::FileMetaVersion { + ENTITIES_FILE_META_VERSION + } + + fn tile_path(&self, level: usize, coords: TileCoords) -> std::path::PathBuf { + self.config.entities_path(level, coords) + } + + fn write_tile( + &self, + file: &mut std::io::BufWriter, + sources: &[super::tile_merger::Source], + ) -> Result<()> { + Self::merge_entity_lists(file, sources.iter().map(|source| &source.1)) + } +} + +impl<'a> TileCollector for EntityCollector<'a> { + type CollectOutput = (); + + fn tiles(&self) -> &[TileCoords] { + self.regions + } + + fn prepare(&self, level: usize) -> Result<()> { + fs::create_dir_all(&self.config.entities_dir(level)) + } + + fn finish( + &self, + _level: usize, + _outputs: impl Iterator, + ) -> Result<()> { + Ok(()) + } + + fn collect_one( + &self, + level: usize, + coords: TileCoords, + prev: &TileCoordMap, + ) -> Result { + self.merge_tiles(level, coords, prev)?; + Ok(()) + } +} + +impl<'a> EntityCollector<'a> { + /// Constructs a new EntityCollector + pub fn new(config: &'a Config, regions: &'a [TileCoords]) -> Self { + EntityCollector { config, regions } + } + + /// Merges multiple entity lists into one + fn merge_entity_lists>( + file: &mut std::io::BufWriter, + sources: impl Iterator, + ) -> Result<()> { + let mut output = ProcessedEntities::default(); + + for source_path in sources { + let mut source: ProcessedEntities = + match storage::read_file(source_path.as_ref(), storage::Format::Json) { + Ok(source) => source, + Err(err) => { + warn!( + "Failed to read entity data file {}: {:?}", + source_path.as_ref().display(), + err, + ); + continue; + } + }; + + output.block_entities.append(&mut source.block_entities); + } + + storage::write(file, &output, storage::Format::Json).context("Failed to write entity data") + } + + /// Runs the mipmap generation + pub fn run(self) -> Result<()> { + info!("Collecting entity data..."); + + let tile_stack = self.collect_tiles()?; + + // Final merge + let level = tile_stack.len() - 1; + let tile_map = &tile_stack[level]; + let sources: Vec<_> = [(-1, -1), (-1, 0), (0, -1), (0, 0)] + .into_iter() + .map(|(x, z)| TileCoords { x, z }) + .filter(|&coords| tile_map.contains(coords)) + .map(|coords| self.tile_path(level, coords)) + .collect(); + + fs::create_with_tmpfile(&self.config.entities_path_final, |file| { + Self::merge_entity_lists(file, sources.iter()) + })?; + + info!("Collected entity data."); + Ok(()) + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index f892a62..61bdb76 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,6 +1,7 @@ //! Core functions of the MinedMap CLI mod common; +mod entity_collector; mod metadata_writer; mod region_group; mod region_processor; @@ -21,6 +22,8 @@ use region_processor::RegionProcessor; use tile_mipmapper::TileMipmapper; use tile_renderer::TileRenderer; +use self::entity_collector::EntityCollector; + /// MinedMap version number const VERSION: &str = git_version!( args = ["--abbrev=7", "--match=v*", "--dirty=-modified"], @@ -77,6 +80,7 @@ pub fn cli() -> Result<()> { let regions = RegionProcessor::new(&config).run()?; TileRenderer::new(&config, &rt, ®ions).run()?; let tiles = TileMipmapper::new(&config, ®ions).run()?; + EntityCollector::new(&config, ®ions).run()?; MetadataWriter::new(&config, &tiles).run()?; Ok(())