minedmap: skip generation steps when the inputs are unchanged

This commit is contained in:
Matthias Schiffer 2023-07-31 00:23:06 +02:00
parent 6077138292
commit 80781c9c20
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
4 changed files with 97 additions and 50 deletions

View file

@ -66,43 +66,46 @@ impl<'a> RegionProcessor<'a> {
} }
fn save_region( fn save_region(
&self, path: &Path,
coords: TileCoords,
processed_region: &ProcessedRegion, processed_region: &ProcessedRegion,
timestamp: SystemTime, timestamp: SystemTime,
) -> Result<()> { ) -> Result<()> {
let output_path = self.config.processed_path(coords); storage::write(path, processed_region, FILE_META_VERSION, timestamp)
storage::write(&output_path, processed_region, FILE_META_VERSION, timestamp)
} }
fn save_lightmap( fn save_lightmap(
&self, path: &Path,
coords: TileCoords,
lightmap: &image::GrayAlphaImage, lightmap: &image::GrayAlphaImage,
timestamp: SystemTime, timestamp: SystemTime,
) -> Result<()> { ) -> Result<()> {
fs::create_with_timestamp( fs::create_with_timestamp(path, FILE_META_VERSION, timestamp, |file| {
&self.config.tile_path(TileKind::Lightmap, 0, coords),
FILE_META_VERSION,
timestamp,
|file| {
lightmap lightmap
.write_to(file, image::ImageFormat::Png) .write_to(file, image::ImageFormat::Png)
.context("Failed to save image") .context("Failed to save image")
}, })
)
} }
/// Processes a single region file /// Processes a single region file
fn process_region(&self, path: &Path, coords: TileCoords) -> Result<()> { fn process_region(&self, path: &Path, coords: TileCoords) -> Result<()> {
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32; 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 processed_region = ProcessedRegion::default();
let mut lightmap = image::GrayAlphaImage::new(N, N); 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( minedmap::io::region::from_file(path)?.foreach_chunk(
|chunk_coords, data: world::de::Chunk| { |chunk_coords, data: world::de::Chunk| {
@ -125,8 +128,12 @@ impl<'a> RegionProcessor<'a> {
}, },
)?; )?;
self.save_region(coords, &processed_region, timestamp)?; if Some(input_timestamp) > output_timestamp {
self.save_lightmap(coords, &lightmap, 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(()) Ok(())
} }

View file

@ -49,17 +49,7 @@ impl<'a> TileMipmapper<'a> {
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32; const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
let output_path = self.config.tile_path(kind, level, coords); let output_path = self.config.tile_path(kind, level, coords);
let output_timestamp = fs::read_timestamp(&output_path, FILE_META_VERSION);
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::<P, Vec<P::Subpixel>>::new(N, N).into();
let sources: Vec<_> = [(0, 0), (0, 1), (1, 0), (1, 1)] let sources: Vec<_> = [(0, 0), (0, 1), (1, 0), (1, 1)]
.into_iter() .into_iter()
@ -84,10 +74,32 @@ impl<'a> TileMipmapper<'a> {
}) })
.collect(); .collect();
let Some(timestamp) = sources.iter().map(|(_, _, ts)| *ts).max() else { let Some(input_timestamp) = sources.iter().map(|(_, _, ts)| *ts).max() else {
return Ok(()); 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::<P, Vec<P::Subpixel>>::new(N, N).into();
for ((dx, dz), source_path, _) in sources { for ((dx, dz), source_path, _) in sources {
let source = match image::open(&source_path) { let source = match image::open(&source_path) {
Ok(source) => source, 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 image
.write_to(file, image::ImageFormat::Png) .write_to(file, image::ImageFormat::Png)
.context("Failed to save image") .context("Failed to save image")

View file

@ -1,4 +1,4 @@
use std::time::SystemTime; use std::path::Path;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -19,12 +19,8 @@ impl<'a> TileRenderer<'a> {
TileRenderer { config } TileRenderer { config }
} }
fn load_region(&self, coords: TileCoords) -> Result<(ProcessedRegion, SystemTime)> { fn load_region(processed_path: &Path) -> Result<ProcessedRegion> {
let processed_path = self.config.processed_path(coords); storage::read(processed_path).context("Failed to load processed region data")
let timestamp = fs::modified_timestamp(&processed_path)?;
let region =
storage::read(&processed_path).context("Failed to load processed region data")?;
Ok((region, timestamp))
} }
fn render_chunk(image: &mut image::RgbaImage, coords: ChunkCoords, chunk: &ProcessedChunk) { 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<()> { fn render_tile(&self, coords: TileCoords) -> Result<()> {
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32; 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_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!( println!(
"Rendering tile {}", "Rendering tile {}",
@ -74,15 +85,20 @@ impl<'a> TileRenderer<'a> {
.display(), .display(),
); );
let (region, timestamp) = self.load_region(coords)?; let region = Self::load_region(&processed_path)?;
let mut image = image::RgbaImage::new(N, N); let mut image = image::RgbaImage::new(N, N);
Self::render_region(&mut image, &region); Self::render_region(&mut image, &region);
fs::create_with_timestamp(&output_path, FILE_META_VERSION, timestamp, |file| { fs::create_with_timestamp(
&output_path,
FILE_META_VERSION,
processed_timestamp,
|file| {
image image
.write_to(file, image::ImageFormat::Png) .write_to(file, image::ImageFormat::Png)
.context("Failed to save image") .context("Failed to save image")
}) },
)
} }
pub fn run(self, regions: &[TileCoords]) -> Result<()> { pub fn run(self, regions: &[TileCoords]) -> Result<()> {

View file

@ -6,12 +6,12 @@ use std::{
}; };
use anyhow::{Context, Ok, Result}; 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); pub struct FileMetaVersion(pub u32);
#[derive(Debug, Serialize)] #[derive(Debug, Serialize, Deserialize)]
struct FileMeta { struct FileMeta {
version: FileMetaVersion, version: FileMetaVersion,
timestamp: SystemTime, timestamp: SystemTime,
@ -115,6 +115,18 @@ pub fn modified_timestamp(path: &Path) -> Result<SystemTime> {
}) })
} }
pub fn read_timestamp(path: &Path, version: FileMetaVersion) -> Option<SystemTime> {
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<T, F>( pub fn create_with_timestamp<T, F>(
path: &Path, path: &Path,
version: FileMetaVersion, version: FileMetaVersion,