mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-07-01 13:29:06 +02:00
Implement watch mode
This commit is contained in:
parent
37126f69fc
commit
c10e9e4902
5 changed files with 292 additions and 16 deletions
|
@ -135,6 +135,8 @@ pub enum TileKind {
|
|||
pub struct Config {
|
||||
/// Number of threads for parallel processing
|
||||
pub num_threads: usize,
|
||||
/// Number of threads for initial parallel processing
|
||||
pub num_threads_initial: usize,
|
||||
/// Path of input region directory
|
||||
pub region_dir: PathBuf,
|
||||
/// Path of input `level.dat` file
|
||||
|
@ -169,6 +171,7 @@ impl Config {
|
|||
Some(threads) => threads,
|
||||
None => 1,
|
||||
};
|
||||
let num_threads_initial = args.jobs_initial.unwrap_or(num_threads);
|
||||
|
||||
let region_dir = [&args.input_dir, Path::new("region")].iter().collect();
|
||||
let level_dat_path = [&args.input_dir, Path::new("level.dat")].iter().collect();
|
||||
|
@ -189,6 +192,7 @@ impl Config {
|
|||
|
||||
Ok(Config {
|
||||
num_threads,
|
||||
num_threads_initial,
|
||||
region_dir,
|
||||
level_dat_path,
|
||||
level_dat_old_path,
|
||||
|
|
116
src/core/mod.rs
116
src/core/mod.rs
|
@ -10,7 +10,12 @@ mod tile_merger;
|
|||
mod tile_mipmapper;
|
||||
mod tile_renderer;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::mpsc::{self, Receiver},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
|
@ -18,9 +23,13 @@ use git_version::git_version;
|
|||
|
||||
use common::{Config, ImageFormat};
|
||||
use metadata_writer::MetadataWriter;
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher as _};
|
||||
use rayon::ThreadPool;
|
||||
use region_processor::RegionProcessor;
|
||||
use tile_mipmapper::TileMipmapper;
|
||||
use tile_renderer::TileRenderer;
|
||||
use tokio::runtime::Runtime;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use self::entity_collector::EntityCollector;
|
||||
|
||||
|
@ -44,9 +53,26 @@ pub struct Args {
|
|||
/// use one thread per logical CPU core.
|
||||
#[arg(short, long)]
|
||||
pub jobs: Option<usize>,
|
||||
/// Number of parallel threads to use for initial processing
|
||||
///
|
||||
/// Passing this option only makes sense with --watch. The first run after
|
||||
/// starting MinedMap will use as many parallel jobs as configured using
|
||||
/// --job-initial, while subsequent regenerations of tiles will use the
|
||||
/// the number configured using --jobs.
|
||||
///
|
||||
/// If not given, the value from the --jobs option is used.
|
||||
#[arg(long)]
|
||||
pub jobs_initial: Option<usize>,
|
||||
/// Enable verbose messages
|
||||
#[arg(short, long)]
|
||||
pub verbose: bool,
|
||||
/// Watch for file changes and regenerate tiles automatically instead of
|
||||
/// exiting after generation
|
||||
#[arg(long)]
|
||||
pub watch: bool,
|
||||
/// Minimum delay between map generation cycles in watch mode
|
||||
#[arg(long, value_parser = humantime::parse_duration, default_value = "30s")]
|
||||
pub watch_delay: Duration,
|
||||
/// Format of generated map tiles
|
||||
#[arg(long, value_enum, default_value_t)]
|
||||
pub image_format: ImageFormat,
|
||||
|
@ -74,14 +100,73 @@ pub struct Args {
|
|||
pub output_dir: PathBuf,
|
||||
}
|
||||
|
||||
/// Configures the Rayon thread pool for parallel processing
|
||||
fn setup_threads(num_threads: usize) -> Result<()> {
|
||||
/// Configures a Rayon thread pool for parallel processing
|
||||
fn setup_threads(num_threads: usize) -> Result<ThreadPool> {
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(num_threads)
|
||||
.build_global()
|
||||
.build()
|
||||
.context("Failed to configure thread pool")
|
||||
}
|
||||
|
||||
/// Runs all MinedMap generation steps, updating all tiles as needed
|
||||
fn generate(config: &Config, rt: &Runtime) -> Result<()> {
|
||||
let regions = RegionProcessor::new(config).run()?;
|
||||
TileRenderer::new(config, rt, ®ions).run()?;
|
||||
let tiles = TileMipmapper::new(config, ®ions).run()?;
|
||||
EntityCollector::new(config, ®ions).run()?;
|
||||
MetadataWriter::new(config, &tiles).run()
|
||||
}
|
||||
|
||||
/// Creates a file watcher for the
|
||||
fn create_watcher(args: &Args) -> Result<(RecommendedWatcher, Receiver<()>)> {
|
||||
let (tx, rx) = mpsc::sync_channel::<()>(1);
|
||||
let mut watcher = notify::recommended_watcher(move |res| {
|
||||
// Ignore errors - we already have a watch trigger queued if try_send() fails
|
||||
let event: notify::Event = match res {
|
||||
Ok(event) => event,
|
||||
Err(err) => {
|
||||
warn!("Watch error: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let notify::EventKind::Modify(modify_kind) = event.kind else {
|
||||
return;
|
||||
};
|
||||
if !matches!(
|
||||
modify_kind,
|
||||
notify::event::ModifyKind::Data(_)
|
||||
| notify::event::ModifyKind::Name(notify::event::RenameMode::To)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if !event
|
||||
.paths
|
||||
.iter()
|
||||
.any(|path| path.ends_with("level.dat") || path.extension() == Some("mcu".as_ref()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let _ = tx.try_send(());
|
||||
})?;
|
||||
watcher.watch(&args.input_dir, RecursiveMode::Recursive)?;
|
||||
Ok((watcher, rx))
|
||||
}
|
||||
|
||||
/// Watches the data directory for changes, returning when a change has happened
|
||||
fn wait_watcher(args: &Args, watch_channel: &Receiver<()>) -> Result<()> {
|
||||
info!("Watching for changes...");
|
||||
let () = watch_channel
|
||||
.recv()
|
||||
.context("Failed to read watch event channel")?;
|
||||
info!("Change detected.");
|
||||
|
||||
thread::sleep(args.watch_delay);
|
||||
|
||||
let _ = watch_channel.try_recv();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// MinedMap CLI main function
|
||||
pub fn cli() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
@ -96,17 +181,26 @@ pub fn cli() -> Result<()> {
|
|||
.with_target(false)
|
||||
.init();
|
||||
|
||||
setup_threads(config.num_threads)?;
|
||||
let mut pool = setup_threads(config.num_threads_initial)?;
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let regions = RegionProcessor::new(&config).run()?;
|
||||
TileRenderer::new(&config, &rt, ®ions).run()?;
|
||||
let tiles = TileMipmapper::new(&config, ®ions).run()?;
|
||||
EntityCollector::new(&config, ®ions).run()?;
|
||||
MetadataWriter::new(&config, &tiles).run()?;
|
||||
let watch = args.watch.then(|| create_watcher(&args)).transpose()?;
|
||||
|
||||
Ok(())
|
||||
pool.install(|| generate(&config, &rt))?;
|
||||
|
||||
let Some((_watcher, watch_channel)) = watch else {
|
||||
// watch mode disabled
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if config.num_threads != config.num_threads_initial {
|
||||
pool = setup_threads(config.num_threads)?;
|
||||
}
|
||||
pool.install(move || loop {
|
||||
wait_watcher(&args, &watch_channel)?;
|
||||
generate(&config, &rt)?;
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue