CSE190L Notes for Friday, 4/27/07

We continued our discussion of the clicker example. The version we had as of the end of Wednesday's lecture doesn't behave very well. The user clicks on a position and that immediately leads to two circles being drawn on the screen. I spent some time discussing the sequence of events that is taking place behind the scenes:

All of this happens so quickly that it seems like it happens simultaneously. Obviously if we're trying to simulate an interaction where the user and computer trade turns selecting positions, we don't want it to behave like this. We spent the rest of the lecture exploring various ways to get that behavior.

Someone suggested using a timer, but I said I didn't want to explore that option just yet. Someone else suggested asking for the current time and using it to compute a target time 2 seconds later:

        long targetTime = System.currentTimeMillis() + 2000;
Then we could write a loop that would wait until that time was reached:

        while (System.currentTimeMillis() < targetTime);
The loop has an empty body because it's basically saying, "While we haven't reached the target time, do nothing (just keep checking the time)." This is an approach known as polling and it's a very bad idea. It's the computational equivalent of the Simpsons episode where Bart and Lisa keep saying to Homer, "Are we there yet? Are we there yet? Are we there yet?" You don't want to waste your processor's time in that way.

A better approach would be to tell the program to pause for a short period of time. This can be done with a method called "sleep". You tell the current thread to sleep for a short period of time. The sleep method throws a checked exception, so you either need to include it in a try/catch block or you need to declare that you throw the exception. This is one of the most annoying exceptions in the Java framework because even though you are required to handle it, the exception should never happen. I had the following method available to us that has a try/catch for the method:

        // Utility method to pause for a given number of milliseconds.
        private void pause(int milliseconds) {
            try {
                Thread.sleep(milliseconds);
            } catch (Exception e) {
                throw new InternalError();
            }
        }
Our Wednesday code included the following method:

        private void handleClick(MouseEvent e) {
            model.add(e.getPoint(), ClickerModel.Player.HUMAN);
            computerMove();
        }
So we changed this to:

        private void handleClick(MouseEvent e) {
            model.add(e.getPoint(), ClickerModel.Player.HUMAN);
            pause(2000);
            computerMove();
        }
This version behaved rather oddly. We expected it to draw the user's point, then pause for 2 seconds, then draw the computer's point. Instead, it paused for 2 seconds and then drew both points at the same time. Someone wondered whether repaint had been called to draw the user's point. I reminded people that we had traced the code just a few minutes earlier to see how that happened, but I added a call on repaint just to be sure:

        private void handleClick(MouseEvent e) {
            model.add(e.getPoint(), ClickerModel.Player.HUMAN);
            panel.repaint();
            pause(2000);
            computerMove();
        }
This version behaved the same way. There are two issues here. The first is that Swing wants to be efficient about when it actually repaints things. When you call repaint, you are telling Swing that the component involved needs to be repainted. But Swing doesn't do the actual repainting immediately because it might get several repaint requests in a row and it doesn't want to repaint many times if it doesn't have to. There is a way around this and I showed it in lecture, but it's not considered good style in Swing, so I'm not going to include that line of code in the notes. In general, you don't want to fight Swing. You want to work with the framework the way it's intended.

But that still leaves open another question. Why doesn't Swing do the repainting when we call sleep? The explanation has to do with the fact that a Swing program involves several threads of execution. We haven't really talked about this before. In a simple procedural program, the only code that executes would be code that we wrote. But in a Swing program, our code is executing only some of the time.

In the programs we wrote for cse142 and cse143, method main determined everything that happened. Once main was done executing, the program was done executing. But that's not true with a Swing program. To underscore this point, I added a println to our main method:

        public class Clicker {
            public static void main(String[] args) {
                ClickerModel m = new ClickerModel();
                ClickerController c = new ClickerController(m, true);
                c.run();
                System.out.println("that's all folks");
            }
        }
We saw that when we ran the program, we got the message "That's all folks" right after the frame appeared on the screen. So our method main finished executing, but the program is still running. That's because when you launch a Swing program it starts up several different threads of executing that are all running. So even though none of our code is running at that moment, the other Swing threads are running. In particular, there is a thread that waits for user events. If we have registered a listener with such an event, then the event thread will make a call on our code and we'll be back executing our code again. This happens, for example, when the user clicks on the panel because we registered a mouse listener with the panel.

