7

I am making a program that shows cellular growth through an array. I have gotten it so when I press the start button, the array updates every 10 seconds in a while(true){} loop. The problem with that is I want to be able to stop the loop by pressing the pause button, but while in the loop, it wont let me use any of the controls. I need something other than a infinite loop in orer to refresh the frames.

I am a bit of a newbie, but I am in a java class at the moment. so I have some grasp of the language.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Luke
  • 115
  • 1
  • 1
  • 6
  • 1
    What do you mean by start and pause buttons? Is your program running within some GUI? Is this some environment set up for your class? – Uri Jan 19 '10 at 19:53

9 Answers9

10

I would suggest using a seperate thread to handle the array. Make sure you are using thread safe object (check Java Docs) and simply call .start() on your thread object when you want to start. Keep a pointer to it so you can pause it via setPaused(true)

Something like this....

class MyArrayUpdater extends Thread {
    private volatile boolean enabled = true;
    private volatile boolean paused = true;

    @Override
    public void run() {
        super.run();
        try {
            while (enabled) {
                if (!paused) {
                    // Do stuff to your array here.....
                }
                Thread.sleep(1);
            }
        } catch (InterruptedException ex) {
            // handle ex
        }
    }

    public void setEnabled(boolean arg) {
        enabled = arg;
    }

    public void setPaused(boolean arg) {
        paused = arg;
    }
}
Chris Kannon
  • 5,931
  • 4
  • 25
  • 35
  • 1
    Since the setPaused and setEnabled should be called from other threads, you should make the enabled and paused booleans volatile, or synchronise when reading/setting them. – Paul Wagland Jan 19 '10 at 20:40
  • 1
    That would consume a lot of resources if the app is paused for a long period of time ( 1 min for instance ) because the while loop will be running at full speed, only to set the thread to sleep for a millisecond and start it again. That would create 60,000 ( aprox ) unneeded invocations, only for a 1 minute stop. – OscarRyz Jan 19 '10 at 21:12
  • Actually, this is a very poor solution. When paused == true, where there is no work to be done, this code incurs two context switches (read: very expensive) every 1 ms (assuming that the OS could provide such accuracy and could keep up). The net effect would be a slowdown to other threads doing actual work. A better solution is to *wait* till the condition "paused == false" is true. Either with an old fashioned lock and Object#wait, #notify, or via a java.util.concurrent.locks.Lock/Condition and appropriate methods. Weird to see this solution got the most votes! – Dimitris Andreou Jan 19 '10 at 21:22
  • Paul Clapham's answer is much better. I'd be surprised if SWT didn't have a similar timer event if the app is done in that instead of swing. – Geoff Reedy Jan 19 '10 at 21:59
  • The wait period or lock can be handled many different ways. The sleep(1) could be made configurable, or as Dimitris commented could be locked until #notify(I like that method). In general the method above is an easy and generic way of handling a threaded process. I also would not argue the timer based solution below is a valid solution, as long as the asker is using Swing and 1 second updates are fast enough for him. – Chris Kannon Jan 20 '10 at 14:03
10

What you need is use a Timer which changes the state of your component ( in this case the cellular growth ) and then call JComponent.repaint()

This timer can be cancelled to make the pause and then to restart it, you just create a new one:

So you could define the following two methods:

private Timer timer;
...
public void startPaiting() {
    timer = new Timer();
    timer.schedule( new TimerTask(){
        public void run(){
            changeState();
            repaint();
        }
    },0,  10000 ); // 10 s. 
}

public void pause(){
    timer.cancel();
}

And then in your "Pause/Resume" button invoke this "pause/startPaiting" methods:

if( e.getActionCommand().equals("Pause")){
    growPanel.pause();
    setText("Resume");
} else {
    growPanel.startPaiting();
    setText("Pause");
}

Here's the complete source code to see it running:

import javax.swing.*;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.*;
import java.util.Timer;
import java.util.TimerTask;

public class Grow {

