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 indexmap::IndexSet;
use serde::{Deserialize, Serialize}; 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 /// 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) /// Increase when the mipmap generation changes (this should not happen)
pub const MIPMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0); 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 /// Coordinate pair of a generated tile
/// ///
/// Each tile corresponds to one Minecraft region file /// Each tile corresponds to one Minecraft region file
@ -94,6 +104,13 @@ pub struct ProcessedRegion {
pub chunks: ChunkArray<Option<Box<ProcessedChunk>>>, 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 /// Derives a filename from region coordinates and a file extension
/// ///
/// Can be used for input regions, processed data or rendered tiles /// Can be used for input regions, processed data or rendered tiles
@ -122,6 +139,8 @@ pub struct Config {
pub output_dir: PathBuf, pub output_dir: PathBuf,
/// Path for storage of intermediate processed data files /// Path for storage of intermediate processed data files
pub processed_dir: PathBuf, pub processed_dir: PathBuf,
/// Path for storage of processed entity data files
pub entities_dir: PathBuf,
/// Path of viewer metadata file /// Path of viewer metadata file
pub metadata_path: PathBuf, pub metadata_path: PathBuf,
} }
@ -137,7 +156,8 @@ impl Config {
let region_dir = [&args.input_dir, Path::new("region")].iter().collect(); 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 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(); let metadata_path = [&args.output_dir, Path::new("info.json")].iter().collect();
Config { Config {
@ -146,6 +166,7 @@ impl Config {
level_dat_path, level_dat_path,
output_dir: args.output_dir.clone(), output_dir: args.output_dir.clone(),
processed_dir, processed_dir,
entities_dir,
metadata_path, metadata_path,
} }
} }
@ -162,6 +183,20 @@ impl Config {
[&self.processed_dir, Path::new(&filename)].iter().collect() [&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 /// Constructs the base output path for a [TileKind] and mipmap level
pub fn tile_dir(&self, kind: TileKind, level: usize) -> PathBuf { pub fn tile_dir(&self, kind: TileKind, level: usize) -> PathBuf {
let prefix = match kind { let prefix = match kind {

View file

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