The Swing approach is to give priority to the code that we've written. So Swing doesn't wake back up to execute again until our code has finished executing. As a result, calling sleep on our thread is not a good idea. Even spawning a new thread to do the sleeping doesn't work:

        private void handleClick(MouseEvent e) {
            model.add(e.getPoint(), ClickerModel.Player.HUMAN);
            new Thread() {
                public void run() {
                    pause(2000);
                    computerMove();
                }
            }.run();
        }
This doesn't work because by default any new threads created by our thread have the same priority as our thread, which means they'll be executed before Swing takes time to do its own work (like repainting). But there is a good solution. There is a method in the SwingUtilities class called invokeLater:

        public static void invokeLater(Runnable doRun)
As its documentation says:

Causes doRun.run() to be executed asynchronously on the AWT event dispatching thread. This will happen after all pending AWT events have been processed. This method should be used when an application thread needs to update the GUI.
So it is designed specifically for our situation. We want to let the pending repaint commands be executed before our code gets executed. The method requires a parameter of type Runnable, which is an interface that has a single method called run. We can use an anonymous inner class to construct the Runnable object to pass to the method:

        private void handleClick(MouseEvent e) {
            model.add(e.getPoint(), ClickerModel.Player.HUMAN);
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    pause(2000);
                    computerMove();
                }
            });
        }
This version had the behavior we were looking for. It would draw the user's point, then pause 2 seconds, then draw the computer's point. In our code above we're calling a pause method, but what you're more likely to run across in your own code is a method that takes a long time to execute. For example, if your program had a "pick move" method that took a long time to execute, then you'd want to use this approach to make sure the GUI is updated before your method starts thinking about what move to make.

I was running short of time, but I wanted to mention two other variations. I described them quickly in lecture, but I'll talk about them more slowly here. First, one problem we saw was that while the computer is pausing, the user might click several times. What happens in that case is that the clicks are all remembered, so that they are executed later. This seems like bad behavior for the application. The user might be confused by the fact that the application has paused and might be clicking for that reason. That doesn't mean that the user is somehow specifying several moves in a row.

To fix this, we added a new boolean flag to the controller to keep track of whether or not clicks were allowed:

        private boolean clickOkay;
We set this to true when it is the user's turn and we set it to false when it's not the user's turn. If the user clicks when it's not okay to click, we simply ignore it (we could also "beep" if we wanted to give feedback to the user). So our handleClick code became:
        private void handleClick(MouseEvent e) {
            if (!clickOkay)
                return;
            model.add(e.getPoint(), ClickerModel.Player.HUMAN);
            clickOkay = false;
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    pause(2000);
                    computerMove();
                    clickOkay = true;
                }
            });
        }
The method now begins with a check that ignores clicks when they are not allowed. After the user makes a move, we don't allow any more clicks until the computer has made its move.

We also have to incorporate this into our run method, to allow the user to make a move initially:

        public void run() {
            frame.setVisible(true);
            if (!userFirst)
                computerMove();
            clickOkay = true;
        }
I also revisited the idea of doing this with a timer. With a timer, we don't need the pause method. We can instruct the timer to execute the code at some later time. I added this field to the controller class:

        private Timer timer;
When I did so, I got a compiler error because there is more than one kind of Timer in the Java class libraries. The trick to fix this is to include an extra import at the end of my list of imports that mentions exactly which version of Timer I want:

        import javax.swing.Timer;
I then included code in the constructor to set up the timer object:

        timer = new Timer(2000, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                computerMove();
                clickOkay = true;
            }
        });
In this case, we don't want the timer to fire multiple times. We just want to do this once. The timer has an option for this, so I also included this line of code:

        timer.setRepeats(false);
With the timer, the handleClick code involves setting the flag and then starting the timer:

        private void handleClick(MouseEvent e) {
            if (!clickOkay)
                return;
            model.add(e.getPoint(), ClickerModel.Player.HUMAN);
            clickOkay = false;
            timer.start();
        }
The complete version of the code is available as handout #14 from the class web page.


Stuart Reges
Last modified: Wed May 9 10:10:12 PDT 2007