mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-04-20 19:35:08 +02:00
core/region_processor: refactor RegionProcessor::process_region()
This commit is contained in:
parent
fa15a4e6e5
commit
a5ad057e0c
1 changed files with 185 additions and 132 deletions
|
@ -1,21 +1,22 @@
|
|||
//! 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 indexmap::IndexSet;
|
||||
use rayon::prelude::*;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use super::common::*;
|
||||
use crate::{
|
||||
io::{fs, storage},
|
||||
resource::{self, Biome},
|
||||
resource,
|
||||
types::*,
|
||||
world::{
|
||||
self,
|
||||
layer::{self, LayerData},
|
||||
},
|
||||
world::{self, layer},
|
||||
};
|
||||
|
||||
/// Parses a filename in the format r.X.Z.mca into the contained X and Z values
|
||||
|
@ -44,6 +45,182 @@ enum RegionProcessorStatus {
|
|||
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
|
||||
///
|
||||
/// The RegionProcessor builds lightmap tiles as well as processed region data
|
||||
|
@ -91,133 +268,9 @@ impl<'a> RegionProcessor<'a> {
|
|||
.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
|
||||
fn process_region(&self, coords: TileCoords) -> Result<RegionProcessorStatus> {
|
||||
/// Width/height of the region data
|
||||
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)
|
||||
SingleRegionProcessor::new(self, coords)?.run()
|
||||
}
|
||||
|
||||
/// Iterates over all region files of a Minecraft save directory
|
||||
|
|
Loading…
Add table
Reference in a new issue