0

I am writing the Sugarscape simulation in Java and need a working GUI. Sugarscape is a spatial landscape consisting of tiles (of sugar), and agents moving and consuming sugar. For simplicity, I have only one agent and no sugar- I just want to see the agent moving.

For the past 2 weeks I have read into painting in java, concurrency in java, concurrency in swing, I have read filthy rich clients and countless StackOverflow threads, but I must resort to asking a question here.

I need my model separate from the GUI. This presents a problem since 99% of tutorials suggest to call for repaint within other methods. My idea was to run one "tick" of the simulation: all agents move, and then send an Event (my GUI class extends Observer) which then triggers a repaint(); request and update the GUI. However the problem (the misunderstanding) lies with the SwingUtilities.InvokeLater method. My code is:

public void setupGUI()
{
    SwingUtilities.invokeLater(new Runnable() 
    {
        public void run() {
            System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
            SugarFrame frame = new SugarFrame(simulation.getWorld());
            frame.setVisible(true);
        }

    });

}

For understanding what is happening I have inserted println's everywhere. The order of events is what confuses me:


Console output:

1.Agent created. Starting Position: X= 19 Y= 46 // This is in the Agent constructor

2.Simulation start. Experiment number: 0

  1. GUI is being setup, on EDT now? true // As you see above, this is WITHIN the SwingUtilities.InvokeLater section. But then the EDT pauses and the real model continues:

  2. Tick number 0

  3. Invoke Agent Actions, fire TickStart Event

  4. TickStartEvent created

  5. Invoke Agent Actions, for-loop starting now

  6. Agent number 0 moving now:

  7. Consuming Sugar now.

  8. Moving now.

  9. Sleeping now.

  10. The Sugarframe has been created and Grid added. All on EDT? true // And there it is back again. The paint component follows and the window with the Agent visible appears.

  11. paintComponent called, on EDT? true


Now, I have read that by putting the main thread to sleep, you give the EDT time to run the repaint. However, this only happens once. Repaint is never called again, and I only ever see one iteration of the model.

I simply do not understand what piece of information I am missing to work with the EDT properly. Swingworker and Swingtimer are suggested regularly, but for every suggestion there is a notion that they are not needed for a model such as mine. Either paintComponent is not called at all, or queued up until the end (and then still not repainting, even if I use thread.sleep).

I'd appreciate any help. Apologies for the long post.

//Edit: as per request some more code. The entire main method:

public class SimulationController {

static Simulation simulation;

public static final int NUM_EXPERIMENTS = 1;

public SimulationController() 
{
    Random prng = new Random();
    SimulationController.simulation = new Simulation(prng);
}

public void run() {

    setupGUI();
    for(int i=0; i<NUM_EXPERIMENTS; i++) {
        System.out.println("Simulation start. Experiment number: " + i);
        simulation.getWorld().addObserver(simulation);
        simulation.addObserver(simulation.getWorld());
        simulation.run();
    }
}

public void setupGUI()
{
    SwingUtilities.invokeLater(new Runnable() 
    {
        public void run() {
            System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
            SugarFrame frame = new SugarFrame(simulation.getWorld());
            frame.setVisible(true);
        }

    });

}


public static void main(String[] args) {
    SimulationController controller = new SimulationController();
    controller.run();

}

}

The paint override in my JPanel class:

    @Override
public void paintComponent(Graphics g) {
    System.out.println(">>>>>>>>paintComponent called, on EDT? " + SwingUtilities.isEventDispatchThread()+"<<<<<<<<<<");
    super.paintComponent(g);
    //g.clearRect(0, 0, getWidth(), getHeight());
    rectWidth = getWidth() / world.getSizeX();
    rectHeight = getHeight() / world.getSizeY();

    for (int i = 0; i < world.getSizeX(); i++) 
    {
        for (int j = 0; j < world.getSizeY(); j++) 
        {
            // Upper left corner of this terrain rect
            x = i * rectWidth;
            y = j * rectHeight;
            Tile tile = world.getTile(new Position(i, j));
            if (tile.hasAgent()) 
            {
                g.setColor(Color.red);
            } else 
                {
                g.setColor(Color.black);
                }

            g.fillRect(x, y, rectWidth, rectHeight);
        }

    }
   } 

JPanel class again, update methods:

    public void update(Observable o, Object arg) 
{   
    if (arg instanceof TickEnd)
    {
        TickEvent tickEndevent = new TickEvent();
        this.addTickEvent(tickEndevent);
    }

    }
} 

   private final BlockingQueue<TickEvent> TICK_EVENTS = new LinkedBlockingQueue<TickEvent>();

/**Runnable object that updates the GUI (I think)**/
private final Runnable processEventsRunnable = new Runnable()
{
    public void run()
    {
        TickEvent event = new TickEvent();
        while ((event = TICK_EVENTS.poll()) != null)
                {
                System.out.println("This is within processEventsRunnable, inside the While loop. Repaint is called now.");
                repaint();
                }

    }
};


