Search in sources :

Example 1 with ParallelProgressManager

use of org.pepsoft.util.ParallelProgressManager in project WorldPainter by Captain-Chaos.

the class JavaWorldMerger method mergeDimension.

private void mergeDimension(final File worldDir, File backupWorldDir, final Dimension dimension, final Platform platform, ProgressReceiver progressReceiver) throws ProgressReceiver.OperationCancelled, IOException {
    if (progressReceiver != null) {
        progressReceiver.setMessage("merging " + dimension.getName() + " dimension");
    }
    final File dimensionDir, backupDimensionDir;
    switch(dimension.getDim()) {
        case org.pepsoft.worldpainter.Constants.DIM_NORMAL:
            dimensionDir = worldDir;
            backupDimensionDir = backupWorldDir;
            break;
        case org.pepsoft.worldpainter.Constants.DIM_NETHER:
            dimensionDir = new File(worldDir, "DIM-1");
            backupDimensionDir = new File(backupWorldDir, "DIM-1");
            break;
        case org.pepsoft.worldpainter.Constants.DIM_END:
            dimensionDir = new File(worldDir, "DIM1");
            backupDimensionDir = new File(backupWorldDir, "DIM1");
            break;
        default:
            throw new IllegalArgumentException("Dimension " + dimension.getDim() + " not supported");
    }
    File regionDir = new File(dimensionDir, "region");
    if (!regionDir.exists()) {
        regionDir.mkdirs();
    }
    dimension.rememberChanges();
    try {
        // Gather all layers used on the map
        final Map<Layer, LayerExporter> exporters = new HashMap<>();
        Set<Layer> allLayers = dimension.getAllLayers(false);
        allLayers.addAll(dimension.getMinimumLayers());
        // If there are combined layers, apply them and gather any newly
        // added layers, recursively
        boolean done;
        do {
            done = true;
            for (Layer layer : new HashSet<>(allLayers)) {
                if (layer instanceof CombinedLayer) {
                    // Apply the combined layer
                    Set<Layer> addedLayers = ((CombinedLayer) layer).apply(dimension);
                    // Remove the combined layer from the list
                    allLayers.remove(layer);
                    // Add any layers it might have added
                    allLayers.addAll(addedLayers);
                    // Signal that we have to go around at least once more,
                    // in case any of the newly added layers are themselves
                    // combined layers
                    done = false;
                }
            }
        } while (!done);
        // Load all layer settings into the exporters
        for (Layer layer : allLayers) {
            @SuppressWarnings("unchecked") LayerExporter exporter = layer.getExporter();
            if (exporter != null) {
                exporter.setSettings(dimension.getLayerSettings(layer));
                exporters.put(layer, exporter);
            }
        }
        // Sort tiles into regions
        int lowestRegionX = Integer.MAX_VALUE, highestRegionX = Integer.MIN_VALUE, lowestRegionZ = Integer.MAX_VALUE, highestRegionZ = Integer.MIN_VALUE;
        Map<Point, Map<Point, Tile>> tilesByRegion = new HashMap<>();
        final boolean tileSelection = selectedTiles != null;
        if (tileSelection) {
            // Sanity check
            assert selectedDimensions.size() == 1;
            assert selectedDimensions.contains(dimension.getDim());
            for (Point tileCoords : selectedTiles) {
                Tile tile = dimension.getTile(tileCoords);
                boolean nonReadOnlyChunksFound = false;
                outerLoop: for (int chunkX = 0; chunkX < TILE_SIZE; chunkX += 16) {
                    for (int chunkY = 0; chunkY < TILE_SIZE; chunkY += 16) {
                        if (!tile.getBitLayerValue(ReadOnly.INSTANCE, chunkX, chunkY)) {
                            nonReadOnlyChunksFound = true;
                            break outerLoop;
                        }
                    }
                }
                if (!nonReadOnlyChunksFound) {
                    // be merged
                    continue;
                }
                int regionX = tileCoords.x >> 2;
                int regionZ = tileCoords.y >> 2;
                Point regionCoords = new Point(regionX, regionZ);
                Map<Point, Tile> tilesForRegion = tilesByRegion.computeIfAbsent(regionCoords, k -> new HashMap<>());
                tilesForRegion.put(tileCoords, tile);
                if (regionX < lowestRegionX) {
                    lowestRegionX = regionX;
                }
                if (regionX > highestRegionX) {
                    highestRegionX = regionX;
                }
                if (regionZ < lowestRegionZ) {
                    lowestRegionZ = regionZ;
                }
                if (regionZ > highestRegionZ) {
                    highestRegionZ = regionZ;
                }
            }
        } else {
            for (Tile tile : dimension.getTiles()) {
                boolean nonReadOnlyChunksFound = false;
                outerLoop: for (int chunkX = 0; chunkX < TILE_SIZE; chunkX += 16) {
                    for (int chunkY = 0; chunkY < TILE_SIZE; chunkY += 16) {
                        if (!tile.getBitLayerValue(ReadOnly.INSTANCE, chunkX, chunkY)) {
                            nonReadOnlyChunksFound = true;
                            break outerLoop;
                        }
                    }
                }
                if (!nonReadOnlyChunksFound) {
                    // be merged
                    continue;
                }
                int regionX = tile.getX() >> 2;
                int regionZ = tile.getY() >> 2;
                Point regionCoords = new Point(regionX, regionZ);
                Map<Point, Tile> tilesForRegion = tilesByRegion.computeIfAbsent(regionCoords, k -> new HashMap<>());
                tilesForRegion.put(new Point(tile.getX(), tile.getY()), tile);
                if (regionX < lowestRegionX) {
                    lowestRegionX = regionX;
                }
                if (regionX > highestRegionX) {
                    highestRegionX = regionX;
                }
                if (regionZ < lowestRegionZ) {
                    lowestRegionZ = regionZ;
                }
                if (regionZ > highestRegionZ) {
                    highestRegionZ = regionZ;
                }
            }
        }
        // Read the region coordinates of the existing map
        final File backupRegionDir = new File(backupDimensionDir, "region");
        final Pattern regionFilePattern = platform.equals(DefaultPlugin.JAVA_ANVIL) ? Pattern.compile("r\\.-?\\d+\\.-?\\d+\\.mca") : Pattern.compile("r\\.-?\\d+\\.-?\\d+\\.mcr");
        File[] existingRegionFiles = backupRegionDir.listFiles((dir, name) -> regionFilePattern.matcher(name).matches());
        Map<Point, File> existingRegions = new HashMap<>();
        for (File file : existingRegionFiles) {
            String[] parts = file.getName().split("\\.");
            int regionX = Integer.parseInt(parts[1]);
            int regionZ = Integer.parseInt(parts[2]);
            existingRegions.put(new Point(regionX, regionZ), file);
            if (regionX < lowestRegionX) {
                lowestRegionX = regionX;
            }
            if (regionX > highestRegionX) {
                highestRegionX = regionX;
            }
            if (regionZ < lowestRegionZ) {
                lowestRegionZ = regionZ;
            }
            if (regionZ > highestRegionZ) {
                highestRegionZ = regionZ;
            }
        }
        final Set<Point> allRegionCoords = new HashSet<>();
        allRegionCoords.addAll(tilesByRegion.keySet());
        allRegionCoords.addAll(existingRegions.keySet());
        // Sort the regions to export the first two rows together, and then
        // row by row, to get the optimum tempo of performing fixups
        List<Point> sortedRegions = new ArrayList<>(allRegionCoords.size());
        if (lowestRegionZ == highestRegionZ) {
            // No point in sorting it
            sortedRegions.addAll(allRegionCoords);
        } else {
            for (int x = lowestRegionX; x <= highestRegionX; x++) {
                for (int z = lowestRegionZ; z <= (lowestRegionZ + 1); z++) {
                    Point regionCoords = new Point(x, z);
                    if (allRegionCoords.contains(regionCoords)) {
                        sortedRegions.add(regionCoords);
                    }
                }
            }
            for (int z = lowestRegionZ + 2; z <= highestRegionZ; z++) {
                for (int x = lowestRegionX; x <= highestRegionX; x++) {
                    Point regionCoords = new Point(x, z);
                    if (allRegionCoords.contains(regionCoords)) {
                        sortedRegions.add(regionCoords);
                    }
                }
            }
        }
        // Merge each individual region
        final WorldPainterChunkFactory chunkFactory = new WorldPainterChunkFactory(dimension, exporters, world.getPlatform(), world.getMaxHeight());
        Runtime runtime = Runtime.getRuntime();
        runtime.gc();
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long memoryInUse = totalMemory - freeMemory;
        long maxMemory = runtime.maxMemory();
        long maxMemoryAvailable = maxMemory - memoryInUse;
        int maxThreadsByMem = (int) (maxMemoryAvailable / 250000000L);
        int threads;
        if (System.getProperty("org.pepsoft.worldpainter.threads") != null) {
            threads = Math.max(Math.min(Integer.parseInt(System.getProperty("org.pepsoft.worldpainter.threads")), tilesByRegion.size()), 1);
        } else {
            threads = Math.max(Math.min(Math.min(maxThreadsByMem, runtime.availableProcessors()), allRegionCoords.size()), 1);
        }
        logger.info("Using " + threads + " thread(s) for merge (cores: " + runtime.availableProcessors() + ", available memory: " + (maxMemoryAvailable / 1048576L) + " MB)");
        final Map<Point, List<Fixup>> fixups = new HashMap<>();
        final Set<Point> exportedRegions = new HashSet<>();
        ExecutorService executor = Executors.newFixedThreadPool(threads, new ThreadFactory() {

            @Override
            public synchronized Thread newThread(Runnable r) {
                Thread thread = new Thread(threadGroup, r, "Merger-" + nextID++);
                thread.setPriority(Thread.MIN_PRIORITY);
                return thread;
            }

            private final ThreadGroup threadGroup = new ThreadGroup("Mergers");

            private int nextID = 1;
        });
        final ParallelProgressManager parallelProgressManager = (progressReceiver != null) ? new ParallelProgressManager(progressReceiver, allRegionCoords.size()) : null;
        try {
            // Merge each individual region
            for (final Point regionCoords : sortedRegions) {
                if (existingRegions.containsKey(regionCoords)) {
                    if (tilesByRegion.containsKey(regionCoords)) {
                        // Region exists in new and existing maps; merge it
                        final Map<Point, Tile> tiles = tilesByRegion.get(regionCoords);
                        executor.execute(() -> {
                            ProgressReceiver progressReceiver1 = (parallelProgressManager != null) ? parallelProgressManager.createProgressReceiver() : null;
                            if (progressReceiver1 != null) {
                                try {
                                    progressReceiver1.checkForCancellation();
                                } catch (ProgressReceiver.OperationCancelled e) {
                                    return;
                                }
                            }
                            try {
                                List<Fixup> regionFixups = new ArrayList<>();
                                WorldRegion minecraftWorld = new WorldRegion(regionCoords.x, regionCoords.y, dimension.getMaxHeight(), platform);
                                try {
                                    String regionWarnings = mergeRegion(minecraftWorld, backupRegionDir, dimension, platform, regionCoords, tiles, tileSelection, exporters, chunkFactory, regionFixups, (progressReceiver1 != null) ? new SubProgressReceiver(progressReceiver1, 0.0f, 0.9f) : null);
                                    if (regionWarnings != null) {
                                        if (warnings == null) {
                                            warnings = regionWarnings;
                                        } else {
                                            warnings = warnings + regionWarnings;
                                        }
                                    }
                                    if (logger.isDebugEnabled()) {
                                        logger.debug("Merged region " + regionCoords.x + "," + regionCoords.y);
                                    }
                                } finally {
                                    minecraftWorld.save(worldDir, dimension.getDim());
                                }
                                synchronized (fixups) {
                                    if (!regionFixups.isEmpty()) {
                                        fixups.put(new Point(regionCoords.x, regionCoords.y), regionFixups);
                                    }
                                    exportedRegions.add(regionCoords);
                                }
                                // thread is not already doing it
                                if (performingFixups.tryAcquire()) {
                                    try {
                                        Map<Point, List<Fixup>> myFixups = new HashMap<>();
                                        synchronized (fixups) {
                                            for (Iterator<Map.Entry<Point, List<Fixup>>> i = fixups.entrySet().iterator(); i.hasNext(); ) {
                                                Map.Entry<Point, List<Fixup>> entry = i.next();
                                                Point fixupRegionCoords = entry.getKey();
                                                if (isReadyForFixups(allRegionCoords, exportedRegions, fixupRegionCoords)) {
                                                    myFixups.put(fixupRegionCoords, entry.getValue());
                                                    i.remove();
                                                }
                                            }
                                        }
                                        if (!myFixups.isEmpty()) {
                                            performFixups(worldDir, dimension, platform, (progressReceiver1 != null) ? new SubProgressReceiver(progressReceiver1, 0.9f, 0.1f) : null, myFixups);
                                        }
                                    } finally {
                                        performingFixups.release();
                                    }
                                }
                            } catch (Throwable t) {
                                if (progressReceiver1 != null) {
                                    progressReceiver1.exceptionThrown(t);
                                } else {
                                    logger.error("Exception while exporting region", t);
                                }
                            }
                        });
                    } else {
                        // Region only exists in existing world. Copy it to the new
                        // world
                        ProgressReceiver subProgressReceiver = (parallelProgressManager != null) ? parallelProgressManager.createProgressReceiver() : null;
                        if (subProgressReceiver != null) {
                            subProgressReceiver.setMessage("Copying region " + regionCoords.x + "," + regionCoords.y + " unchanged");
                        }
                        FileUtils.copyFileToDir(existingRegions.get(regionCoords), regionDir, subProgressReceiver);
                        synchronized (fixups) {
                            exportedRegions.add(regionCoords);
                        }
                        if (logger.isDebugEnabled()) {
                            logger.debug("Copied region " + regionCoords.x + "," + regionCoords.y);
                        }
                    }
                } else {
                    // Region only exists in new world. Create it as new
                    executor.execute(() -> {
                        ProgressReceiver progressReceiver1 = (parallelProgressManager != null) ? parallelProgressManager.createProgressReceiver() : null;
                        if (progressReceiver1 != null) {
                            try {
                                progressReceiver1.checkForCancellation();
                            } catch (ProgressReceiver.OperationCancelled e) {
                                return;
                            }
                        }
                        try {
                            WorldRegion minecraftWorld = new WorldRegion(regionCoords.x, regionCoords.y, dimension.getMaxHeight(), platform);
                            ExportResults exportResults = null;
                            try {
                                exportResults = exportRegion(minecraftWorld, dimension, null, platform, regionCoords, tileSelection, exporters, null, chunkFactory, null, (progressReceiver1 != null) ? new SubProgressReceiver(progressReceiver1, 0.9f, 0.1f) : null);
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Generated region " + regionCoords.x + "," + regionCoords.y);
                                }
                            } finally {
                                if ((exportResults != null) && exportResults.chunksGenerated) {
                                    minecraftWorld.save(worldDir, dimension.getDim());
                                }
                            }
                            synchronized (fixups) {
                                if ((exportResults.fixups != null) && (!exportResults.fixups.isEmpty())) {
                                    fixups.put(new Point(regionCoords.x, regionCoords.y), exportResults.fixups);
                                }
                                exportedRegions.add(regionCoords);
                            }
                            // thread is not already doing it
                            if (performingFixups.tryAcquire()) {
                                try {
                                    Map<Point, List<Fixup>> myFixups = new HashMap<>();
                                    synchronized (fixups) {
                                        for (Iterator<Map.Entry<Point, List<Fixup>>> i = fixups.entrySet().iterator(); i.hasNext(); ) {
                                            Map.Entry<Point, List<Fixup>> entry = i.next();
                                            Point fixupRegionCoords = entry.getKey();
                                            if (isReadyForFixups(allRegionCoords, exportedRegions, fixupRegionCoords)) {
                                                myFixups.put(fixupRegionCoords, entry.getValue());
                                                i.remove();
                                            }
                                        }
                                    }
                                    if (!myFixups.isEmpty()) {
                                        performFixups(worldDir, dimension, platform, (progressReceiver1 != null) ? new SubProgressReceiver(progressReceiver1, 0.9f, 0.1f) : null, myFixups);
                                    }
                                } finally {
                                    performingFixups.release();
                                }
                            }
                        } catch (Throwable t) {
                            if (progressReceiver1 != null) {
                                progressReceiver1.exceptionThrown(t);
                            } else {
                                logger.error("Exception while exporting region", t);
                            }
                        }
                    });
                }
            }
        } finally {
            executor.shutdown();
            try {
                executor.awaitTermination(1000, TimeUnit.DAYS);
            } catch (InterruptedException e) {
                throw new RuntimeException("Thread interrupted while waiting for all tasks to finish", e);
            }
        }
        // performing fixups and thread B added new ones and then quit
        synchronized (fixups) {
            if (!fixups.isEmpty()) {
                if (progressReceiver != null) {
                    progressReceiver.setMessage("doing remaining fixups for " + dimension.getName());
                    progressReceiver.reset();
                }
                performFixups(worldDir, dimension, platform, (progressReceiver != null) ? new SubProgressReceiver(progressReceiver, 0.9f, 0.1f) : null, fixups);
            }
        }
        if (progressReceiver != null) {
            progressReceiver.setProgress(1.0f);
        }
    } finally {
        // Undo any changes we made (such as applying any combined layers)
        if (dimension.undoChanges()) {
            // TODO: some kind of cleverer undo mechanism (undo history
            // cloning?) so we don't mess up the user's redo history
            dimension.clearRedo();
            dimension.armSavePoint();
        }
    }
}
Also used : ThreadFactory(java.util.concurrent.ThreadFactory) List(java.util.List) SubProgressReceiver(org.pepsoft.util.SubProgressReceiver) SubProgressReceiver(org.pepsoft.util.SubProgressReceiver) ProgressReceiver(org.pepsoft.util.ProgressReceiver) HistoryEntry(org.pepsoft.worldpainter.history.HistoryEntry) Pattern(java.util.regex.Pattern) ParallelProgressManager(org.pepsoft.util.ParallelProgressManager) ExecutorService(java.util.concurrent.ExecutorService)

