core/region_processor: refactor RegionProcessor::process_region()

This commit is contained in:
Matthias Schiffer 2023-11-25 22:54:08 +01:00
parent fa15a4e6e5
commit a5ad057e0c
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C

View file

@ -1,21 +1,22 @@
//! The [RegionProcessor] and related functions //! The [RegionProcessor] and related functions
use std::{ffi::OsStr, path::Path, sync::mpsc, time::SystemTime}; use std::{
ffi::OsStr,
path::{Path, PathBuf},
sync::mpsc,
time::SystemTime,
};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use indexmap::IndexSet;
use rayon::prelude::*; use rayon::prelude::*;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use super::common::*; use super::common::*;
use crate::{ use crate::{
io::{fs, storage}, io::{fs, storage},
resource::{self, Biome}, resource,
types::*, types::*,
world::{ world::{self, layer},
self,
layer::{self, LayerData},
},
}; };
/// Parses a filename in the format r.X.Z.mca into the contained X and Z values /// Parses a filename in the format r.X.Z.mca into the contained X and Z values
@ -44,6 +45,182 @@ enum RegionProcessorStatus {
ErrorMissing, ErrorMissing,
} }
/// Handles processing for a single region
struct SingleRegionProcessor<'a> {
/// Registry of known block types
block_types: &'a resource::BlockTypes,
/// Registry of known biome types
biome_types: &'a resource::BiomeTypes,
/// Coordinates of the region this instance is processing
coords: TileCoords,
/// Input region filename
input_path: PathBuf,
/// Processed region data output filename
output_path: PathBuf,
/// Lightmap output filename
lightmap_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<SystemTime>,
/// Timestamp of last modification of lightmap output file (if valid)
lightmap_timestamp: Option<SystemTime>,
/// Processed region intermediate data
processed_region: ProcessedRegion,
/// Lightmap intermediate data
lightmap: image::GrayAlphaImage,
}
impl<'a> SingleRegionProcessor<'a> {
/// Initializes a [SingleRegionProcessor]
fn new(processor: &'a RegionProcessor<'a>, coords: TileCoords) -> Result<Self> {
/// Width/height of the region data
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
let input_path = processor.config.region_path(coords);
let input_timestamp = fs::modified_timestamp(&input_path)?;
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 processed_region = ProcessedRegion::default();
let lightmap = image::GrayAlphaImage::new(N, N);
Ok(SingleRegionProcessor {
block_types: &processor.block_types,
biome_types: &processor.biome_types,
coords,
input_path,
output_path,
lightmap_path,
input_timestamp,
output_timestamp,
lightmap_timestamp,
processed_region,
lightmap,
})
}
/// Renders a lightmap subtile from chunk block light data
fn render_chunk_lightmap(
block_light: Box<world::layer::BlockLightArray>,
) -> image::GrayAlphaImage {
/// Width/height of generated chunk lightmap
const N: u32 = BLOCKS_PER_CHUNK as u32;
image::GrayAlphaImage::from_fn(N, N, |x, z| {
let v: f32 = block_light[LayerBlockCoords {
x: BlockX::new(x),
z: BlockZ::new(z),
}]
.into();
image::LumaA([0, (192.0 * (1.0 - v / 15.0)) as u8])
})
}
/// Saves processed region data
///
/// The timestamp is the time of the last modification of the input region data.
fn save_region(
path: &Path,
processed_region: &ProcessedRegion,
timestamp: SystemTime,
) -> Result<()> {
storage::write(path, processed_region, REGION_FILE_META_VERSION, timestamp)
}
/// Saves a lightmap tile
///
/// The timestamp is the time of the last modification of the input region data.
fn save_lightmap(
path: &Path,
lightmap: &image::GrayAlphaImage,
timestamp: SystemTime,
) -> Result<()> {
fs::create_with_timestamp(path, LIGHTMAP_FILE_META_VERSION, timestamp, |file| {
lightmap
.write_to(file, image::ImageFormat::Png)
.context("Failed to save image")
})
}
/// Processes the region
fn run(mut self) -> Result<RegionProcessorStatus> {
if Some(self.input_timestamp) <= self.output_timestamp
&& Some(self.input_timestamp) <= self.lightmap_timestamp
{
debug!(
"Skipping unchanged region r.{}.{}.mca",
self.coords.x, self.coords.z
);
return Ok(RegionProcessorStatus::Skipped);
}
debug!(
"Processing region r.{}.{}.mca",
self.coords.x, self.coords.z
);
if let Err(err) = (|| -> Result<()> {
crate::nbt::region::from_file(&self.input_path)?.foreach_chunk(
|chunk_coords, data: world::de::Chunk| {
let chunk = world::chunk::Chunk::new(&data, self.block_types, self.biome_types)
.with_context(|| format!("Failed to decode chunk {:?}", chunk_coords))?;
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(());
};
self.processed_region.chunks[chunk_coords] = Some(Box::new(ProcessedChunk {
blocks,
biomes,
depths,
}));
let chunk_lightmap = Self::render_chunk_lightmap(block_light);
overlay_chunk(&mut self.lightmap, &chunk_lightmap, chunk_coords);
Ok(())
},
)
})() {
if self.output_timestamp.is_some() && self.lightmap_timestamp.is_some() {
warn!(
"Failed to process region {:?}, using old data: {:?}",
self.coords, err
);
return Ok(RegionProcessorStatus::ErrorOk);
} else {
warn!(
"Failed to process region {:?}, no old data available: {:?}",
self.coords, err
);
return Ok(RegionProcessorStatus::ErrorMissing);
}
}
if Some(self.input_timestamp) > self.output_timestamp {
Self::save_region(
&self.output_path,
&self.processed_region,
self.input_timestamp,
)?;
}
if Some(self.input_timestamp) > self.lightmap_timestamp {
Self::save_lightmap(&self.lightmap_path, &self.lightmap, self.input_timestamp)?;
}
Ok(RegionProcessorStatus::Ok)
}
}
/// Type with methods for processing the regions of a Minecraft save directory /// Type with methods for processing the regions of a Minecraft save directory
/// ///
/// The RegionProcessor builds lightmap tiles as well as processed region data /// The RegionProcessor builds lightmap tiles as well as processed region data
@ -91,133 +268,9 @@ impl<'a> RegionProcessor<'a> {
.collect()) .collect())
} }
/// Processes a single chunk
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(biome_list, &chunk)
}
/// Renders a lightmap subtile from chunk block light data
fn render_chunk_lightmap(
block_light: Box<world::layer::BlockLightArray>,
) -> image::GrayAlphaImage {
/// Width/height of generated chunk lightmap
const N: u32 = BLOCKS_PER_CHUNK as u32;
image::GrayAlphaImage::from_fn(N, N, |x, z| {
let v: f32 = block_light[LayerBlockCoords {
x: BlockX::new(x),
z: BlockZ::new(z),
}]
.into();
image::LumaA([0, (192.0 * (1.0 - v / 15.0)) as u8])
})
}
/// Saves processed region data
///
/// The timestamp is the time of the last modification of the input region data.
fn save_region(
path: &Path,
processed_region: &ProcessedRegion,
timestamp: SystemTime,
) -> Result<()> {
storage::write(path, processed_region, REGION_FILE_META_VERSION, timestamp)
}
/// Saves a lightmap tile
///
/// The timestamp is the time of the last modification of the input region data.
fn save_lightmap(
path: &Path,
lightmap: &image::GrayAlphaImage,
timestamp: SystemTime,
) -> Result<()> {
fs::create_with_timestamp(path, LIGHTMAP_FILE_META_VERSION, timestamp, |file| {
lightmap
.write_to(file, image::ImageFormat::Png)
.context("Failed to save image")
})
}
/// Processes a single region file /// Processes a single region file
fn process_region(&self, coords: TileCoords) -> Result<RegionProcessorStatus> { fn process_region(&self, coords: TileCoords) -> Result<RegionProcessorStatus> {
/// Width/height of the region data SingleRegionProcessor::new(self, coords)?.run()
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
let mut processed_region = ProcessedRegion::default();
let mut lightmap = image::GrayAlphaImage::new(N, N);
let input_path = self.config.region_path(coords);
let input_timestamp = fs::modified_timestamp(&input_path)?;
let output_path = self.config.processed_path(coords);
let output_timestamp = fs::read_timestamp(&output_path, REGION_FILE_META_VERSION);
let lightmap_path = self.config.tile_path(TileKind::Lightmap, 0, coords);
let lightmap_timestamp = fs::read_timestamp(&lightmap_path, LIGHTMAP_FILE_META_VERSION);
if Some(input_timestamp) <= output_timestamp && Some(input_timestamp) <= lightmap_timestamp
{
debug!("Skipping unchanged region r.{}.{}.mca", coords.x, coords.z);
return Ok(RegionProcessorStatus::Skipped);
}
debug!("Processing region r.{}.{}.mca", coords.x, coords.z);
if let Err(err) = (|| -> Result<()> {
crate::nbt::region::from_file(input_path)?.foreach_chunk(
|chunk_coords, data: world::de::Chunk| {
let Some(layer::LayerData {
blocks,
biomes,
block_light,
depths,
}) = self
.process_chunk(&mut processed_region.biome_list, data)
.with_context(|| format!("Failed to process chunk {:?}", chunk_coords))?
else {
return Ok(());
};
processed_region.chunks[chunk_coords] = Some(Box::new(ProcessedChunk {
blocks,
biomes,
depths,
}));
let chunk_lightmap = Self::render_chunk_lightmap(block_light);
overlay_chunk(&mut lightmap, &chunk_lightmap, chunk_coords);
Ok(())
},
)
})() {
if output_timestamp.is_some() && lightmap_timestamp.is_some() {
warn!(
"Failed to process region {:?}, using old data: {:?}",
coords, err
);
return Ok(RegionProcessorStatus::ErrorOk);
} else {
warn!(
"Failed to process region {:?}, no old data available: {:?}",
coords, err
);
return Ok(RegionProcessorStatus::ErrorMissing);
}
}
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(RegionProcessorStatus::Ok)
} }
/// Iterates over all region files of a Minecraft save directory /// Iterates over all region files of a Minecraft save directory