    public static void main( String [] args ) {
        JFrame frame = new JFrame();
        final GrowPanel growPanel = new GrowPanel();
        frame.add( growPanel );
        frame.add( new JPanel(){{
            add( new JButton("Pause"){{
                addActionListener( new ActionListener(){
                    public void actionPerformed( ActionEvent e ){
                        if( e.getActionCommand().equals("Pause")){
                            growPanel.pause();
                            setText("Resume");
                        } else {
                            growPanel.startPaiting();
                            setText("Pause");
                        }
                    }
                });
        }});}}, java.awt.BorderLayout.SOUTH );
        frame.setSize( 400, 300 );
        frame.setVisible( true );
    }
}

class GrowPanel extends JComponent {
    private int x;
    private int y;
    private Timer timer;
    GrowPanel() {
        x = 10;
        y = 10;
        startPaiting();
    }

    public void startPaiting() {
        timer = new Timer();
        timer.schedule( new TimerTask(){
            public void run(){
                changeState();
                repaint();
            }
        },0,  100 ); // or 10000 which is 10 s. 
    }

    public void pause(){
        timer.cancel();
    }

    public void paintComponent( Graphics g ){
        g.fillOval( x, y, 10, 10 );
    }
    private void changeState(){
            x+=10;
            if( x >= 400 ) {
                y+=10;
                x = 0;
            }
            if( y >= 300 ){
                y = 10;
            }
    }

}
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
  • Thank you, this is exactly what I need. I am having a little trouble getting it to work in my code because its set up a bit differently than yours. But 'll work on it Thanks – Luke Jan 20 '10 at 15:10
  • @Luke, I'm glad. You can mark my answer as accepted ( I'm telling you this because you're new to StackOverflow :P ) – OscarRyz Jan 20 '10 at 15:42
  • HAha, alright. Is there a way I can send you my code? I am having the worst time figuring out how to get what you gave me into my code? I understand thats a lot to ask of a stranger, so no worries if thats too much. – Luke Jan 20 '10 at 19:39
  • Sure, but, I think it will be much better if you create a new question here. I can take a look at that or any other. Phrase it like: *I asked this and I'm having this problem to make it work ( describe specific problem you're having ). I would like to work as Oscar's solution, but my code is not working. My code is here )* If you you have several questions, post each one on each. i think you'll have better chance to get it work. – OscarRyz Jan 20 '10 at 19:48
2

If those "buttons" are Swing buttons, then the way to do this is: have the Start button create a new javax.swing.Timer object which does the update every 10 seconds. Then have the Pause button stop that timer.

Paul Clapham
  • 1,074
  • 5
  • 6
1

You want to run your simulation in a Thread ( Look for Runnable Interface ). Then you can pass messages to this Thread to pause, continue and stop.

1

I personally prefer using the Timer class rather than Thread or Thread.sleep(). The timer class handles both running the code periodically and canceling it.

Your code would look like:

TimerTask myTask = new TimerTask() {
  public void run() {
    // here goes my code
  }
};

Timer timer = new Timer();
timer.schedule(myTask, 0 /*starts now*/, 10 * 1000 /* every 10 seconds */);

// whenever you need to cancel it:
timer.cancel();
notnoop
  • 58,763
  • 21
  • 123
  • 144
0

Yep. Sounds like you're doing your drawing on the event dispatcher thread. Events (button presses) never have a chance to get called because you've never returned control from the last time it was called...

Brian Knoblauch
  • 20,639
  • 15
  • 57
  • 92
0

What you're looking for is multi-threading. Take the data processing and put it in a second thread, run it asynchronously, and use the main thread to check for user input.

Of course this might also entail using a semaphore to communicate between the two threads. What level class are you in - have you covered both of these topics already?

Chris
  • 3,400
  • 1
  • 27
  • 41
  • ahhhh, I am in a highschool java class as an elective, so my training is... minimal. I no what your saying, but I have absolutely no idea how to implement it into code. Thanks though. – Luke Jan 20 '10 at 15:13
0

Take a look at the wait() / notify() machinery. Briefly, a thread can wait for 10 seconds, but still be notified that an external event has occurred,

David Soroko
  • 8,521
  • 2
  • 39
  • 51
0

Another way to handle this problem is by using BlockingQueue of jobs.

Amir Fo
  • 5,163
  • 1
  • 43
  • 51