mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-03-04 17:23:33 +01:00
core/region_processor: store processed block entities for each region
This commit is contained in:
parent
c44f6ab859
commit
7297c03567
2 changed files with 101 additions and 20 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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...");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue