From 9fdd960009934b9eb6042fceb618fd4356b67e34 Mon Sep 17 00:00:00 2001 From: louis-e <44675238+louis-e@users.noreply.github.com> Date: Sun, 7 Dec 2025 18:18:12 +0100 Subject: [PATCH] Fix world lock held during map preview generation --- src/data_processing.rs | 107 ++++++++++++++++++++++++++--------------- src/gui.rs | 26 ++++++++-- 2 files changed, 92 insertions(+), 41 deletions(-) diff --git a/src/data_processing.rs b/src/data_processing.rs index c0d199e..998d746 100644 --- a/src/data_processing.rs +++ b/src/data_processing.rs @@ -275,6 +275,8 @@ pub fn generate_world_with_options( // Save world editor.save(); + emit_gui_progress_update(99.0, "Finalizing world..."); + // Update player spawn Y coordinate based on terrain height after generation #[cfg(feature = "gui")] if world_format == WorldFormat::JavaAnvil { @@ -303,8 +305,6 @@ pub fn generate_world_with_options( } } - emit_gui_progress_update(99.0, "Finalizing world..."); - // For Bedrock format, emit event to open the mcworld file if world_format == WorldFormat::BedrockMcWorld { if let Some(path_str) = output_path.to_str() { @@ -312,41 +312,72 @@ pub fn generate_world_with_options( } } - // Generate top-down map preview silently in background after completion (Java only) - // Skip map preview for very large areas to avoid memory issues - const MAX_MAP_PREVIEW_AREA: i64 = 6400 * 6900; - let world_width = (xzbbox.max_x() - xzbbox.min_x()) as i64; - let world_height = (xzbbox.max_z() - xzbbox.min_z()) as i64; - let world_area = world_width * world_height; - - if world_format == WorldFormat::JavaAnvil && world_area <= MAX_MAP_PREVIEW_AREA { - let world_path = args.path.clone(); - let bounds = ( - xzbbox.min_x(), - xzbbox.max_x(), - xzbbox.min_z(), - xzbbox.max_z(), - ); - std::thread::spawn(move || { - // Use catch_unwind to prevent any panic from affecting the application - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - map_renderer::render_world_map(&world_path, bounds.0, bounds.1, bounds.2, bounds.3) - })); - - match result { - Ok(Ok(_path)) => { - // Notify the GUI that the map preview is ready - emit_map_preview_ready(); - } - Ok(Err(e)) => { - eprintln!("Warning: Failed to generate map preview: {}", e); - } - Err(_) => { - eprintln!("Warning: Map preview generation panicked unexpectedly"); - } - } - }); - } - Ok(output_path) } + +/// Information needed to generate a map preview after world generation is complete +#[derive(Clone)] +pub struct MapPreviewInfo { + pub world_path: PathBuf, + pub min_x: i32, + pub max_x: i32, + pub min_z: i32, + pub max_z: i32, + pub world_area: i64, +} + +impl MapPreviewInfo { + /// Create MapPreviewInfo from world bounds + pub fn new(world_path: PathBuf, xzbbox: &XZBBox) -> Self { + let world_width = (xzbbox.max_x() - xzbbox.min_x()) as i64; + let world_height = (xzbbox.max_z() - xzbbox.min_z()) as i64; + Self { + world_path, + min_x: xzbbox.min_x(), + max_x: xzbbox.max_x(), + min_z: xzbbox.min_z(), + max_z: xzbbox.max_z(), + world_area: world_width * world_height, + } + } +} + +/// Maximum area for which map preview generation is allowed (to avoid memory issues) +pub const MAX_MAP_PREVIEW_AREA: i64 = 6400 * 6900; + +/// Start map preview generation in a background thread. +/// This should be called AFTER the world generation is complete, the session lock is released, +/// and the GUI has been notified of 100% completion. +/// +/// For Java worlds only, and only if the world area is within limits. +pub fn start_map_preview_generation(info: MapPreviewInfo) { + if info.world_area > MAX_MAP_PREVIEW_AREA { + return; + } + + std::thread::spawn(move || { + // Use catch_unwind to prevent any panic from affecting the application + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + map_renderer::render_world_map( + &info.world_path, + info.min_x, + info.max_x, + info.min_z, + info.max_z, + ) + })); + + match result { + Ok(Ok(_path)) => { + // Notify the GUI that the map preview is ready + emit_map_preview_ready(); + } + Ok(Err(e)) => { + eprintln!("Warning: Failed to generate map preview: {}", e); + } + Err(_) => { + eprintln!("Warning: Map preview generation panicked unexpectedly"); + } + } + }); +} diff --git a/src/gui.rs b/src/gui.rs index 64d8964..3efbc1f 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -963,17 +963,27 @@ fn gui_start_generation( let _ = data_processing::generate_world_with_options( parsed_elements, - xzbbox, + xzbbox.clone(), args.bbox, ground, &args, - generation_options, + generation_options.clone(), ); // Explicitly release session lock before showing Done message // so Minecraft can open the world immediately drop(_session_lock); emit_gui_progress_update(100.0, "Done! World generation completed."); println!("{}", "Done! World generation completed.".green().bold()); + + // Start map preview generation silently in background (Java only) + if world_format == WorldFormat::JavaAnvil { + let preview_info = data_processing::MapPreviewInfo::new( + generation_options.path.clone(), + &xzbbox, + ); + data_processing::start_map_preview_generation(preview_info); + } + return Ok(()); } @@ -1006,7 +1016,7 @@ fn gui_start_generation( let _ = data_processing::generate_world_with_options( parsed_elements, - xzbbox, + xzbbox.clone(), args.bbox, ground, &args, @@ -1017,6 +1027,16 @@ fn gui_start_generation( drop(_session_lock); emit_gui_progress_update(100.0, "Done! World generation completed."); println!("{}", "Done! World generation completed.".green().bold()); + + // Start map preview generation silently in background (Java only) + if world_format == WorldFormat::JavaAnvil { + let preview_info = data_processing::MapPreviewInfo::new( + generation_options.path.clone(), + &xzbbox, + ); + data_processing::start_map_preview_generation(preview_info); + } + Ok(()) } Err(e) => {