/**Add Event to the processing-Events-queue**/
public void addTickEvent(TickEvent event)
{
    //System.out.println("This is in the Add TickEvent method, but before the adding.  "+TICK_EVENTS.toString());

    TICK_EVENTS.add(event);
    System.out.println("TickEvent has been added!  "+TICK_EVENTS.toString() + "On EDT?" + SwingUtilities.isEventDispatchThread());


    if (TICK_EVENTS.size() >= 1)
    {
        SwingUtilities.invokeLater(processEventsRunnable);
    }
}

And last but not least, the JFrame constructor:

/** Sugarframe Constructor**/
public SugarFrame(World world)
{
    super("Sugarscape");    // creates frame, the constructor uses a string argument for the frame title
    grid = new Grid(world); // variable is declared in the class
    add(grid);
    setDefaultCloseOperation(EXIT_ON_CLOSE);  // specifies what happens when user closes the frame. exit_on_close means the program will stop
    this.setContentPane(grid);
    this.getContentPane().setPreferredSize(new Dimension(500, 500));
    this.pack(); // resizes frame to its content sizes (rather than fixed height/width)
    System.out.println("The Sugarframe has been created and Grid added. All on EDT? "+ SwingUtilities.isEventDispatchThread());

    this.setVisible(true); // makes the Frame appear on screen
}
Zoyd
  • 3,449
  • 1
  • 18
  • 27
marts
  • 658
  • 1
  • 10
  • 15
  • Where does the rest of the simulation actually happens? It would be nice to give more code about the rest of the simulation, as it is complicated to know exactly what is going on, just from the traces. – Gnoupi May 21 '14 at 15:11
  • Well I have around 17 classes. One class, simulation, entails all objects and includes the methods for invoking the model actions. SimulationController is the main method setting up the model. SugarFrame is the JFrame, Grid is the JPanel. I try to add a relevant piece of code, as I can't post all of it here. – marts May 21 '14 at 17:20
  • "Now, I have read that by putting the main thread to sleep, you give the EDT time to run the repaint." This makes no sense. The EDT processes blocks of code, mostly events. The EDT "has time" to process the event that you scheduled as long as it is not busy with older events. Obviously processing each event is very quick, the only thing that you have to worry about is that you are not blocking the EDT in one of those events. For exemple calling `sleep` within the EDT is a major problem since that stops all event processing and the GUI freezes. The status of the main thread is irrelevant. – DSquare May 21 '14 at 17:40
  • Can you elaborate on "I need my model separate from the GUI." What do you mean by "model" if it's the logic and data behind the program that is fine and recomended. The tutorials usually call repaint() within other methods simply because they call it after modifying the GUI, wherever that is, there is no point in waiting. Also note that `repaint()` does not actually repaint the GUI but rather schedules the repainting task in the EDT. Whenever it's free it'll be executed. That will be very shortly but nonetheless it is asynchronous with the call. – DSquare May 21 '14 at 17:49
  • Yes by "model" I mean the logic and data of the sugarscape. I.e. Ihave separate classes for the GUI that will rely on events being passed from the main model, rather than the method for consuming sugar or moving containing code relevant to the GUI. Yes it is exactly the scheduling that's giving me the headache. I don't know how I can accommodate for the asynchronous threads. – marts May 21 '14 at 18:09

1 Answers1

1

The sentences,

I need my model separate from the GUI. This presents a problem since 99% of tutorials suggest to call for repaint within other methods.

and

Now, I have read that by putting the main thread to sleep, you give the EDT time to run the repaint.

don't sound quite right to me, so I'll try to clear things up a bit and maybe If you reevaluate the fundamental ideas you had behind those statements you can find the piece of information that you were missing.

First of all, always keep in mind this scheduling model that we were talking about. You can not say "EDT do this for me now!". It is always "EDT here's one more task you need to do, do it when you are done with whatever you are doing". So the EDT has a queue of "tasks" to do and goes through it consuming one by one.

These tasks are usually created by events: pressing a button gives the EDT a task to do, when the state of a component of the GUI changes some listeners may be notified and enqueue some work in the EDT. However, you can also straight up say "EDT execute this piece of code, later". This is what you do with invokeLater, you schedule a work to do in the EDT whenever it's free. Even if you call invokeLater from the EDT the task is scheduled, not executed at the moment.

The same happens with invokeAndWait yes, the code is executed sequentially as if it was executed at the moment, but it is still an scheduled work. So repaint() is no exception to this. repaint() doesn't repaint the GUI, but rather schedules the repainting of the GUI.

However repaint() is exceptional in the sense that it can be called from outside the EDT! This is not surprising now that we know that the only thing that does is scheduling a certain work, it does not actually mess with the GUI so you can call it wherever you want.

This means that the line

