Ticket #19199: 19199.threading.5.patch

File 19199.threading.5.patch, 17.7 KB (added by taylor.smock, 5 years ago)

Drop listener code in ExtendedDialog

  • src/org/openstreetmap/josm/actions/SimplifyWayAction.java

    diff --git a/src/org/openstreetmap/josm/actions/SimplifyWayAction.java b/src/org/openstreetmap/josm/actions/SimplifyWayAction.java
    index de0c78790..e13f2b471 100644
    a b import static org.openstreetmap.josm.tools.I18n.trn;  
    88import java.awt.GridBagLayout;
    99import java.awt.event.ActionEvent;
    1010import java.awt.event.KeyEvent;
     11import java.awt.event.WindowAdapter;
     12import java.awt.event.WindowEvent;
    1113import java.util.ArrayList;
    1214import java.util.Arrays;
    1315import java.util.Collection;
    import java.util.LinkedList;  
    1719import java.util.List;
    1820import java.util.Objects;
    1921import java.util.Set;
     22import java.util.concurrent.ForkJoinTask;
     23import java.util.concurrent.atomic.AtomicBoolean;
     24import java.util.concurrent.atomic.AtomicReference;
    2025import java.util.stream.Collectors;
     26import java.util.stream.Stream;
    2127
    2228import javax.swing.BorderFactory;
    2329import javax.swing.JCheckBox;
    import org.openstreetmap.josm.gui.HelpAwareOptionPane;  
    4652import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
    4753import org.openstreetmap.josm.gui.MainApplication;
    4854import org.openstreetmap.josm.gui.Notification;
     55import org.openstreetmap.josm.gui.util.GuiHelper;
    4956import org.openstreetmap.josm.spi.preferences.Config;
    5057import org.openstreetmap.josm.spi.preferences.IPreferences;
    5158import org.openstreetmap.josm.tools.GBC;
    5259import org.openstreetmap.josm.tools.ImageProvider;
     60import org.openstreetmap.josm.tools.Logging;
    5361import org.openstreetmap.josm.tools.Shortcut;
    5462import org.openstreetmap.josm.tools.StreamUtils;
    5563
    import org.openstreetmap.josm.tools.StreamUtils;  
    5866 * @since 2575
    5967 */
    6068public class SimplifyWayAction extends JosmAction {
     69    private static final int MAX_WAYS_NO_ASK = 10;
    6170
    6271    /**
    6372     * Constructs a new {@code SimplifyWayAction}.
    public class SimplifyWayAction extends JosmAction {  
    120129    }
    121130
    122131    /**
    123      * Asks the user for max-err value used to simplify ways, if not remembered before
     132     * Asks the user for max-err value used to simplify ways, if not remembered before. This is not asynchronous.
    124133     * @param ways the ways that are being simplified (to show estimated number of nodes to be removed)
    125134     * @param text the text being shown
    126135     * @param auto whether it's called automatically (conversion) or by the user
    127136     * @return the max-err value or -1 if canceled
     137     * @see #askSimplifyWays(Collection, String, boolean, SimplifyWayActionListener...)
    128138     * @since 16566
    129139     */
    130140    public static double askSimplifyWays(List<Way> ways, String text, boolean auto) {
     141        AtomicReference<Double> returnVar = new AtomicReference<>((double) -1);
     142        AtomicBoolean hasRun = new AtomicBoolean();
     143        askSimplifyWays(ways, text, auto, (result, command) -> {
     144            synchronized (returnVar) {
     145                returnVar.set(result);
     146                returnVar.notifyAll();
     147                hasRun.set(true);
     148            }
     149        });
     150        if (!SwingUtilities.isEventDispatchThread()) {
     151            synchronized (returnVar) {
     152                while (!hasRun.get()) {
     153                    try {
     154                        // Wait 2 seconds
     155                        returnVar.wait(2000);
     156                    } catch (InterruptedException e) {
     157                        Logging.error(e);
     158                        Thread.currentThread().interrupt();
     159                    }
     160                }
     161            }
     162        }
     163        return returnVar.get();
     164    }
     165
     166    /**
     167     * Asks the user for max-err value used to simplify ways, if not remembered before
     168     * @param waysCollection the ways that are being simplified (to show estimated number of nodes to be removed)
     169     * @param text the text being shown
     170     * @param auto whether it's called automatically (conversion) or by the user
     171     * @param listeners Listeners to call when the max error is calculated
     172     * @since xxx
     173     */
     174    public static void askSimplifyWays(Collection<Way> waysCollection, String text, boolean auto, SimplifyWayActionListener... listeners) {
     175        final Collection<SimplifyWayActionListener> realListeners = Stream.of(listeners).filter(Objects::nonNull).collect(Collectors.toList());
     176        final List<Way> ways;
     177        if (waysCollection instanceof List) {
     178            ways = (List<Way>) waysCollection;
     179        } else {
     180            ways = new ArrayList<>(waysCollection);
     181        }
    131182        IPreferences s = Config.getPref();
    132183        String key = "simplify-way." + (auto ? "auto." : "");
    133184        String keyRemember = key + "remember";
    public class SimplifyWayAction extends JosmAction {  
    135186
    136187        String r = s.get(keyRemember, "ask");
    137188        if (auto && "no".equals(r)) {
    138             return -1;
     189            realListeners.forEach(listener -> listener.maxErrorListener(-1, null));
     190            return;
    139191        } else if ("yes".equals(r)) {
    140             return s.getDouble(keyError, 3.0);
     192            realListeners.forEach(listener -> listener.maxErrorListener(s.getDouble(keyError, 3.0), null));
     193            return;
    141194        }
    142195
    143196        JPanel p = new JPanel(new GridBagLayout());
    public class SimplifyWayAction extends JosmAction {  
    178231        } else {
    179232            ed.setButtonIcons("ok", "cancel");
    180233        }
    181 
    182         int ret = ed.showDialog().getValue();
    183         double val = (double) n.getValue();
    184         if (l.lastCommand != null && l.lastCommand.equals(UndoRedoHandler.getInstance().getLastCommand())) {
    185             UndoRedoHandler.getInstance().undo();
    186             l.lastCommand = null;
    187         }
    188         if (ret == 1) {
    189             s.putDouble(keyError, val);
    190             if (c.isSelected()) {
    191                 s.put(keyRemember, "yes");
    192             }
    193             return val;
    194         } else {
    195             if (auto && c.isSelected()) { //do not remember cancel for manual simplify, otherwise nothing would happen
    196                 s.put(keyRemember, "no");
     234        ed.addWindowListener(new WindowAdapter() {
     235            @Override
     236            public void windowClosed(WindowEvent e) {
     237                final double result;
     238                final int ret = ed.getValue();
     239                double val = (double) n.getValue();
     240                final Command command = l.lastCommand;
     241                if (l.lastCommand != null && l.lastCommand.equals(UndoRedoHandler.getInstance().getLastCommand())) {
     242                    UndoRedoHandler.getInstance().undo();
     243                    l.lastCommand = null;
     244                }
     245                if (ret == 1) {
     246                    s.putDouble(keyError, val);
     247                    if (c.isSelected()) {
     248                        s.put(keyRemember, "yes");
     249                    }
     250                    result = val;
     251                } else {
     252                    if (auto && c.isSelected()) { //do not remember cancel for manual simplify, otherwise nothing would happen
     253                        s.put(keyRemember, "no");
     254                    }
     255                    result = -1;
     256                }
     257                realListeners.forEach(listener -> listener.maxErrorListener(result, command));
    197258            }
    198             return -1;
    199         }
     259        });
     260        GuiHelper.runInEDT(ed::showDialog);
    200261    }
    201262
    202263    @Override
    203264    public void actionPerformed(ActionEvent e) {
    204265        DataSet ds = getLayerManager().getEditDataSet();
    205         ds.update(() -> {
    206             List<Way> ways = ds.getSelectedWays().stream()
    207                     .filter(p -> !p.isIncomplete())
    208                     .collect(Collectors.toList());
    209             if (ways.isEmpty()) {
    210                 alertSelectAtLeastOneWay();
    211                 return;
    212             } else if (!confirmWayWithNodesOutsideBoundingBox(ways) || (ways.size() > 10 && !confirmSimplifyManyWays(ways.size()))) {
    213                 return;
    214             }
    215 
    216             String lengthstr = SystemOfMeasurement.getSystemOfMeasurement().getDistText(
    217                     ways.stream().mapToDouble(Way::getLength).sum());
    218 
    219             double err = askSimplifyWays(ways, trn(
    220                     "You are about to simplify {0} way with a total length of {1}.",
    221                     "You are about to simplify {0} ways with a total length of {1}.",
    222                     ways.size(), ways.size(), lengthstr), false);
     266        List<Way> ways = ds.getSelectedWays().stream()
     267                .filter(p -> !p.isIncomplete())
     268                .collect(Collectors.toList());
     269        if (ways.isEmpty()) {
     270            alertSelectAtLeastOneWay();
     271            return;
     272        } else if (!confirmWayWithNodesOutsideBoundingBox(ways) || (ways.size() > MAX_WAYS_NO_ASK && !confirmSimplifyManyWays(ways.size()))) {
     273            return;
     274        }
    223275
    224             if (err > 0) {
    225                 simplifyWays(ways, err);
     276        String lengthstr = SystemOfMeasurement.getSystemOfMeasurement().getDistText(
     277                ways.stream().mapToDouble(Way::getLength).sum());
     278
     279        askSimplifyWays(ways, trn(
     280            "You are about to simplify {0} way with a total length of {1}.",
     281            "You are about to simplify {0} ways with a total length of {1}.",
     282            ways.size(), ways.size(), lengthstr), false, (maxErr, simplifyCommand) -> {
     283                if (maxErr > 0) {
     284                    if (simplifyCommand == null) {
     285                        simplifyWays(ways, maxErr);
     286                    } else {
     287                        GuiHelper.runInEDT(() -> UndoRedoHandler.getInstance().add(simplifyCommand));
     288                    }
     289                }
    226290            }
    227         });
     291        );
     292
    228293    }
    229294
    230295    /**
    public class SimplifyWayAction extends JosmAction {  
    308373     * @since 16566 (private)
    309374     */
    310375    private static SequenceCommand buildSimplifyWaysCommand(List<Way> ways, double threshold) {
    311         Collection<Command> allCommands = ways.stream()
     376        // Use `parallelStream` since this can significantly decrease the amount of time taken for large numbers of ways
     377        Collection<Command> allCommands = ways.parallelStream()
    312378                .map(way -> createSimplifyCommand(way, threshold))
    313379                .filter(Objects::nonNull)
    314380                .collect(StreamUtils.toUnmodifiableList());
    public class SimplifyWayAction extends JosmAction {  
    499565        private final JLabel nodesToRemove;
    500566        private final SpinnerNumberModel errorModel;
    501567        private final List<Way> ways;
     568        private ForkJoinTask<?> futureNodesToRemove;
    502569
    503570        SimplifyChangeListener(JLabel nodesToRemove, SpinnerNumberModel errorModel, List<Way> ways) {
    504571            this.nodesToRemove = nodesToRemove;
    public class SimplifyWayAction extends JosmAction {  
    508575
    509576        @Override
    510577        public void stateChanged(ChangeEvent e) {
    511             if (Objects.equals(UndoRedoHandler.getInstance().getLastCommand(), lastCommand)) {
    512                 UndoRedoHandler.getInstance().undo();
     578            MainApplication.worker.execute(() -> {
     579                synchronized (errorModel) {
     580                    double threshold = errorModel.getNumber().doubleValue();
     581                    if (futureNodesToRemove != null) {
     582                        futureNodesToRemove.cancel(false);
     583                    }
     584                    futureNodesToRemove = MainApplication.getForkJoinPool().submit(new ForkJoinTask<Command>() {
     585                        private Command result;
     586
     587                        @Override
     588                        public Command getRawResult() {
     589                            return result;
     590                        }
     591
     592                        @Override
     593                        protected void setRawResult(Command value) {
     594                            this.result = value;
     595                        }
     596
     597                        @Override
     598                        protected boolean exec() {
     599                            synchronized (SimplifyChangeListener.this) {
     600                                if (!this.isCancelled()) {
     601                                    GuiHelper.runInEDT(() -> nodesToRemove.setText(tr("calculating...")));
     602                                    result = updateNodesToRemove(ways, threshold);
     603                                    return true;
     604                                }
     605                            }
     606                            return false;
     607                        }
     608                    });
     609                }
     610            });
     611        }
     612
     613        private Command updateNodesToRemove(List<Way> ways, double threshold) {
     614            // This must come first in order for everything else to be accurate
     615            if (lastCommand != null && lastCommand.equals(UndoRedoHandler.getInstance().getLastCommand())) {
     616                // We need to wait to avoid cases where we cannot undo due threading issues.
     617                GuiHelper.runInEDTAndWait(() -> UndoRedoHandler.getInstance().undo());
    513618            }
    514             double threshold = errorModel.getNumber().doubleValue();
    515             int removeNodes = simplifyWaysCountNodesRemoved(ways, threshold);
    516             nodesToRemove.setText(trn(
    517                     "(about {0} node to remove)",
    518                     "(about {0} nodes to remove)", removeNodes, removeNodes));
    519             lastCommand = SimplifyWayAction.buildSimplifyWaysCommand(ways, threshold);
    520             if (lastCommand != null) {
    521                 UndoRedoHandler.getInstance().add(lastCommand);
     619            final Command command = buildSimplifyWaysCommand(ways, threshold);
     620            // This is the same code from simplifyWaysCountNodesRemoved, but is duplicated for performance reasons
     621            // (this avoids running the code to calculate the command twice).
     622            int removeNodes = command == null ? 0 : (int) command.getParticipatingPrimitives().stream()
     623                    .filter(Node.class::isInstance)
     624                    .count();
     625            int totalNodes = ways.parallelStream().mapToInt(Way::getNodesCount).sum();
     626            int percent = (int) Math.round(100 * removeNodes / (double) totalNodes);
     627            GuiHelper.runInEDTAndWait(() ->
     628                    nodesToRemove.setText(trn(
     629                            "(about {0} node to remove ({1}%))",
     630                            "(about {0} nodes to remove ({1}%))", removeNodes, removeNodes, percent))
     631            );
     632            lastCommand = command;
     633            if (lastCommand != null && ways.size() < MAX_WAYS_NO_ASK) {
     634                GuiHelper.runInEDTAndWait(() -> UndoRedoHandler.getInstance().add(lastCommand));
    522635            }
     636            return lastCommand;
    523637        }
    524638    }
     639
     640
     641    /**
     642     * A listener that is called when the {@link #askSimplifyWays} method is called.
     643     * @since xxx
     644     */
     645    @FunctionalInterface
     646    public interface SimplifyWayActionListener {
     647        /**
     648         * Called when the max error is set
     649         * @param maxError The max error
     650         * @param simplify The command that was used to generate stats for the user with the maxError. May be null.
     651         */
     652        void maxErrorListener(double maxError, Command simplify);
     653    }
    525654}
  • src/org/openstreetmap/josm/gui/MainApplication.java

    diff --git a/src/org/openstreetmap/josm/gui/MainApplication.java b/src/org/openstreetmap/josm/gui/MainApplication.java
    index 744d8878c..a3ab8ff65 100644
    a b import java.util.Set;  
    4444import java.util.TreeSet;
    4545import java.util.concurrent.ExecutorService;
    4646import java.util.concurrent.Executors;
     47import java.util.concurrent.ForkJoinPool;
    4748import java.util.concurrent.Future;
    4849import java.util.logging.Level;
    4950import java.util.stream.Collectors;
    public class MainApplication {  
    212213     */
    213214    public static final ExecutorService worker = new ProgressMonitorExecutor("main-worker-%d", Thread.NORM_PRIORITY);
    214215
     216    /**
     217     * This class literally exists to ensure that consumers cannot shut the pool down.
     218     * @since xxx
     219     */
     220    private static class ForkJoinPoolAlways extends ForkJoinPool {
     221        ForkJoinPoolAlways() {
     222            // Leave a processor for the UI.
     223            super(Math.max(1, Runtime.getRuntime().availableProcessors() - 1), ForkJoinPool.defaultForkJoinWorkerThreadFactory,
     224                    new BugReportExceptionHandler(), false);
     225        }
     226
     227        @Override
     228        public void shutdown() {
     229            // Do nothing. This is functionally the same as for the commonPool.
     230        }
     231
     232        @Override
     233        public List<Runnable> shutdownNow() {
     234            // Do nothing. This is functionally the same as for the commonPool.
     235            return Collections.emptyList();
     236        }
     237    }
     238
     239    // If there is a security manager, ForkJoinPool.commonPool uses an "InnocuousForkJoinWorkerThreadFactory", which can kill stuff calling
     240    // repaint. Otherwise, the common pool is better from (most) perspectives.
     241    private static final ForkJoinPool forkJoinPool = System.getSecurityManager() != null ? new ForkJoinPoolAlways() : ForkJoinPool.commonPool();
     242
     243    /**
     244     * Get a generic {@link ForkJoinPool}.
     245     * @return A ForkJoinPool to use. Calling {@link ForkJoinPool#shutdown()} or {@link ForkJoinPool#shutdownNow()} has no effect.
     246     * @since xxx
     247     */
     248    public static final ForkJoinPool getForkJoinPool() {
     249        return forkJoinPool;
     250    }
     251
    215252    /**
    216253     * Provides access to the layers displayed in the main view.
    217254     */
    public class MainApplication {  
    283320     * Listener that sets the enabled state of undo/redo menu entries.
    284321     */
    285322    final CommandQueueListener redoUndoListener = (queueSize, redoSize) -> {
    286             menu.undo.setEnabled(queueSize > 0);
    287             menu.redo.setEnabled(redoSize > 0);
     323            GuiHelper.runInEDT(() -> menu.undo.setEnabled(queueSize > 0));
     324            GuiHelper.runInEDT(() -> menu.redo.setEnabled(redoSize > 0));
    288325        };
    289326
    290327    /**