mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-04-20 11:35:07 +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
|
//! 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
|
||||||
|
|
Loading…
Add table
Reference in a new issue