Search code examples
javaalgorithmswingconcurrencygraphstream

Thread.sleep() freezes JFrame/GUI containing GraphStream graph


My JFrame containing an embedded single graph (Graphstream) freezes when I try to update it in a loop that calls Thread,sleep(). I have tried using the same update on a standalone-graph (displayed on it's own) and it works as expected.

I have a single graph embedded in JFrame as follows (AppGraph.java):

public static ViewPanel init(){

    graph.addAttribute("ui.stylesheet", styleSheet);
    graph.setAutoCreate(true);
    graph.setStrict(false);
    graph.addAttribute("ui.quality");
    graph.addAttribute("ui.antialias");

    initGraph();

    initNodes(graph);

    return attachViewPanel();

}

private static ViewPanel attachViewPanel() {
    Viewer viewer = new Viewer(graph, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD);
    viewer.enableAutoLayout();
    return viewer.addDefaultView(false);
}

private static void initGraph(){
    FileSource fs = new FileSourceDOT();
    String graph_filename = "graph.gv";
    String absolute_path = System.getProperty("user.home") + File.separator + graph_filename;
    fs.addSink(graph);
    try {
        fs.readAll(absolute_path);
    } catch (IOException | NullPointerException e) {
        e.printStackTrace();
    } finally {
        fs.removeSink(graph);
    }
}

Then this is called in the JFrame class as below:

   /*AppWindow.java
    * Set up graph
    */
    GridBagConstraints graphConstraints = new GridBagConstraints();
    graphConstraints.fill = GridBagConstraints.BOTH;
    graphConstraints.gridx = 0;
    graphConstraints.gridy = 1;
    graphConstraints.weightx = 0.5;
    graphConstraints.weighty = 0.5;
    graphConstraints.gridwidth = 4;
    graphConstraints.gridheight = GridBagConstraints.RELATIVE;
    add(AppGraph.init(), graphConstraints);`

On the JFrame are buttons for different search algorithms like BFS. During the execution of these algorithms, edges traversed are colored at fixed time intervals to create a sort of animation effect as shown below:

   //BFSAlgorithm.java 
   private void callBFS(Node startNode, Node goalNode) {
            startNode.setAttribute("parent", "null");
            startNode.setAttribute("level", 0);
            startNode.setAttribute("visited?");
            LinkedList<Node> queueFrontier = new LinkedList<>();
            int level = 1;
            queueFrontier.addLast(startNode);
            while (!queueFrontier.isEmpty()) {
                System.out.println("Level: " + (level - 1));
                LinkedList<Node> next = new LinkedList<>();
                for (Node node : queueFrontier) {
                    if (node == goalNode) {
                        System.out.println(node.getId() + ": Found Found Found!!!");
                        if (node != startNode) {
                            colorEdge(node);
                        }
                        return;
                    }
                    System.out.print(node.getId() + " visited \t");
                    if (node != startNode) {
                        colorEdge(node);
                    }
                    for (Edge edge : node.getEdgeSet()) {
                        Node opposite = edge.getOpposite(node);
                        if (!opposite.hasAttribute("visited?")) {
                            System.out.print(opposite.getId() + " enqueued \t");
                            opposite.setAttribute("level", level);
                            opposite.setAttribute("parent", node);
                            opposite.setAttribute("visited?");
                            next.addLast(opposite);
                        }
                    }
                    System.out.print("\n");
                }
                level++;
                queueFrontier = next;
                sleep();
        }
    }

    private void colorEdge(Node node) {
        Edge visitedEdge = node.getEdgeBetween(node.getAttribute("parent", Node.class));
        visitedEdge.setAttribute("ui.color", 0.5);
        sleep();
    }

    private void sleep() {
        try {
            Thread.sleep(AppWindow.speed);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

This BFSAlgorithm implements DynamicAlgorithm and extends SinkAdapter. I have extended the SinkAdapter to enable it to interact with the View as the algorithm runs. When I call the BFSAlgorithm, while the algorithm runs and the various println statements are delayed by sleep(), the GUI freezes and is unresponsive until after execution before all the visited edges are then colored. I tried implementing ViewerListener in my AppGraph.java as is documented on the graphstream documentation but it only resulted in an infinite loop that crashed the application:

/*...init() method from AppGraph.java*/
ProxyPipe fromViewer = viewer.newThreadProxyOnGraphicGraph();
        fromViewer.addSink(graph);
        fromViewer.pump();

while(loop) {
            fromViewer.pump(); //

}

Solution

  • Like @Frakool and @MadProgrammer suggested in the comments, if anyone is having similar issues, using SwingWorker and Swing Timer will provide the desired results. According to the documentation:

    In general, we recommend using Swing timers rather than general-purpose timers for GUI-related tasks because Swing timers all share the same, pre-existing timer thread and the GUI-related task automatically executes on the event-dispatch thread. However, you might use a general-purpose timer if you don't plan on touching the GUI from the timer, or need to perform lengthy processing.

    Here's how I used it to stop the gui freezing. I created a private inner SwingWorker class that uses a Swing Timer as below:

    private class BFSTask extends SwingWorker<LinkedList<Node>, Node>{
        private ArrayList<Node> visitedList;
        private int visitedIndex = 0;
        private boolean traversalDone = false;
        private Timer traversal = new Timer(AppWindow.speed, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                Node lastVisited = visitedList.get(visitedIndex);
                Edge visitedEdge = lastVisited.getEdgeBetween(lastVisited.getAttribute("parent", Node.class));
                visitedEdge.setAttribute("ui.color", 0.5);
                visitedIndex++;
                if(visitedIndex >= visitedList.size()){
                    traversal.stop();
                    traversalDone = true;
                    if(BFSAlgorithm.this.getPathToGoal() != null){
                        startTimer();
                    }
                }
            }
        });
    
         @Override
        protected LinkedList<Node> doInBackground() throws Exception {
            Node found = publishNodeBreadthFirst(getStartNode(), getGoalNode());
            if (found != null) {
                return getPathToGoal(found);
            } else{
                return null;
            }
        }
    
        @Override
        protected void process(List<Node> list) {
            visitedList = (ArrayList<Node>) list;
            traversal.start();
        }
    
        @Override
        protected void done() {
            try {
                BFSAlgorithm.this.pathToGoal = get();
                if(traversalDone && BFSAlgorithm.this.getPathToGoal() != null){
                    startTimer();
                }
                if(BFSAlgorithm.this.getPathToGoal() == null){
                    throw new NullPointerException("Goal Not Found.");
                }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            } catch (NullPointerException e){
                JOptionPane.showMessageDialog(getAppWindow(), "Goal Node Not Found!", "Error", JOptionPane.ERROR_MESSAGE);
                getAppWindow().disableExceptClear();
                getAppWindow().changeStatus("Goal node not found");
    
            }
        }
    
        private LinkedList<Node> getPathToGoal(Node found) {
            LinkedList<Node> path = new LinkedList<>();
            Node parent = found.getAttribute("parent");
            path.addLast(found);
            while (parent != getStartNode()){
                path.addLast(parent);
                parent = parent.getAttribute("parent");
            }
            return path;
        }
    }