Example 2 with ParallelProgressManager

use of org.pepsoft.util.ParallelProgressManager in project WorldPainter by Captain-Chaos.

the class AbstractWorldExporter method parallelExportRegions.

protected final ChunkFactory.Stats parallelExportRegions(Dimension dimension, Platform platform, File worldDir, ProgressReceiver progressReceiver) throws OperationCancelled {
    if (progressReceiver != null) {
        progressReceiver.setMessage("Exporting " + dimension.getName() + " dimension");
    }
    long start = System.currentTimeMillis();
    final Dimension ceiling;
    switch(dimension.getDim()) {
        case DIM_NORMAL:
            ceiling = dimension.getWorld().getDimension(DIM_NORMAL_CEILING);
            break;
        case DIM_NETHER:
            ceiling = dimension.getWorld().getDimension(DIM_NETHER_CEILING);
            break;
        case DIM_END:
            ceiling = dimension.getWorld().getDimension(DIM_END_CEILING);
            break;
        default:
            throw new IllegalArgumentException("Dimension " + dimension.getDim() + " not supported");
    }
    final ChunkFactory.Stats collectedStats = new ChunkFactory.Stats();
    boolean wasDirty = dimension.isDirty(), ceilingWasDirty = (ceiling != null) && ceiling.isDirty();
    dimension.rememberChanges();
    if (ceiling != null) {
        ceiling.rememberChanges();
    }
    try {
        final Map<Layer, LayerExporter> exporters = setupDimensionForExport(dimension);
        final Map<Layer, LayerExporter> ceilingExporters = (ceiling != null) ? setupDimensionForExport(ceiling) : null;
        // Determine regions to export
        int lowestRegionX = Integer.MAX_VALUE, highestRegionX = Integer.MIN_VALUE, lowestRegionZ = Integer.MAX_VALUE, highestRegionZ = Integer.MIN_VALUE;
        final Set<Point> regions = new HashSet<>(), exportedRegions = new HashSet<>();
        final boolean tileSelection = selectedTiles != null;
        if (tileSelection) {
            // Sanity check
            assert selectedDimensions.size() == 1;
            assert selectedDimensions.contains(dimension.getDim());
            for (Point tile : selectedTiles) {
                int regionX = tile.x >> 2;
                int regionZ = tile.y >> 2;
                regions.add(new Point(regionX, regionZ));
                if (regionX < lowestRegionX) {
                    lowestRegionX = regionX;
                }
                if (regionX > highestRegionX) {
                    highestRegionX = regionX;
                }
                if (regionZ < lowestRegionZ) {
                    lowestRegionZ = regionZ;
                }
                if (regionZ > highestRegionZ) {
                    highestRegionZ = regionZ;
                }
            }
        } else {
            for (Tile tile : dimension.getTiles()) {
                // Also add regions for any bedrock wall and/or border
                // tiles, if present
                int r = (((dimension.getBorder() != null) && (!dimension.getBorder().isEndless())) ? dimension.getBorderSize() : 0) + (((dimension.getBorder() == null) || (!dimension.getBorder().isEndless())) && dimension.isBedrockWall() ? 1 : 0);
                for (int dx = -r; dx <= r; dx++) {
                    for (int dy = -r; dy <= r; dy++) {
                        int regionX = (tile.getX() + dx) >> 2;
                        int regionZ = (tile.getY() + dy) >> 2;
                        regions.add(new Point(regionX, regionZ));
                        if (regionX < lowestRegionX) {
                            lowestRegionX = regionX;
                        }
                        if (regionX > highestRegionX) {
                            highestRegionX = regionX;
                        }
                        if (regionZ < lowestRegionZ) {
                            lowestRegionZ = regionZ;
                        }
                        if (regionZ > highestRegionZ) {
                            highestRegionZ = regionZ;
                        }
                    }
                }
            }
            if (ceiling != null) {
                for (Tile tile : ceiling.getTiles()) {
                    int regionX = tile.getX() >> 2;
                    int regionZ = tile.getY() >> 2;
                    regions.add(new Point(regionX, regionZ));
                    if (regionX < lowestRegionX) {
                        lowestRegionX = regionX;
                    }
                    if (regionX > highestRegionX) {
                        highestRegionX = regionX;
                    }
                    if (regionZ < lowestRegionZ) {
                        lowestRegionZ = regionZ;
                    }
                    if (regionZ > highestRegionZ) {
                        highestRegionZ = regionZ;
                    }
                }
            }
        }
        // Sort the regions to export the first two rows together, and then
        // row by row, to get the optimum tempo of performing fixups
        java.util.List<Point> sortedRegions = new ArrayList<>(regions.size());
        if (lowestRegionZ == highestRegionZ) {
            // No point in sorting it
            sortedRegions.addAll(regions);
        } else {
            for (int x = lowestRegionX; x <= highestRegionX; x++) {
                for (int z = lowestRegionZ; z <= (lowestRegionZ + 1); z++) {
                    Point regionCoords = new Point(x, z);
                    if (regions.contains(regionCoords)) {
                        sortedRegions.add(regionCoords);
                    }
                }
            }
            for (int z = lowestRegionZ + 2; z <= highestRegionZ; z++) {
                for (int x = lowestRegionX; x <= highestRegionX; x++) {
                    Point regionCoords = new Point(x, z);
                    if (regions.contains(regionCoords)) {
                        sortedRegions.add(regionCoords);
                    }
                }
            }
        }
        final WorldPainterChunkFactory chunkFactory = new WorldPainterChunkFactory(dimension, exporters, platform, world.getMaxHeight());
        final WorldPainterChunkFactory ceilingChunkFactory = (ceiling != null) ? new WorldPainterChunkFactory(ceiling, ceilingExporters, platform, world.getMaxHeight()) : null;
        Runtime runtime = Runtime.getRuntime();
        runtime.gc();
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long memoryInUse = totalMemory - freeMemory;
        long maxMemory = runtime.maxMemory();
        long maxMemoryAvailable = maxMemory - memoryInUse;
        int maxThreadsByMem = (int) (maxMemoryAvailable / 250000000L);
        int threads;
        if (System.getProperty("org.pepsoft.worldpainter.threads") != null) {
            threads = Math.max(Math.min(Integer.parseInt(System.getProperty("org.pepsoft.worldpainter.threads")), sortedRegions.size()), 1);
        } else {
            threads = Math.max(Math.min(Math.min(maxThreadsByMem, runtime.availableProcessors()), sortedRegions.size()), 1);
        }
        logger.info("Using " + threads + " thread(s) for export (cores: " + runtime.availableProcessors() + ", available memory: " + (maxMemoryAvailable / 1048576L) + " MB)");
        final Map<Point, List<Fixup>> fixups = new HashMap<>();
        ExecutorService executor = Executors.newFixedThreadPool(threads, new ThreadFactory() {

            @Override
            public synchronized Thread newThread(Runnable r) {
                Thread thread = new Thread(threadGroup, r, "Exporter-" + nextID++);
                thread.setPriority(Thread.MIN_PRIORITY);
                return thread;
            }

            private final ThreadGroup threadGroup = new ThreadGroup("Exporters");

            private int nextID = 1;
        });
        final ParallelProgressManager parallelProgressManager = (progressReceiver != null) ? new ParallelProgressManager(progressReceiver, regions.size()) : null;
        try {
            // Export each individual region
            for (Point region : sortedRegions) {
                final Point regionCoords = region;
                executor.execute(() -> {
                    ProgressReceiver progressReceiver1 = (parallelProgressManager != null) ? parallelProgressManager.createProgressReceiver() : null;
                    if (progressReceiver1 != null) {
                        try {
                            progressReceiver1.checkForCancellation();
                        } catch (OperationCancelled e) {
                            return;
                        }
                    }
                    try {
                        WorldRegion worldRegion = new WorldRegion(regionCoords.x, regionCoords.y, dimension.getMaxHeight(), platform);
                        ExportResults exportResults = null;
                        try {
                            exportResults = exportRegion(worldRegion, dimension, ceiling, platform, regionCoords, tileSelection, exporters, ceilingExporters, chunkFactory, ceilingChunkFactory, (progressReceiver1 != null) ? new SubProgressReceiver(progressReceiver1, 0.0f, 0.9f) : null);
                            if (logger.isDebugEnabled()) {
                                logger.debug("Generated region " + regionCoords.x + "," + regionCoords.y);
                            }
                            if (exportResults.chunksGenerated) {
                                synchronized (collectedStats) {
                                    collectedStats.landArea += exportResults.stats.landArea;
                                    collectedStats.surfaceArea += exportResults.stats.surfaceArea;
                                    collectedStats.waterArea += exportResults.stats.waterArea;
                                }
                            }
                        } finally {
                            if ((exportResults != null) && exportResults.chunksGenerated) {
                                long saveStart = System.currentTimeMillis();
                                worldRegion.save(worldDir, dimension.getDim());
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Saving region took {} ms", System.currentTimeMillis() - saveStart);
                                }
                            }
                        }
                        synchronized (fixups) {
                            if ((exportResults.fixups != null) && (!exportResults.fixups.isEmpty())) {
                                fixups.put(new Point(regionCoords.x, regionCoords.y), exportResults.fixups);
                            }
                            exportedRegions.add(regionCoords);
                        }
                        // thread is not already doing it
                        if (performingFixups.tryAcquire()) {
                            try {
                                Map<Point, List<Fixup>> myFixups = new HashMap<>();
                                synchronized (fixups) {
                                    for (Iterator<Map.Entry<Point, List<Fixup>>> i = fixups.entrySet().iterator(); i.hasNext(); ) {
                                        Map.Entry<Point, List<Fixup>> entry = i.next();
                                        Point fixupRegionCoords = entry.getKey();
                                        if (isReadyForFixups(regions, exportedRegions, fixupRegionCoords)) {
                                            myFixups.put(fixupRegionCoords, entry.getValue());
                                            i.remove();
                                        }
                                    }
                                }
                                if (!myFixups.isEmpty()) {
                                    performFixups(worldDir, dimension, platform, (progressReceiver1 != null) ? new SubProgressReceiver(progressReceiver1, 0.9f, 0.1f) : null, myFixups);
                                }
                            } finally {
                                performingFixups.release();
                            }
                        }
                    } catch (Throwable t) {
                        if (progressReceiver1 != null) {
                            progressReceiver1.exceptionThrown(t);
                        } else {
                            logger.error("Exception while exporting region", t);
                        }
                    }
                });
            }
        } finally {
            executor.shutdown();
            try {
                executor.awaitTermination(366, TimeUnit.DAYS);
            } catch (InterruptedException e) {
                throw new RuntimeException("Thread interrupted while waiting for all tasks to finish", e);
            }
        }
        // performing fixups and thread B added new ones and then quit
        synchronized (fixups) {
            if (!fixups.isEmpty()) {
                if (progressReceiver != null) {
                    progressReceiver.setMessage("Doing remaining fixups for " + dimension.getName());
                    progressReceiver.reset();
                }
                performFixups(worldDir, dimension, platform, progressReceiver, fixups);
            }
        }
        // Calculate total size of dimension
        collectedStats.time = System.currentTimeMillis() - start;
        if (progressReceiver != null) {
            progressReceiver.setProgress(1.0f);
        }
    } finally {
        // Undo any changes we made (such as applying any combined layers)
        if (dimension.undoChanges()) {
            // TODO: some kind of cleverer undo mechanism (undo history
            // cloning?) so we don't mess up the user's redo history
            dimension.clearRedo();
            dimension.armSavePoint();
        }
        // If the dimension wasn't dirty make sure it still isn't
        dimension.setDirty(wasDirty);
        if (ceiling != null) {
            // Undo any changes we made (such as applying any combined layers)
            if (ceiling.undoChanges()) {
                // TODO: some kind of cleverer undo mechanism (undo history
                // cloning?) so we don't mess up the user's redo history
                ceiling.clearRedo();
                ceiling.armSavePoint();
            }
            // If the dimension wasn't dirty make sure it still isn't
            ceiling.setDirty(ceilingWasDirty);
        }
    }
    return collectedStats;
}
Also used : List(java.util.List) OperationCancelled(org.pepsoft.util.ProgressReceiver.OperationCancelled) Tile(org.pepsoft.worldpainter.Tile) SubProgressReceiver(org.pepsoft.util.SubProgressReceiver) Dimension(org.pepsoft.worldpainter.Dimension) CustomLayer(org.pepsoft.worldpainter.layers.CustomLayer) CombinedLayer(org.pepsoft.worldpainter.layers.CombinedLayer) Layer(org.pepsoft.worldpainter.layers.Layer) java.util(java.util) ParallelProgressManager(org.pepsoft.util.ParallelProgressManager) SubProgressReceiver(org.pepsoft.util.SubProgressReceiver) ProgressReceiver(org.pepsoft.util.ProgressReceiver)

Aggregations

List (java.util.List)2 ParallelProgressManager (org.pepsoft.util.ParallelProgressManager)2 ProgressReceiver (org.pepsoft.util.ProgressReceiver)2 SubProgressReceiver (org.pepsoft.util.SubProgressReceiver)2 java.util (java.util)1 ExecutorService (java.util.concurrent.ExecutorService)1 ThreadFactory (java.util.concurrent.ThreadFactory)1 Pattern (java.util.regex.Pattern)1 OperationCancelled (org.pepsoft.util.ProgressReceiver.OperationCancelled)1 Dimension (org.pepsoft.worldpainter.Dimension)1 Tile (org.pepsoft.worldpainter.Tile)1 HistoryEntry (org.pepsoft.worldpainter.history.HistoryEntry)1 CombinedLayer (org.pepsoft.worldpainter.layers.CombinedLayer)1 CustomLayer (org.pepsoft.worldpainter.layers.CustomLayer)1 Layer (org.pepsoft.worldpainter.layers.Layer)1