core/region_processor: refactor

Separate configuration and mutable state, also allowing to avoid a few
mem::take().
This commit is contained in:
Matthias Schiffer 2025-03-13 20:27:51 +01:00
parent 53a0f24600
commit 708fb9645d
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C

View file

@ -1,6 +1,6 @@
//! The [RegionProcessor] and related functions //! The [RegionProcessor] and related functions
use std::{ffi::OsStr, mem, path::PathBuf, sync::mpsc, time::SystemTime}; use std::{ffi::OsStr, path::PathBuf, sync::mpsc, time::SystemTime};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use enum_map::{Enum, EnumMap}; use enum_map::{Enum, EnumMap};
@ -45,6 +45,37 @@ enum RegionProcessorStatus {
ErrorMissing, ErrorMissing,
} }
/// Data of a region being processed by a [SingleRegionProcessor]
#[derive(Debug)]
struct SingleRegionData {
/// [IndexSet] of biomes used by the processed region
biome_list: IndexSet<Biome>,
/// Processed region chunk intermediate data
chunks: ChunkArray<Option<Box<ProcessedChunk>>>,
/// Lightmap intermediate data
lightmap: image::GrayAlphaImage,
/// Processed entity intermediate data
entities: ProcessedEntities,
/// True if any unknown block or biome types were encountered during processing
has_unknown: bool,
}
impl Default for SingleRegionData {
fn default() -> Self {
/// Width/height of the region data
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
let lightmap = image::GrayAlphaImage::new(N, N);
Self {
biome_list: Default::default(),
chunks: Default::default(),
lightmap,
entities: Default::default(),
has_unknown: false,
}
}
}
/// Handles processing for a single region /// Handles processing for a single region
struct SingleRegionProcessor<'a> { struct SingleRegionProcessor<'a> {
/// Registry of known block types /// Registry of known block types
@ -75,26 +106,13 @@ struct SingleRegionProcessor<'a> {
lightmap_needed: bool, lightmap_needed: bool,
/// True if entity output file needs to be updated /// True if entity output file needs to be updated
entities_needed: bool, entities_needed: bool,
/// [IndexSet] of biomes used by the processed region
biome_list: IndexSet<Biome>,
/// Processed region intermediate data
processed_region: ProcessedRegion,
/// Lightmap intermediate data
lightmap: image::GrayAlphaImage,
/// Processed entity intermediate data
entities: ProcessedEntities,
/// Format of generated map tiles /// Format of generated map tiles
image_format: image::ImageFormat, image_format: image::ImageFormat,
/// True if any unknown block or biome types were encountered during processing
has_unknown: bool,
} }
impl<'a> SingleRegionProcessor<'a> { impl<'a> SingleRegionProcessor<'a> {
/// Initializes a [SingleRegionProcessor] /// Initializes a [SingleRegionProcessor]
fn new(processor: &'a RegionProcessor<'a>, coords: TileCoords) -> Result<Self> { 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_path = processor.config.region_path(coords);
let input_timestamp = fs::modified_timestamp(&input_path)?; let input_timestamp = fs::modified_timestamp(&input_path)?;
@ -111,11 +129,6 @@ impl<'a> SingleRegionProcessor<'a> {
let lightmap_needed = Some(input_timestamp) > lightmap_timestamp; let lightmap_needed = Some(input_timestamp) > lightmap_timestamp;
let entities_needed = Some(input_timestamp) > entities_timestamp; let entities_needed = Some(input_timestamp) > entities_timestamp;
let processed_region = ProcessedRegion::default();
let biome_list = IndexSet::default();
let lightmap = image::GrayAlphaImage::new(N, N);
let entities = ProcessedEntities::default();
Ok(SingleRegionProcessor { Ok(SingleRegionProcessor {
block_types: &processor.block_types, block_types: &processor.block_types,
biome_types: &processor.biome_types, biome_types: &processor.biome_types,
@ -131,12 +144,7 @@ impl<'a> SingleRegionProcessor<'a> {
output_needed, output_needed,
lightmap_needed, lightmap_needed,
entities_needed, entities_needed,
processed_region,
biome_list,
lightmap,
entities,
image_format: processor.config.tile_image_format(), image_format: processor.config.tile_image_format(),
has_unknown: false,
}) })
} }
@ -160,14 +168,14 @@ impl<'a> SingleRegionProcessor<'a> {
/// Saves processed region data /// Saves processed region data
/// ///
/// The timestamp is the time of the last modification of the input region data. /// The timestamp is the time of the last modification of the input region data.
fn save_region(&self) -> Result<()> { fn save_region(&self, processed_region: &ProcessedRegion) -> Result<()> {
if !self.output_needed { if !self.output_needed {
return Ok(()); return Ok(());
} }
storage::write_file( storage::write_file(
&self.output_path, &self.output_path,
&self.processed_region, processed_region,
REGION_FILE_META_VERSION, REGION_FILE_META_VERSION,
self.input_timestamp, self.input_timestamp,
) )
@ -176,7 +184,7 @@ impl<'a> SingleRegionProcessor<'a> {
/// Saves a lightmap tile /// Saves a lightmap tile
/// ///
/// The timestamp is the time of the last modification of the input region data. /// The timestamp is the time of the last modification of the input region data.
fn save_lightmap(&self) -> Result<()> { fn save_lightmap(&self, lightmap: &image::GrayAlphaImage) -> Result<()> {
if !self.lightmap_needed { if !self.lightmap_needed {
return Ok(()); return Ok(());
} }
@ -186,7 +194,7 @@ impl<'a> SingleRegionProcessor<'a> {
LIGHTMAP_FILE_META_VERSION, LIGHTMAP_FILE_META_VERSION,
self.input_timestamp, self.input_timestamp,
|file| { |file| {
self.lightmap lightmap
.write_to(file, self.image_format) .write_to(file, self.image_format)
.context("Failed to save image") .context("Failed to save image")
}, },
@ -196,27 +204,32 @@ impl<'a> SingleRegionProcessor<'a> {
/// Saves processed entity data /// Saves processed entity data
/// ///
/// The timestamp is the time of the last modification of the input region data. /// The timestamp is the time of the last modification of the input region data.
fn save_entities(&mut self) -> Result<()> { fn save_entities(&self, entities: &mut ProcessedEntities) -> Result<()> {
if !self.entities_needed { if !self.entities_needed {
return Ok(()); return Ok(());
} }
self.entities.block_entities.sort_unstable(); entities.block_entities.sort_unstable();
storage::write_file( storage::write_file(
&self.entities_path, &self.entities_path,
&self.entities, entities,
ENTITIES_FILE_META_VERSION, ENTITIES_FILE_META_VERSION,
self.input_timestamp, self.input_timestamp,
) )
} }
/// Processes a single chunk /// Processes a single chunk
fn process_chunk(&mut self, chunk_coords: ChunkCoords, data: world::de::Chunk) -> Result<()> { fn process_chunk(
&self,
data: &mut SingleRegionData,
chunk_coords: ChunkCoords,
chunk_data: world::de::Chunk,
) -> Result<()> {
let (chunk, has_unknown) = let (chunk, has_unknown) =
world::chunk::Chunk::new(&data, self.block_types, self.biome_types) world::chunk::Chunk::new(&chunk_data, self.block_types, self.biome_types)
.with_context(|| format!("Failed to decode chunk {:?}", chunk_coords))?; .with_context(|| format!("Failed to decode chunk {:?}", chunk_coords))?;
self.has_unknown |= has_unknown; data.has_unknown |= has_unknown;
if self.output_needed || self.lightmap_needed { if self.output_needed || self.lightmap_needed {
if let Some(layer::LayerData { if let Some(layer::LayerData {
@ -224,11 +237,11 @@ impl<'a> SingleRegionProcessor<'a> {
biomes, biomes,
block_light, block_light,
depths, depths,
}) = world::layer::top_layer(&mut self.biome_list, &chunk) }) = world::layer::top_layer(&mut data.biome_list, &chunk)
.with_context(|| format!("Failed to process chunk {:?}", chunk_coords))? .with_context(|| format!("Failed to process chunk {:?}", chunk_coords))?
{ {
if self.output_needed { if self.output_needed {
self.processed_region.chunks[chunk_coords] = Some(Box::new(ProcessedChunk { data.chunks[chunk_coords] = Some(Box::new(ProcessedChunk {
blocks, blocks,
biomes, biomes,
depths, depths,
@ -237,7 +250,7 @@ impl<'a> SingleRegionProcessor<'a> {
if self.lightmap_needed { if self.lightmap_needed {
let chunk_lightmap = Self::render_chunk_lightmap(block_light); let chunk_lightmap = Self::render_chunk_lightmap(block_light);
overlay_chunk(&mut self.lightmap, &chunk_lightmap, chunk_coords); overlay_chunk(&mut data.lightmap, &chunk_lightmap, chunk_coords);
} }
} }
} }
@ -249,20 +262,21 @@ impl<'a> SingleRegionProcessor<'a> {
chunk_coords, chunk_coords,
) )
})?; })?;
self.entities.block_entities.append(&mut block_entities); data.entities.block_entities.append(&mut block_entities);
} }
Ok(()) Ok(())
} }
/// Processes the chunks of the region /// Processes the chunks of the region
fn process_chunks(&mut self) -> Result<()> { fn process_chunks(&self, data: &mut SingleRegionData) -> Result<()> {
crate::nbt::region::from_file(&self.input_path)? crate::nbt::region::from_file(&self.input_path)?.foreach_chunk(
.foreach_chunk(|chunk_coords, data| self.process_chunk(chunk_coords, data)) |chunk_coords, chunk_data| self.process_chunk(data, chunk_coords, chunk_data),
)
} }
/// Processes the region /// Processes the region
fn run(mut self) -> Result<RegionProcessorStatus> { fn run(&self) -> Result<RegionProcessorStatus> {
if !self.output_needed && !self.lightmap_needed && !self.entities_needed { if !self.output_needed && !self.lightmap_needed && !self.entities_needed {
debug!( debug!(
"Skipping unchanged region r.{}.{}.mca", "Skipping unchanged region r.{}.{}.mca",
@ -276,7 +290,9 @@ impl<'a> SingleRegionProcessor<'a> {
self.coords.x, self.coords.z self.coords.x, self.coords.z
); );
if let Err(err) = self.process_chunks() { let mut data = SingleRegionData::default();
if let Err(err) = self.process_chunks(&mut data) {
if self.output_timestamp.is_some() if self.output_timestamp.is_some()
&& self.lightmap_timestamp.is_some() && self.lightmap_timestamp.is_some()
&& self.entities_timestamp.is_some() && self.entities_timestamp.is_some()
@ -295,13 +311,16 @@ impl<'a> SingleRegionProcessor<'a> {
} }
} }
self.processed_region.biome_list = mem::take(&mut self.biome_list).into_iter().collect(); let processed_region = ProcessedRegion {
biome_list: data.biome_list.into_iter().collect(),
chunks: data.chunks,
};
self.save_region()?; self.save_region(&processed_region)?;
self.save_lightmap()?; self.save_lightmap(&data.lightmap)?;
self.save_entities()?; self.save_entities(&mut data.entities)?;
Ok(if self.has_unknown { Ok(if data.has_unknown {
RegionProcessorStatus::OkWithUnknown RegionProcessorStatus::OkWithUnknown
} else { } else {
RegionProcessorStatus::Ok RegionProcessorStatus::Ok