diff --git a/src/bin/minedmap/region_processor.rs b/src/bin/minedmap/region_processor.rs index d20f82b..a95f6ea 100644 --- a/src/bin/minedmap/region_processor.rs +++ b/src/bin/minedmap/region_processor.rs @@ -66,43 +66,46 @@ impl<'a> RegionProcessor<'a> { } fn save_region( - &self, - coords: TileCoords, + path: &Path, processed_region: &ProcessedRegion, timestamp: SystemTime, ) -> Result<()> { - let output_path = self.config.processed_path(coords); - storage::write(&output_path, processed_region, FILE_META_VERSION, timestamp) + storage::write(path, processed_region, FILE_META_VERSION, timestamp) } fn save_lightmap( - &self, - coords: TileCoords, + path: &Path, lightmap: &image::GrayAlphaImage, timestamp: SystemTime, ) -> Result<()> { - fs::create_with_timestamp( - &self.config.tile_path(TileKind::Lightmap, 0, coords), - FILE_META_VERSION, - timestamp, - |file| { - lightmap - .write_to(file, image::ImageFormat::Png) - .context("Failed to save image") - }, - ) + fs::create_with_timestamp(path, FILE_META_VERSION, timestamp, |file| { + lightmap + .write_to(file, image::ImageFormat::Png) + .context("Failed to save image") + }) } /// Processes a single region file fn process_region(&self, path: &Path, coords: TileCoords) -> Result<()> { const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32; - println!("Processing region r.{}.{}.mca", coords.x, coords.z); - let mut processed_region = ProcessedRegion::default(); let mut lightmap = image::GrayAlphaImage::new(N, N); - let timestamp = fs::modified_timestamp(path)?; + let input_timestamp = fs::modified_timestamp(path)?; + + let output_path = self.config.processed_path(coords); + let output_timestamp = fs::read_timestamp(&output_path, FILE_META_VERSION); + let lightmap_path = self.config.tile_path(TileKind::Lightmap, 0, coords); + let lightmap_timestamp = fs::read_timestamp(&lightmap_path, FILE_META_VERSION); + + if Some(input_timestamp) <= output_timestamp && Some(input_timestamp) <= lightmap_timestamp + { + println!("Skipping unchanged region r.{}.{}.mca", coords.x, coords.z); + return Ok(()); + } + + println!("Processing region r.{}.{}.mca", coords.x, coords.z); minedmap::io::region::from_file(path)?.foreach_chunk( |chunk_coords, data: world::de::Chunk| { @@ -125,8 +128,12 @@ impl<'a> RegionProcessor<'a> { }, )?; - self.save_region(coords, &processed_region, timestamp)?; - self.save_lightmap(coords, &lightmap, timestamp)?; + if Some(input_timestamp) > output_timestamp { + Self::save_region(&output_path, &processed_region, input_timestamp)?; + } + if Some(input_timestamp) > lightmap_timestamp { + Self::save_lightmap(&lightmap_path, &lightmap, input_timestamp)?; + } Ok(()) } diff --git a/src/bin/minedmap/tile_mipmapper.rs b/src/bin/minedmap/tile_mipmapper.rs index 22b942d..40add3a 100644 --- a/src/bin/minedmap/tile_mipmapper.rs +++ b/src/bin/minedmap/tile_mipmapper.rs @@ -49,17 +49,7 @@ impl<'a> TileMipmapper<'a> { const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32; let output_path = self.config.tile_path(kind, level, coords); - - println!( - "Rendering mipmap tile {}", - output_path - .strip_prefix(&self.config.output_dir) - .expect("tile path must be in output directory") - .display(), - ); - - let mut image: image::DynamicImage = - image::ImageBuffer::>::new(N, N).into(); + let output_timestamp = fs::read_timestamp(&output_path, FILE_META_VERSION); let sources: Vec<_> = [(0, 0), (0, 1), (1, 0), (1, 1)] .into_iter() @@ -84,10 +74,32 @@ impl<'a> TileMipmapper<'a> { }) .collect(); - let Some(timestamp) = sources.iter().map(|(_, _, ts)| *ts).max() else { + let Some(input_timestamp) = sources.iter().map(|(_, _, ts)| *ts).max() else { return Ok(()); }; + if Some(input_timestamp) <= output_timestamp { + println!( + "Skipping unchanged mipmap tile {}", + output_path + .strip_prefix(&self.config.output_dir) + .expect("tile path must be in output directory") + .display(), + ); + return Ok(()); + } + + println!( + "Rendering mipmap tile {}", + output_path + .strip_prefix(&self.config.output_dir) + .expect("tile path must be in output directory") + .display(), + ); + + let mut image: image::DynamicImage = + image::ImageBuffer::>::new(N, N).into(); + for ((dx, dz), source_path, _) in sources { let source = match image::open(&source_path) { Ok(source) => source, @@ -109,7 +121,7 @@ impl<'a> TileMipmapper<'a> { ); } - fs::create_with_timestamp(&output_path, FILE_META_VERSION, timestamp, |file| { + fs::create_with_timestamp(&output_path, FILE_META_VERSION, input_timestamp, |file| { image .write_to(file, image::ImageFormat::Png) .context("Failed to save image") diff --git a/src/bin/minedmap/tile_renderer.rs b/src/bin/minedmap/tile_renderer.rs index 0e91054..955c964 100644 --- a/src/bin/minedmap/tile_renderer.rs +++ b/src/bin/minedmap/tile_renderer.rs @@ -1,4 +1,4 @@ -use std::time::SystemTime; +use std::path::Path; use anyhow::{Context, Result}; @@ -19,12 +19,8 @@ impl<'a> TileRenderer<'a> { TileRenderer { config } } - fn load_region(&self, coords: TileCoords) -> Result<(ProcessedRegion, SystemTime)> { - let processed_path = self.config.processed_path(coords); - let timestamp = fs::modified_timestamp(&processed_path)?; - let region = - storage::read(&processed_path).context("Failed to load processed region data")?; - Ok((region, timestamp)) + fn load_region(processed_path: &Path) -> Result { + storage::read(processed_path).context("Failed to load processed region data") } fn render_chunk(image: &mut image::RgbaImage, coords: ChunkCoords, chunk: &ProcessedChunk) { @@ -64,7 +60,22 @@ impl<'a> TileRenderer<'a> { fn render_tile(&self, coords: TileCoords) -> Result<()> { const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32; + let processed_path = self.config.processed_path(coords); + let processed_timestamp = fs::modified_timestamp(&processed_path)?; + let output_path = self.config.tile_path(TileKind::Map, 0, coords); + let output_timestamp = fs::read_timestamp(&output_path, FILE_META_VERSION); + + if Some(processed_timestamp) <= output_timestamp { + println!( + "Skipping unchanged tile {}", + output_path + .strip_prefix(&self.config.output_dir) + .expect("tile path must be in output directory") + .display(), + ); + return Ok(()); + } println!( "Rendering tile {}", @@ -74,15 +85,20 @@ impl<'a> TileRenderer<'a> { .display(), ); - let (region, timestamp) = self.load_region(coords)?; + let region = Self::load_region(&processed_path)?; let mut image = image::RgbaImage::new(N, N); Self::render_region(&mut image, ®ion); - fs::create_with_timestamp(&output_path, FILE_META_VERSION, timestamp, |file| { - image - .write_to(file, image::ImageFormat::Png) - .context("Failed to save image") - }) + fs::create_with_timestamp( + &output_path, + FILE_META_VERSION, + processed_timestamp, + |file| { + image + .write_to(file, image::ImageFormat::Png) + .context("Failed to save image") + }, + ) } pub fn run(self, regions: &[TileCoords]) -> Result<()> { diff --git a/src/io/fs.rs b/src/io/fs.rs index dfb2e78..8f6a79a 100644 --- a/src/io/fs.rs +++ b/src/io/fs.rs @@ -6,12 +6,12 @@ use std::{ }; use anyhow::{Context, Ok, Result}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, Serialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub struct FileMetaVersion(pub u32); -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] struct FileMeta { version: FileMetaVersion, timestamp: SystemTime, @@ -115,6 +115,18 @@ pub fn modified_timestamp(path: &Path) -> Result { }) } +pub fn read_timestamp(path: &Path, version: FileMetaVersion) -> Option { + let meta_path = metafile_name(path); + let mut file = BufReader::new(fs::File::open(meta_path).ok()?); + + let meta: FileMeta = serde_json::from_reader(&mut file).ok()?; + if meta.version != version { + return None; + } + + Some(meta.timestamp) +} + pub fn create_with_timestamp( path: &Path, version: FileMetaVersion,