SwingUtilities.invokeLater(processEventsRunnable);

where processEventsRunnable basically executes a repaint() is meaningless and the whole tick system overly complex and unnecesary. You just have to call repaint() when you change something on the GUI or on the data that the GUI feeds on so the changes are reflected on the screen.

Furthermore, if you wanted to do something that needs to be executed in the EDT (like changing the text of a Label with the score) you can just put that code in an invokeLater block in your main thread. That will queue and execute the task properly, you don't need to do your own event queue system.

Keeping all this in mind the following makes no sense:

I have read that by putting the main thread to sleep, you give the EDT time to run the repaint

The GUI will be updated on its own shortly after you call repaint(). The main doing a lot of things and calling a lot of repaints does not prevent the GUI from being updated. However, if you want to "sleep" the main so the pace of the changes is slow so the user can appreciate it on the screen, you should use a timer.

So, as long as your main is not accessing GUI values and methods, feel free to call repaint whenever you are done changing the data, periodically or not.

Edit: Also it sounds a little bit weird that you have a main thread doing things. As you read in the concurrency chapter, usually you just create the GUI in the EDT and then the application is mostly event-driven when buttons are pressed and such. If you need to do changes periodically use a timer. You can use auxiliar threads to do specific non-GUI related heavy work, like reading a file. But you don't usually have an auxiliar thread permanently active as part of the design.

The following is a very simple program that moves an square periodically. I just use a timer to change the data and call repaint(). Note that I'm using a SwingTimer (it is executed in the EDT) since I wanted to check the panel width. Otherwise I could run the code of the timer in any thread.

In your case you probably have your "map" stored independently of the GUI, so you just need to check that data to properly move the coordinates of the agent whenever you want (on keyboard press, periodically...).

It looks like this:

example

Full code:

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class MovingSquareTest
{
    int x, y, size, step;
    MyPanel panel;
    Timer timer;

    public static final void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {
                MovingSquareTest app = new MovingSquareTest();
                app.createAndShowGUI();
                app.timer.start();
            }
        });
    }

    public MovingSquareTest()
    {
        x = 0;
        y = 150;
        size = 50;
        step = 50;

        timer = new Timer(500, new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                x += step;
                if (x < 0) x = 0;
                if (x + size > panel.getWidth()) x = panel.getWidth() - size;
                if (x == 0 || x + size == panel.getWidth()) step *= -1;
                panel.repaint();
            }
        });
    }

    public void createAndShowGUI()
    {
        JFrame frame =  new JFrame("Dance, my square!");

        panel = new MyPanel();

        frame.add(panel);

        frame.setSize(600, 400);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

    }

    private class MyPanel extends JPanel
    {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);

            g.drawRect(x, y, size, size);
        }
    }
}
Community
  • 1
  • 1
DSquare
  • 2,458
  • 17
  • 19
  • Thank you for the answer and for the clarification on the sleep-sentence, perhaps I worded it badly. But I do not need something to happen when buttons are pressed, nor do I need a change via a timer. I need a change when the agent in the model has moved. Just like you repaint a tetris form every step it "moves". In your case, actionperformed listens to the timer. But I need it to listen to the square having moved. So: don't repaint when timer hits, but rather: square has moved, repaint. I will look over my main class again tomorrow, thank you very much ! – marts May 22 '14 at 00:32
  • In that case you might find either [PropertyChangeListener](http://docs.oracle.com/javase/tutorial/uiswing/events/propertychangelistener.html) or the [observer-observable pattern](http://www.javaworld.com/article/2077258/learn-java/observer-and-observable.html) interesting. I still wonder under what criteria/circumstances the agent in the model changes. – DSquare May 22 '14 at 00:50
  • I am using Observer-Observable. But calling repaint in update() seems to be too good to be true (as it indeed doesn't work). The agent changes its position, simple as that. The tile it is on changes. – marts May 22 '14 at 23:02
  • @ursath I don't understand your comment/issue. What is doing now? What is supposed to do? What causes the data to change and fire an update? Is a tile a GUI object or jsut a position? – DSquare May 22 '14 at 23:34
  • A tile is a class that contains a position object, which is also a class. A position is simply x and y, two integers. It has no GUI. When I write repaint inside the update method of my Grid(GUI) class, which listens to the tick events and agentmoved-events, the paint requests are not being executed. Not even at the end of the simulation. – marts May 24 '14 at 20:03
  • "repaint in update() seems to be too good to be true" I hope you understand that if it doesn't work is because you have a serious EDT/painting issue in your code, not that repaint has a bug, or something. I can't help you because are not giving useful information. You have to rethink your design. Get rid of the tick system, nothing good will come of it. I am still very confused about how your code works because you have not been clear about the most important point: "What causes the data to change?". You just have to put repaint at the end or just after you call `changeModelXY(x, y)`. – DSquare May 25 '14 at 05:03