0

I'm programming a path finder visualizer with Kotlin and Java Swing and I have this Breadth-First Search function:

fun bfs(): MutableList<Node>? {
    Grid.resetNodes()

    val queue: Queue<Node> = LinkedList()

    queue.add(Grid.start!!)
    Grid.start!!.state = State.IN_QUEUE

    while (queue.isNotEmpty()) {
        val current = queue.poll()
        //println(current)
        current.state = State.CLOSE

        if (current == Grid.end!!) break

        Grid.getNodeNeighbours(current).forEach { node ->
            if (node.state == State.OPEN) {
                node.parent = current
                queue.add(node)
                node.state = State.IN_QUEUE
            }
        }
        GridPanel.repaint()
    }

    return getPath()
}

After each iteration of the while loop I want to redraw the Grid and wait a few seconds to make the algorithm view a little slower. I tried using Swing Timers, but I couldn't get it to work. I can't use 'Thread.sleep()' too because of Java Swing.

  • Use coroutines and just insert a delay? – Louis Wasserman Aug 23 '21 at 22:23
  • 1
    You can't just insert an algorithm into a Java Swing application. You have to rewrite the algorithm so one step of the process at a time happens in a javax.swing.Timer ActionListener. – Gilbert Le Blanc Aug 23 '21 at 23:16
  • Swing is a single Thread library. All painting tasks are executed by the ([EDT](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html)). Running long processes (such BFS) on the EDT keeps it busy, so it does not do update the gui (gui freezes). Run the long process on a different thread, preferably by using a `SwingWorker`, and there is also when you apply a "wait". See an example [here](https://stackoverflow.com/questions/50780369/grid-dfs-visualization/50781879#50781879) – c0der Aug 24 '21 at 05:14
  • Another [example](https://stackoverflow.com/a/47494466/3992939) – c0der Aug 24 '21 at 05:21

1 Answers1

1

Swing is a single Thread library. All painting tasks are executed by the EDT.
Running long processes (such BFS) on the EDT keeps it busy, so it does not do update the gui (gui becomes unresponsive, freezes).
Run the long process on a different thread, but remember that all Swing gui updates needs to be done by the EDT.
The different thread is where you can apply a delay for slowing down the process for better visualization.
A good tool to perform such tasks is a SwingWorker.
The following is a single-file mre (copy-paste the entire code to AlgoVisualitation.java and run) demonstrating the basic structure:

import java.awt.*;
import java.util.List;
import java.util.Random;
import javax.swing.*;

public class SwingAlgoVisualization {

    private Cell[][] cells;
    private static final int SIZE = 3, GAP = 2;

    JPanel view() {

        JPanel gridPanel = new JPanel();
        gridPanel.setLayout(new GridLayout(SIZE, SIZE, GAP, GAP));
        gridPanel.setBorder(BorderFactory.createEmptyBorder(GAP, GAP,GAP, GAP));
        cells = new Cell[SIZE][SIZE];

        for(int row=0; row <cells.length; row++) {
            for(int col=0; col<cells[row].length; col++) {
                Cell cell = new Cell();
                cells[row][col] = cell;
                gridPanel.add(cell.getComponent());
            }
        }
        return gridPanel;
    }

     void solve()   {
         //run long process on a SwingWorker
         new LongTask().execute();
     }

    //use swing worker to perform long task off the EDT
    class LongTask extends SwingWorker<Void,int[]> {

        private static final long DELAY = 30;
        private final Random rand = new Random();

        @Override
        public Void doInBackground() {
            myLongComplexAlgo();
            return null;
        }

        @Override
        //runs on EDT
        protected void process(List<int[]> results) {
            for(int[] result : results){
                cells[result[0]][result[1]].updateValue(result[2]);
            }
        }

        void myLongComplexAlgo() { //simulates long process that repeatedly updates gui

            while (true){ //todo add stop mechanism
                //calculate(apply random value to random cell) and publish results
                int row = rand.nextInt(cells.length);
                int col = rand.nextInt(cells[0].length);
                int value = RandomValue.randomDigit();
                publish(new int[]{row,col,value});

                try {
                    Thread.sleep(DELAY); //visualization delay
                } catch (InterruptedException ex) { ex.printStackTrace();}
            }
        }
    }

    public static void main(String[] args) {

        SwingAlgoVisualization av = new SwingAlgoVisualization();
        JFrame jFrame = new JFrame("Random Value Change");
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.setLocationRelativeTo(null);
        jFrame.add(av.view());
        jFrame.pack();
        jFrame.setVisible(true);
        av.solve();
    }
}

class Cell {

    private static int CELL_SIZE =100, BORDER = 1, FONT_SIZE = 20;

    private final JLabel label;
    Cell() {
        label = new JLabel();
        label.setFont(new Font("Calibri", Font.BOLD, FONT_SIZE));
        updateValue(RandomValue.randomDigit());
        label.setHorizontalAlignment(SwingConstants.CENTER);
        label.setBorder(BorderFactory.createLineBorder(Color.BLUE, BORDER));
        label.setPreferredSize(new Dimension(CELL_SIZE , CELL_SIZE));
        label.setOpaque(true);
    }

    void updateValue(int value){
        label.setText(String.valueOf(value));
    }

    JComponent getComponent(){
        return label;
    }
}

class RandomValue{

    private static Random rand = new Random();

    public static int randomDigit(){
        return rand.nextInt(10);
    }
}

(Run it online)


enter image description here

c0der
  • 18,467
  • 6
  • 33
  • 65