core/region_processor: store processed block entities for each region

This commit is contained in:
Matthias Schiffer 2023-11-25 21:46:55 +01:00
parent c44f6ab859
commit 7297c03567
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
2 changed files with 101 additions and 20 deletions

View file

@ -9,7 +9,12 @@ use std::{
use indexmap::IndexSet;
use serde::{Deserialize, Serialize};
use crate::{io::fs::FileMetaVersion, resource::Biome, types::*, world::layer};
use crate::{
io::fs::FileMetaVersion,
resource::Biome,
types::*,
world::{block_entity::BlockEntity, layer},
};
/// Increase to force regeneration of all output files
@ -36,6 +41,11 @@ pub const LIGHTMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0);
/// Increase when the mipmap generation changes (this should not happen)
pub const MIPMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0);
/// MinedMap processed entity data version number
///
/// Increase when entity collection changes bacause of code changes.
pub const ENTITIES_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0);
/// Coordinate pair of a generated tile
///
/// Each tile corresponds to one Minecraft region file
@ -94,6 +104,13 @@ pub struct ProcessedRegion {
pub chunks: ChunkArray<Option<Box<ProcessedChunk>>>,
}
/// Data structure for storing entity data between processing and collection steps
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ProcessedEntities {
/// List of block entities
pub block_entities: Vec<BlockEntity>,
}
/// Derives a filename from region coordinates and a file extension
///
/// Can be used for input regions, processed data or rendered tiles
@ -122,6 +139,8 @@ pub struct Config {
pub output_dir: PathBuf,
/// Path for storage of intermediate processed data files
pub processed_dir: PathBuf,
/// Path for storage of processed entity data files
pub entities_dir: PathBuf,
/// Path of viewer metadata file
pub metadata_path: PathBuf,
}
@ -137,7 +156,8 @@ impl Config {
let region_dir = [&args.input_dir, Path::new("region")].iter().collect();
let level_dat_path = [&args.input_dir, Path::new("level.dat")].iter().collect();
let processed_dir = [&args.output_dir, Path::new("processed")].iter().collect();
let processed_dir: PathBuf = [&args.output_dir, Path::new("processed")].iter().collect();
let entities_dir = [&processed_dir, Path::new("entities")].iter().collect();
let metadata_path = [&args.output_dir, Path::new("info.json")].iter().collect();
Config {
@ -146,6 +166,7 @@ impl Config {
level_dat_path,
output_dir: args.output_dir.clone(),
processed_dir,
entities_dir,
metadata_path,
}
}
@ -162,6 +183,20 @@ impl Config {
[&self.processed_dir, Path::new(&filename)].iter().collect()
}
/// Constructs the base output path for processed entity data
pub fn entities_dir(&self, level: usize) -> PathBuf {
[&self.entities_dir, Path::new(&level.to_string())]
.iter()
.collect()
}
/// Constructs the path of a processed entity data file
pub fn entities_path(&self, level: usize, coords: TileCoords) -> PathBuf {
let filename = coord_filename(coords, "bin");
let dir = self.entities_dir(level);
[Path::new(&dir), Path::new(&filename)].iter().collect()
}
/// Constructs the base output path for a [TileKind] and mipmap level
pub fn tile_dir(&self, kind: TileKind, level: usize) -> PathBuf {
let prefix = match kind {

View file

@ -64,20 +64,28 @@ struct SingleRegionProcessor<'a> {
output_path: PathBuf,
/// Lightmap output filename
lightmap_path: PathBuf,
/// Processed entity output filename
entities_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>,
/// Timestamp of last modification of entity list output file (if valid)
entities_timestamp: Option<SystemTime>,
/// True if processed region output file needs to be updated
output_needed: bool,
/// True if lightmap output file needs to be updated
lightmap_needed: bool,
/// True if entity output file needs to be updated
entities_needed: bool,
/// Processed region intermediate data
processed_region: ProcessedRegion,
/// 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,
}
@ -93,14 +101,20 @@ impl<'a> SingleRegionProcessor<'a> {
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 entities_path = processor.config.entities_path(0, coords);
let entities_timestamp = fs::read_timestamp(&entities_path, ENTITIES_FILE_META_VERSION);
let output_needed = Some(input_timestamp) > output_timestamp;
let lightmap_needed = Some(input_timestamp) > lightmap_timestamp;
let entities_needed = Some(input_timestamp) > entities_timestamp;
let processed_region = ProcessedRegion::default();
let lightmap = image::GrayAlphaImage::new(N, N);
let entities = ProcessedEntities::default();
Ok(SingleRegionProcessor {
block_types: &processor.block_types,
@ -109,13 +123,17 @@ impl<'a> SingleRegionProcessor<'a> {
input_path,
output_path,
lightmap_path,
entities_path,
input_timestamp,
output_timestamp,
lightmap_timestamp,
entities_timestamp,
output_needed,
lightmap_needed,
entities_needed,
processed_region,
lightmap,
entities,
has_unknown: false,
})
}
@ -174,23 +192,39 @@ impl<'a> SingleRegionProcessor<'a> {
)
}
/// Saves processed entity data
///
/// The timestamp is the time of the last modification of the input region data.
fn save_entities(&self) -> Result<()> {
if !self.entities_needed {
return Ok(());
}
storage::write_file(
&self.entities_path,
&self.entities,
storage::Format::Json,
ENTITIES_FILE_META_VERSION,
self.input_timestamp,
)
}
/// Processes a single chunk
fn process_chunk(&mut self, chunk_coords: ChunkCoords, data: world::de::Chunk) -> Result<()> {
let (chunk, has_unknown) =
world::chunk::Chunk::new(&data, self.block_types, self.biome_types)
.with_context(|| format!("Failed to decode chunk {:?}", chunk_coords))?;
self.has_unknown |= has_unknown;
let Some(layer::LayerData {
if self.output_needed || self.lightmap_needed {
if 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(());
};
{
if self.output_needed {
self.processed_region.chunks[chunk_coords] = Some(Box::new(ProcessedChunk {
blocks,
@ -203,6 +237,13 @@ impl<'a> SingleRegionProcessor<'a> {
let chunk_lightmap = Self::render_chunk_lightmap(block_light);
overlay_chunk(&mut self.lightmap, &chunk_lightmap, chunk_coords);
}
}
}
if self.entities_needed {
let mut block_entities = chunk.block_entities();
self.entities.block_entities.append(&mut block_entities);
}
Ok(())
}
@ -215,7 +256,7 @@ impl<'a> SingleRegionProcessor<'a> {
/// Processes the region
fn run(mut self) -> Result<RegionProcessorStatus> {
if !self.output_needed && !self.lightmap_needed {
if !self.output_needed && !self.lightmap_needed && !self.entities_needed {
debug!(
"Skipping unchanged region r.{}.{}.mca",
self.coords.x, self.coords.z
@ -229,7 +270,10 @@ impl<'a> SingleRegionProcessor<'a> {
);
if let Err(err) = self.process_chunks() {
if self.output_timestamp.is_some() && self.lightmap_timestamp.is_some() {
if self.output_timestamp.is_some()
&& self.lightmap_timestamp.is_some()
&& self.entities_timestamp.is_some()
{
warn!(
"Failed to process region {:?}, using old data: {:?}",
self.coords, err
@ -246,6 +290,7 @@ impl<'a> SingleRegionProcessor<'a> {
self.save_region()?;
self.save_lightmap()?;
self.save_entities()?;
Ok(if self.has_unknown {
RegionProcessorStatus::OkWithUnknown
@ -313,6 +358,7 @@ impl<'a> RegionProcessor<'a> {
pub fn run(self) -> Result<Vec<TileCoords>> {
fs::create_dir_all(&self.config.processed_dir)?;
fs::create_dir_all(&self.config.tile_dir(TileKind::Lightmap, 0))?;
fs::create_dir_all(&self.config.entities_dir(0))?;
info!("Processing region files...");