From 5bdd2e2ff7ecbc09c849fe0aec926830b380de42 Mon Sep 17 00:00:00 2001 From: grimsace Date: Wed, 6 May 2026 11:02:19 -0500 Subject: [PATCH] made exporting a pool of images for maximum parrellelism --- src/exporter.rs | 461 ++++++++++++++++++++++++++++++------------------ 1 file changed, 289 insertions(+), 172 deletions(-) diff --git a/src/exporter.rs b/src/exporter.rs index e4a0c28..2939d86 100644 --- a/src/exporter.rs +++ b/src/exporter.rs @@ -109,6 +109,38 @@ struct SceneData { door_edges: HashSet<((usize, usize), (usize, usize))>, } +#[derive(Debug, Clone, Copy)] +enum MaskTask { + Floors, + Walls, + Doors, + Archways, + LockedDoors, + SecretDoors, + Windows, + StartMarkers, + EndMarkers, + TrapMarkers, + MonsterMarkers, + Stairs, + Grid, +} + +#[derive(Debug, Clone, Copy)] +enum CompositeTask { + Mask(MaskTask), + Composite, +} + +struct CompositeExportContext<'a> { + layout: &'a DungeonLayout, + folder: PathBuf, + geometry: ExportGeometry, + scene: SceneData, + wall_width: f32, + door_width: f32, +} + // Collect precomputed cell and edge sets needed for rendering. fn collect_scene_data(layout: &DungeonLayout, settings: &UiSettings) -> SceneData { let mut scene = SceneData::default(); @@ -193,21 +225,10 @@ pub fn export_to_target( match target { ExportTarget::Folder(folder) => { - for &idx in &export_indices { - let layout = &layouts[idx]; - if settings.export_format == ExportFormat::Folder { - let subfolder = if settings.export_level_index == 999 { - folder.join(format!("level {}", idx + 1)) - } else { - folder.clone() - }; - export_composite_masks(layout, settings, &subfolder)?; - } else { - let ext = settings.export_format.extension(); - let filename = format!("level{}.{ext}", idx + 1); - let path = folder.join(filename); - save_layout_to_file(layout, settings, &path)?; - } + if settings.export_format == ExportFormat::Folder { + export_composite_masks_for_levels(layouts, settings, &folder, &export_indices)?; + } else { + export_level_images_to_folder(layouts, settings, &folder, &export_indices)?; } Ok(folder) } @@ -219,6 +240,22 @@ pub fn export_to_target( } } +fn export_level_images_to_folder( + layouts: &[DungeonLayout], + settings: &UiSettings, + folder: &std::path::Path, + export_indices: &[usize], +) -> Result<(), String> { + std::fs::create_dir_all(folder).map_err(|err| format!("Failed creating folder: {err}"))?; + + let ext = settings.export_format.extension(); + export_indices.par_iter().try_for_each(|&idx| { + let filename = format!("level{}.{ext}", idx + 1); + let path = folder.join(filename); + save_layout_to_file(&layouts[idx], settings, &path) + }) +} + fn save_layout_to_file( layout: &DungeonLayout, settings: &UiSettings, @@ -250,6 +287,15 @@ fn save_layout_to_file( fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result { let g = ExportGeometry::new(settings.cols, settings.rows); let scene = collect_scene_data(layout, settings); + render_pixmap_with_scene(layout, settings, &g, &scene) +} + +fn render_pixmap_with_scene( + layout: &DungeonLayout, + settings: &UiSettings, + g: &ExportGeometry, + scene: &SceneData, +) -> Result { let mut pixmap = Pixmap::new(g.width, g.height).ok_or_else(|| "Failed to allocate canvas".to_string())?; fill_bg(&mut pixmap, BG); @@ -258,11 +304,11 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result Result Result Result Result Result Result Result Result Result Result<(), String> { - std::fs::create_dir_all(folder).map_err(|e| format!("Failed creating folder: {e}"))?; + let contexts = export_indices + .iter() + .map(|&idx| { + let subfolder = if settings.export_level_index == 999 { + folder.join(format!("level {}", idx + 1)) + } else { + folder.to_path_buf() + }; + std::fs::create_dir_all(&subfolder) + .map_err(|err| format!("Failed creating folder: {err}"))?; - let g = ExportGeometry::new(settings.cols, settings.rows); - let scene = collect_scene_data(layout, settings); - let wall_w = (g.cell / 2.5).max(1.0); - let door_w = (g.cell / 5.0).max(1.0); - - // List of tasks to perform in parallel - enum MaskTask { - Floors, - Walls, - Doors, - Archways, - LockedDoors, - SecretDoors, - Windows, - StartMarkers, - EndMarkers, - TrapMarkers, - MonsterMarkers, - Stairs, - Grid, - } + let geometry = ExportGeometry::new(settings.cols, settings.rows); + let scene = collect_scene_data(&layouts[idx], settings); + Ok(CompositeExportContext { + layout: &layouts[idx], + folder: subfolder, + geometry, + scene, + wall_width: (geometry.cell / 2.5).max(1.0), + door_width: (geometry.cell / 5.0).max(1.0), + }) + }) + .collect::, String>>()?; let mut tasks = vec![ MaskTask::Floors, @@ -421,129 +468,43 @@ fn export_composite_masks( tasks.push(MaskTask::Grid); } - // Process masks in parallel - tasks.into_par_iter().try_for_each(|task| { - let (stem, pixmap) = match task { - MaskTask::Floors => { - let mut pixmap = Pixmap::new(g.width, g.height).unwrap(); - fill_bg(&mut pixmap, BLACK); - for &(x, y) in &scene.corridor_cells { - fill_rect_cell(&mut pixmap, &g, x, y, WHITE); - } - for room in &layout.rooms { - fill_rect_room(&mut pixmap, &g, room, WHITE); - } - ("floors_mask", pixmap) - } - MaskTask::Walls => { - let mut pixmap = Pixmap::new(g.width, g.height).unwrap(); - fill_bg(&mut pixmap, BLACK); - draw_cell_walls( - &mut pixmap, - &g, - &scene.corridor_cells, - Some(&scene.corridor_edges), - &scene.door_edges, - wall_w, - WHITE, - ); - draw_cell_walls( - &mut pixmap, - &g, - &scene.room_cells, - Some(&scene.room_edges), - &scene.door_edges, - wall_w, - WHITE, - ); - ("walls_mask", pixmap) - } - MaskTask::Doors => { - let mut pixmap = blank_mask(&g, "doors").unwrap(); - for door in &layout.doors { - if !door.secret && !door.archway && !door.locked { - draw_door(&mut pixmap, &g, door, door_w, WHITE, DoorStyle::Solid); - } - } - ("doors_mask", pixmap) - } - MaskTask::Archways => { - let mut pixmap = blank_mask(&g, "archways").unwrap(); - for door in &layout.doors { - if door.archway { - draw_door(&mut pixmap, &g, door, door_w, WHITE, DoorStyle::Solid); - } - } - ("archways_mask", pixmap) - } - MaskTask::LockedDoors => { - let mut pixmap = blank_mask(&g, "locked doors").unwrap(); - for door in &layout.doors { - if door.locked { - draw_door(&mut pixmap, &g, door, door_w, WHITE, DoorStyle::Solid); - } - } - ("locked_doors_mask", pixmap) - } - MaskTask::SecretDoors => { - let mut pixmap = blank_mask(&g, "secret doors").unwrap(); - for door in &layout.doors { - if door.secret { - draw_door(&mut pixmap, &g, door, door_w, WHITE, DoorStyle::Solid); - } - } - ("secret_doors_mask", pixmap) - } - MaskTask::Windows => { - let mut pixmap = blank_mask(&g, "windows").unwrap(); - for window in &layout.windows { - draw_window(&mut pixmap, &g, window, door_w, WHITE, DoorStyle::Solid); - } - ("windows_mask", pixmap) - } - MaskTask::StartMarkers => { - let mut pixmap = blank_mask(&g, "start markers").unwrap(); - fill_marker_mask(&mut pixmap, &g, &layout.start_markers); - ("start_markers_mask", pixmap) - } - MaskTask::EndMarkers => { - let mut pixmap = blank_mask(&g, "end markers").unwrap(); - fill_marker_mask(&mut pixmap, &g, &layout.end_markers); - ("end_markers_mask", pixmap) - } - MaskTask::TrapMarkers => { - let mut pixmap = blank_mask(&g, "trap markers").unwrap(); - fill_marker_mask(&mut pixmap, &g, &layout.trap_markers); - ("trap_markers_mask", pixmap) - } - MaskTask::MonsterMarkers => { - let mut pixmap = blank_mask(&g, "monster markers").unwrap(); - fill_marker_mask(&mut pixmap, &g, &layout.monster_markers); - ("monster_markers_mask", pixmap) - } - MaskTask::Stairs => { - let mut pixmap = blank_mask(&g, "stairs").unwrap(); - for staircase in &layout.stairs { - fill_rect_staircase(&mut pixmap, &g, staircase, WHITE); - } - ("stairs_mask", pixmap) - } - MaskTask::Grid => { - let mut pixmap = Pixmap::new(g.width, g.height).unwrap(); - fill_bg(&mut pixmap, BLACK); - draw_grid_color(&mut pixmap, &g, WHITE); - ("grid_mask", pixmap) - } - }; + let export_tasks = contexts + .iter() + .enumerate() + .flat_map(|(context_idx, _)| { + tasks + .iter() + .copied() + .map(move |task| (context_idx, CompositeTask::Mask(task))) + .chain(std::iter::once((context_idx, CompositeTask::Composite))) + }) + .collect::>(); - save_mask_image(folder, stem, &pixmap, settings) - })?; + export_tasks + .into_par_iter() + .try_for_each(|(context_idx, task)| { + let context = &contexts[context_idx]; + match task { + CompositeTask::Mask(mask_task) => { + let (stem, pixmap) = render_mask_pixmap(context, mask_task)?; + save_mask_image(&context.folder, stem, &pixmap, settings) + } + CompositeTask::Composite => save_composite_image(context, settings), + } + })?; - // Also export the composite image - let composite = render_pixmap(layout, settings)?; + Ok(()) +} + +fn save_composite_image( + context: &CompositeExportContext<'_>, + settings: &UiSettings, +) -> Result<(), String> { + let composite = + render_pixmap_with_scene(context.layout, settings, &context.geometry, &context.scene)?; let composite_img = raster_image_from_pixmap(&composite, settings)?; let composite_ext = settings.export_format.extension(); - let composite_path = folder.join(format!("composite.{composite_ext}")); + let composite_path = context.folder.join(format!("composite.{composite_ext}")); composite_img .save_with_format(&composite_path, export_image_format(settings.export_format)) .map_err(|e| format!("Failed writing {}: {e}", composite_path.display()))?; @@ -551,6 +512,162 @@ fn export_composite_masks( Ok(()) } +fn render_mask_pixmap( + context: &CompositeExportContext<'_>, + task: MaskTask, +) -> Result<(&'static str, Pixmap), String> { + let g = &context.geometry; + let scene = &context.scene; + let layout = context.layout; + + let result = match task { + MaskTask::Floors => { + let mut pixmap = blank_mask(g, "floors")?; + for &(x, y) in &scene.corridor_cells { + fill_rect_cell(&mut pixmap, g, x, y, WHITE); + } + for room in &layout.rooms { + fill_rect_room(&mut pixmap, g, room, WHITE); + } + ("floors_mask", pixmap) + } + MaskTask::Walls => { + let mut pixmap = blank_mask(g, "walls")?; + draw_cell_walls( + &mut pixmap, + g, + &scene.corridor_cells, + Some(&scene.corridor_edges), + &scene.door_edges, + context.wall_width, + WHITE, + ); + draw_cell_walls( + &mut pixmap, + g, + &scene.room_cells, + Some(&scene.room_edges), + &scene.door_edges, + context.wall_width, + WHITE, + ); + ("walls_mask", pixmap) + } + MaskTask::Doors => { + let mut pixmap = blank_mask(g, "doors")?; + for door in &layout.doors { + if !door.secret && !door.archway && !door.locked { + draw_door( + &mut pixmap, + g, + door, + context.door_width, + WHITE, + DoorStyle::Solid, + ); + } + } + ("doors_mask", pixmap) + } + MaskTask::Archways => { + let mut pixmap = blank_mask(g, "archways")?; + for door in &layout.doors { + if door.archway { + draw_door( + &mut pixmap, + g, + door, + context.door_width, + WHITE, + DoorStyle::Solid, + ); + } + } + ("archways_mask", pixmap) + } + MaskTask::LockedDoors => { + let mut pixmap = blank_mask(g, "locked doors")?; + for door in &layout.doors { + if door.locked { + draw_door( + &mut pixmap, + g, + door, + context.door_width, + WHITE, + DoorStyle::Solid, + ); + } + } + ("locked_doors_mask", pixmap) + } + MaskTask::SecretDoors => { + let mut pixmap = blank_mask(g, "secret doors")?; + for door in &layout.doors { + if door.secret { + draw_door( + &mut pixmap, + g, + door, + context.door_width, + WHITE, + DoorStyle::Solid, + ); + } + } + ("secret_doors_mask", pixmap) + } + MaskTask::Windows => { + let mut pixmap = blank_mask(g, "windows")?; + for window in &layout.windows { + draw_window( + &mut pixmap, + g, + window, + context.door_width, + WHITE, + DoorStyle::Solid, + ); + } + ("windows_mask", pixmap) + } + MaskTask::StartMarkers => { + let mut pixmap = blank_mask(g, "start markers")?; + fill_marker_mask(&mut pixmap, g, &layout.start_markers); + ("start_markers_mask", pixmap) + } + MaskTask::EndMarkers => { + let mut pixmap = blank_mask(g, "end markers")?; + fill_marker_mask(&mut pixmap, g, &layout.end_markers); + ("end_markers_mask", pixmap) + } + MaskTask::TrapMarkers => { + let mut pixmap = blank_mask(g, "trap markers")?; + fill_marker_mask(&mut pixmap, g, &layout.trap_markers); + ("trap_markers_mask", pixmap) + } + MaskTask::MonsterMarkers => { + let mut pixmap = blank_mask(g, "monster markers")?; + fill_marker_mask(&mut pixmap, g, &layout.monster_markers); + ("monster_markers_mask", pixmap) + } + MaskTask::Stairs => { + let mut pixmap = blank_mask(g, "stairs")?; + for staircase in &layout.stairs { + fill_rect_staircase(&mut pixmap, g, staircase, WHITE); + } + ("stairs_mask", pixmap) + } + MaskTask::Grid => { + let mut pixmap = blank_mask(g, "grid")?; + draw_grid_color(&mut pixmap, g, WHITE); + ("grid_mask", pixmap) + } + }; + + Ok(result) +} + fn blank_mask(g: &ExportGeometry, name: &str) -> Result { let mut pixmap = Pixmap::new(g.width, g.height) .ok_or_else(|| format!("Failed to allocate {name} mask canvas"))?;