mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-03-04 17:23:33 +01:00
core: split TileCollector trait out of TileMipmapper
Make mipmap-style recursive processing of tiles reusable.
This commit is contained in:
parent
7297c03567
commit
825cf70e51
3 changed files with 206 additions and 107 deletions
|
@ -4,6 +4,7 @@ mod common;
|
|||
mod metadata_writer;
|
||||
mod region_group;
|
||||
mod region_processor;
|
||||
mod tile_collector;
|
||||
mod tile_mipmapper;
|
||||
mod tile_renderer;
|
||||
|
||||
|
|
107
src/core/tile_collector.rs
Normal file
107
src/core/tile_collector.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
//! A trait for recursively processing tiles
|
||||
//!
|
||||
//! Used for mipmap generation and collecting entity data
|
||||
|
||||
use std::sync::mpsc;
|
||||
|
||||
use anyhow::Result;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use super::common::*;
|
||||
|
||||
/// Helper to determine if no further mipmap levels are needed
|
||||
///
|
||||
/// If all tile coordinates are -1 or 0, further mipmap levels will not
|
||||
/// decrease the number of tiles and mipmap generated is considered finished.
|
||||
fn done(tiles: &TileCoordMap) -> bool {
|
||||
tiles
|
||||
.0
|
||||
.iter()
|
||||
.all(|(z, xs)| (-1..=0).contains(z) && xs.iter().all(|x| (-1..=0).contains(x)))
|
||||
}
|
||||
|
||||
/// Derives the map of populated tile coordinates for the next mipmap level
|
||||
fn map_coords(tiles: &TileCoordMap) -> TileCoordMap {
|
||||
let mut ret = TileCoordMap::default();
|
||||
|
||||
for (&z, xs) in &tiles.0 {
|
||||
for &x in xs {
|
||||
let xt = x >> 1;
|
||||
let zt = z >> 1;
|
||||
|
||||
ret.0.entry(zt).or_default().insert(xt);
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Trait to implement for collecting tiles recursively
|
||||
pub trait TileCollector: Sync {
|
||||
/// Return value of [TileCollector::collect_one]
|
||||
type CollectOutput: Send;
|
||||
|
||||
/// List of level 0 tiles
|
||||
fn tiles(&self) -> &[TileCoords];
|
||||
|
||||
/// Called at the beginning of each level of processing
|
||||
fn prepare(&self, level: usize) -> Result<()>;
|
||||
|
||||
/// Called at the end of each level of processing
|
||||
fn finish(
|
||||
&self,
|
||||
level: usize,
|
||||
outputs: impl Iterator<Item = Self::CollectOutput>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Called for each tile coordinate of the level that is currently being generated
|
||||
fn collect_one(
|
||||
&self,
|
||||
level: usize,
|
||||
coords: TileCoords,
|
||||
prev: &TileCoordMap,
|
||||
) -> Result<Self::CollectOutput>;
|
||||
|
||||
/// Collects tiles recursively
|
||||
fn collect_tiles(&self) -> Result<Vec<TileCoordMap>> {
|
||||
let mut tile_stack = {
|
||||
let mut tile_map = TileCoordMap::default();
|
||||
|
||||
for &TileCoords { x, z } in self.tiles() {
|
||||
tile_map.0.entry(z).or_default().insert(x);
|
||||
}
|
||||
|
||||
vec![tile_map]
|
||||
};
|
||||
|
||||
loop {
|
||||
let level = tile_stack.len();
|
||||
let prev = &tile_stack[level - 1];
|
||||
if done(prev) {
|
||||
break;
|
||||
}
|
||||
|
||||
self.prepare(level)?;
|
||||
|
||||
let next = map_coords(prev);
|
||||
|
||||
let (send, recv) = mpsc::channel();
|
||||
|
||||
next.0
|
||||
.par_iter()
|
||||
.flat_map(|(&z, xs)| xs.par_iter().map(move |&x| TileCoords { x, z }))
|
||||
.try_for_each(|coords| {
|
||||
let output = self.collect_one(level, coords, prev)?;
|
||||
send.send(output).unwrap();
|
||||
anyhow::Ok(())
|
||||
})?;
|
||||
|
||||
drop(send);
|
||||
self.finish(level, recv.into_iter())?;
|
||||
|
||||
tile_stack.push(next);
|
||||
}
|
||||
|
||||
Ok(tile_stack)
|
||||
}
|
||||
}
|
|
@ -1,14 +1,53 @@
|
|||
//! The [TileMipmapper]
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::ops::Add;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use rayon::prelude::*;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use super::common::*;
|
||||
use super::{common::*, tile_collector::TileCollector};
|
||||
use crate::{io::fs, types::*};
|
||||
|
||||
/// Counters for the number of processed and total tiles
|
||||
///
|
||||
/// Used as return of [TileMipmapper::collect_one]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MipmapStat {
|
||||
/// Total number of tiles
|
||||
total: usize,
|
||||
/// Processed number of tiles
|
||||
processed: usize,
|
||||
}
|
||||
|
||||
impl MipmapStat {
|
||||
/// Mipmap step return when none of the input files exist
|
||||
const NOT_FOUND: MipmapStat = MipmapStat {
|
||||
total: 0,
|
||||
processed: 0,
|
||||
};
|
||||
/// Mipmap step return when output file is up-to-date
|
||||
const SKIPPED: MipmapStat = MipmapStat {
|
||||
total: 1,
|
||||
processed: 0,
|
||||
};
|
||||
/// Mipmap step return when a new output file has been generated
|
||||
const PROCESSED: MipmapStat = MipmapStat {
|
||||
total: 1,
|
||||
processed: 1,
|
||||
};
|
||||
}
|
||||
|
||||
impl Add for MipmapStat {
|
||||
type Output = MipmapStat;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
MipmapStat {
|
||||
total: self.total + rhs.total,
|
||||
processed: self.processed + rhs.processed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates mipmap tiles from full-resolution tile images
|
||||
pub struct TileMipmapper<'a> {
|
||||
/// Common MinedMap configuration from command line
|
||||
|
@ -17,39 +56,63 @@ pub struct TileMipmapper<'a> {
|
|||
regions: &'a [TileCoords],
|
||||
}
|
||||
|
||||
impl<'a> TileCollector for TileMipmapper<'a> {
|
||||
type CollectOutput = MipmapStat;
|
||||
|
||||
fn tiles(&self) -> &[TileCoords] {
|
||||
self.regions
|
||||
}
|
||||
|
||||
fn prepare(&self, level: usize) -> Result<()> {
|
||||
info!("Generating level {} mipmaps...", level);
|
||||
|
||||
fs::create_dir_all(&self.config.tile_dir(TileKind::Map, level))?;
|
||||
fs::create_dir_all(&self.config.tile_dir(TileKind::Lightmap, level))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish(
|
||||
&self,
|
||||
level: usize,
|
||||
outputs: impl Iterator<Item = Self::CollectOutput>,
|
||||
) -> Result<()> {
|
||||
let stat = outputs.fold(
|
||||
MipmapStat {
|
||||
total: 0,
|
||||
processed: 0,
|
||||
},
|
||||
MipmapStat::add,
|
||||
);
|
||||
info!(
|
||||
"Generated level {} mipmaps ({} processed, {} unchanged)",
|
||||
level,
|
||||
stat.processed,
|
||||
stat.total - stat.processed,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_one(
|
||||
&self,
|
||||
level: usize,
|
||||
coords: TileCoords,
|
||||
prev: &TileCoordMap,
|
||||
) -> Result<Self::CollectOutput> {
|
||||
let map_stat = self.render_mipmap::<image::Rgba<u8>>(TileKind::Map, level, coords, prev)?;
|
||||
let lightmap_stat =
|
||||
self.render_mipmap::<image::LumaA<u8>>(TileKind::Lightmap, level, coords, prev)?;
|
||||
Ok(map_stat + lightmap_stat)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TileMipmapper<'a> {
|
||||
/// Constructs a new TileMipmapper
|
||||
pub fn new(config: &'a Config, regions: &'a [TileCoords]) -> Self {
|
||||
TileMipmapper { config, regions }
|
||||
}
|
||||
|
||||
/// Helper to determine if no further mipmap levels are needed
|
||||
///
|
||||
/// If all tile coordinates are -1 or 0, further mipmap levels will not
|
||||
/// decrease the number of tiles and mipmap generated is considered finished.
|
||||
fn done(tiles: &TileCoordMap) -> bool {
|
||||
tiles
|
||||
.0
|
||||
.iter()
|
||||
.all(|(z, xs)| (-1..=0).contains(z) && xs.iter().all(|x| (-1..=0).contains(x)))
|
||||
}
|
||||
|
||||
/// Derives the map of populated tile coordinates for the next mipmap level
|
||||
fn map_coords(tiles: &TileCoordMap) -> TileCoordMap {
|
||||
let mut ret = TileCoordMap::default();
|
||||
|
||||
for (&z, xs) in &tiles.0 {
|
||||
for &x in xs {
|
||||
let xt = x >> 1;
|
||||
let zt = z >> 1;
|
||||
|
||||
ret.0.entry(zt).or_default().insert(xt);
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Renders and saves a single mipmap tile image
|
||||
///
|
||||
/// Each mipmap tile is rendered by taking 2x2 tiles from the
|
||||
|
@ -60,9 +123,7 @@ impl<'a> TileMipmapper<'a> {
|
|||
level: usize,
|
||||
coords: TileCoords,
|
||||
prev: &TileCoordMap,
|
||||
count_total: &mpsc::Sender<()>,
|
||||
count_processed: &mpsc::Sender<()>,
|
||||
) -> Result<()>
|
||||
) -> Result<MipmapStat>
|
||||
where
|
||||
[P::Subpixel]: image::EncodableLayout,
|
||||
image::ImageBuffer<P, Vec<P::Subpixel>>: Into<image::DynamicImage>,
|
||||
|
@ -97,11 +158,9 @@ impl<'a> TileMipmapper<'a> {
|
|||
.collect();
|
||||
|
||||
let Some(input_timestamp) = sources.iter().map(|(_, _, ts)| *ts).max() else {
|
||||
return Ok(());
|
||||
return Ok(MipmapStat::NOT_FOUND);
|
||||
};
|
||||
|
||||
count_total.send(()).unwrap();
|
||||
|
||||
if Some(input_timestamp) <= output_timestamp {
|
||||
debug!(
|
||||
"Skipping unchanged mipmap tile {}",
|
||||
|
@ -110,7 +169,7 @@ impl<'a> TileMipmapper<'a> {
|
|||
.expect("tile path must be in output directory")
|
||||
.display(),
|
||||
);
|
||||
return Ok(());
|
||||
return Ok(MipmapStat::SKIPPED);
|
||||
}
|
||||
|
||||
debug!(
|
||||
|
@ -156,79 +215,11 @@ impl<'a> TileMipmapper<'a> {
|
|||
},
|
||||
)?;
|
||||
|
||||
count_processed.send(()).unwrap();
|
||||
Ok(())
|
||||
Ok(MipmapStat::PROCESSED)
|
||||
}
|
||||
|
||||
/// Runs the mipmap generation
|
||||
pub fn run(self) -> Result<Vec<TileCoordMap>> {
|
||||
let mut tile_stack = {
|
||||
let mut tile_map = TileCoordMap::default();
|
||||
|
||||
for &TileCoords { x, z } in self.regions {
|
||||
tile_map.0.entry(z).or_default().insert(x);
|
||||
}
|
||||
|
||||
vec![tile_map]
|
||||
};
|
||||
|
||||
loop {
|
||||
let level = tile_stack.len();
|
||||
let prev = &tile_stack[level - 1];
|
||||
if Self::done(prev) {
|
||||
break;
|
||||
}
|
||||
|
||||
info!("Generating level {} mipmaps...", level);
|
||||
|
||||
fs::create_dir_all(&self.config.tile_dir(TileKind::Map, level))?;
|
||||
fs::create_dir_all(&self.config.tile_dir(TileKind::Lightmap, level))?;
|
||||
|
||||
let next = Self::map_coords(prev);
|
||||
|
||||
let (total_send, total_recv) = mpsc::channel();
|
||||
let (processed_send, processed_recv) = mpsc::channel();
|
||||
|
||||
next.0
|
||||
.par_iter()
|
||||
.flat_map(|(&z, xs)| xs.par_iter().map(move |&x| TileCoords { x, z }))
|
||||
.try_for_each(|coords| {
|
||||
self.render_mipmap::<image::Rgba<u8>>(
|
||||
TileKind::Map,
|
||||
level,
|
||||
coords,
|
||||
prev,
|
||||
&total_send,
|
||||
&processed_send,
|
||||
)?;
|
||||
self.render_mipmap::<image::LumaA<u8>>(
|
||||
TileKind::Lightmap,
|
||||
level,
|
||||
coords,
|
||||
prev,
|
||||
&total_send,
|
||||
&processed_send,
|
||||
)?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})?;
|
||||
|
||||
drop(total_send);
|
||||
let total = total_recv.into_iter().count();
|
||||
|
||||
drop(processed_send);
|
||||
let processed = processed_recv.into_iter().count();
|
||||
|
||||
info!(
|
||||
"Generated level {} mipmaps ({} processed, {} unchanged)",
|
||||
level,
|
||||
processed,
|
||||
total - processed,
|
||||
);
|
||||
|
||||
tile_stack.push(next);
|
||||
}
|
||||
|
||||
Ok(tile_stack)
|
||||
self.collect_tiles()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue